Eu estava lendo um tópico intitulado “strlen vs sizeof” no CodeGuru e uma das respostas afirma que “é” de qualquer maneira [sic] uma má prática inicializar [sic] uma matriz char
com uma string literal. “
Isso é verdade ou é apenas a opinião dele (embora seja um” membro de elite “)?
Aqui está a pergunta original:
#include <stdio.h> #include<string.h> main() { char string[] = "october"; strcpy(string, "september"); printf("the size of %s is %d and the length is %d\n\n", string, sizeof(string), strlen(string)); return 0; }
certo. o tamanho deve ser o comprimento mais 1 sim?
este é a saída
the size of september is 8 and the length is 9
o tamanho deve ser 10 certamente. é como calcular o tamanho da string antes de ser alterado por strcpy, mas o comprimento depois.
Há algo errado com minha sintaxe ou o quê?
Aqui está a resposta :
De qualquer forma, é uma má prática inicializar um array de char com uma string literal. Portanto, sempre faça um dos seguintes:
const char string1[] = "october"; char string2[20]; strcpy(string2, "september");
Comentários
- Observe o ” const ” na primeira linha. Será que o autor assumiu c ++ em vez de c? Em c ++, é ” má prática “, porque um literal deve ser const e qualquer compilador c ++ recente fornecerá um aviso (ou erro) sobre como atribuir um literal const a um array não const.
- @Andr é C ++ define literais de string como arrays const, porque essa é a única maneira segura de lidar com eles. Que C não ‘ t é o problema, então você tem uma regra social que impõe a coisa segura
- @Caleth. Eu sei, eu estava mais tentando argumentar que o autor da resposta estava se aproximando da ” má prática ” de uma perspectiva c ++.
- @Andr é não é ‘ uma má prática em C ++, porque não é ‘ ta praticar , é ‘ um erro de tipo direto. Deve ser um erro de tipo em C, mas não é ‘ t, então você deve ter uma regra de guia de estilo informando ” É ‘ proibido ”
Resposta
De qualquer forma, é uma má prática inicializar uma matriz de caracteres com um literal de string.
O autor desse comentário nunca o justifica, e acho a afirmação intrigante.
Em C (e você “marcou isso como C), isso” É praticamente a única maneira de inicializar uma matriz de char
com um valor de string (a inicialização é diferente da atribuição). Você pode escrever
char string[] = "october";
ou
char string[8] = "october";
ou
char string[MAX_MONTH_LENGTH] = "october";
No primeiro caso, o tamanho da matriz é obtido do tamanho do inicializador. Literais de string são armazenados como matrizes de char
com um byte de terminação 0, então o tamanho da matriz é 8 (“o”, “c”, “t”, “o”, “b”, “e”, “r”, 0). Nos segundos dois casos, o tamanho da matriz é especificado como parte da declaração (8 e MAX_MONTH_LENGTH
, seja o que for).
O que você não pode fazer é escrever algo como
char string[]; string = "october";
ou
char string[8]; string = "october";
etc. No primeiro caso, a declaração de string
é incompleta porque nenhum tamanho de array foi especificado e não há inicializador para obter o tamanho. Em ambos casos, o =
não funcionará porque a) uma expressão de matriz como string
pode não ser o alvo de uma atribuição eb) o operador =
não está definido para copiar o conteúdo de uma matriz para outra.
Com esse mesmo token, você não pode “escrever
char string[] = foo;
onde foo
é outra matriz de char
. Esta forma de inicialização só funcionará com literais de string.
EDITAR
Devo corrigir isso para dizer que você também pode inicializar arrays para conter uma string com um inicializador de estilo de matriz, como
char string[] = {"o", "c", "t", "o", "b", "e", "r", 0};
ou
char string[] = {111, 99, 116, 111, 98, 101, 114, 0}; // assumes ASCII
mas é mais fácil para os olhos usar literais de string.
EDITAR 2
Para atribuir o conteúdo de uma matriz fora de uma declaração, você precisaria usar strcpy/strncpy
(para strings terminadas em 0) ou memcpy
(para qualquer outro tipo de matriz):
if (sizeof string > strlen("october")) strcpy(string, "october");
ou
strncpy(string, "october", sizeof string); // only copies as many characters as will // fit in the target buffer; 0 terminator // may not be copied, but the buffer is // uselessly completely zeroed if the // string is shorter!
Comentários
Resposta
O único problema de que me lembro é atribuir literal de string a char *
:
char var1[] = "september"; var1[0] = "S"; // Ok - 10 element char array allocated on stack char const *var2 = "september"; var2[0] = "S"; // Compile time error - pointer to constant string char *var3 = "september"; var3[0] = "S"; // Modifying some memory - which may result in modifying... something or crash
Por exemplo, pegue este programa:
#include <stdio.h> int main() { char *var1 = "september"; char *var2 = "september"; var1[0] = "S"; printf("%s\n", var2); }
Isso em minha plataforma (Linux) trava ao tentar gravar na página marcada como somente leitura. Em outras plataformas, pode imprimir “setembro” etc.
Dito isso – a inicialização por literal cria a quantidade específica de reserva, portanto, não funcionará:
char buf[] = "May"; strncpy(buf, "September", sizeof(buf)); // Result "Sep"
Mas isso vai
char buf[32] = "May"; strncpy(buf, "September", sizeof(buf));
Como última observação – eu não usaria strcpy
em tudo:
char buf[8]; strcpy(buf, "very long string very long string"); // Oops. We overwrite some random memory
Embora alguns compiladores possam alterá-lo para uma chamada segura, strncpy
é muito mais seguro:
char buf[1024]; strncpy(buf, something_else, sizeof(buf)); // Copies at most sizeof(buf) chars so there is no possibility of buffer overrun. Please note that sizeof(buf) works for arrays but NOT pointers. buf[sizeof(buf) - 1] = "\0";
Comentários
- Há ‘ ainda risco de saturação do buffer naquele
strncpy
porque não ‘ termina a string copiada quando o comprimento desomething_else
é maior quesizeof(buf)
. Normalmente defino o último caracterebuf[sizeof(buf)-1] = 0
para proteger contra isso, ou sebuf
for inicializado com zero, usesizeof(buf) - 1
como o comprimento da cópia. - Use
strlcpy
oustrcpy_s
ou mesmosnprintf
se for necessário. - Corrigido. Infelizmente, não existe uma maneira fácil de fazer isso, a menos que você se dê ao luxo de trabalhar com os compiladores mais novos (
strlcpy
esnprintf
não são diretamente acessíveis no MSVC, pelo menos os pedidos estrcpy_s
não estão no * nix). - @MaciejPiechotka: Bem, graças a Deus, o Unix rejeitou o anexo k patrocinado pela microsoft.
Resposta
Principalmente porque você não terá o tamanho de char[]
em uma variável / construção que você pode usar facilmente dentro do programa.
O exemplo de código do link:
char string[] = "october"; strcpy(string, "september");
string
é alocado na pilha com 7 ou 8 caracteres. Não consigo me lembrar se é terminado em nulo desta forma ou não – o tópico vinculado afirmou que é .
Copiar “setembro” sobre essa string é uma saturação de memória óbvia.
Outro desafio surge se você passar string
para outra funçãopara que a outra função possa escrever no array. Você precisa dizer à outra função quanto tempo a matriz é, para que ela não crie uma saturação. Você poderia passar string
junto com o resultado de strlen()
mas o tópico explica como isso pode explodir se string
não tiver terminação nula.
Você está melhor alocar uma string com um tamanho fixo (de preferência definido como uma constante) e depois passar a matriz e o tamanho fixo para a outra função. Os comentários de @John Bode “estão corretos e há maneiras de mitigar esses riscos. Eles também exigem mais esforço de sua parte para usá-los.
Na minha experiência, o valor inicializei char[]
to geralmente é muito pequeno para os outros valores que preciso colocar lá. Usar uma constante definida ajuda a evitar esse problema.
sizeof string
fornecerá o tamanho do buffer (8 bytes); use o resultado dessa expressão em vez de strlen
quando estiver preocupado com a memória.
Da mesma forma, você pode fazer uma verificação antes da chamada para strcpy
para ver se seu buffer de destino é grande o suficiente para a string de origem: if (sizeof target > strlen(src)) { strcpy (target, src); }
.
Sim, se você tiver que passar a matriz para uma função, você “vai também precisa passar seu tamanho físico: foo (array, sizeof array / sizeof *array);
. – John Bode
Comentários
-
sizeof string
fornecerá o tamanho do buffer (8 bytes); use o resultado dessa expressão em vez destrlen
quando você ‘ estiver preocupado com a memória. Da mesma forma, você pode fazer uma verificação antes da chamada parastrcpy
para ver se seu buffer de destino é grande o suficiente para a string de origem:if (sizeof target > strlen(src)) { strcpy (target, src); }
. Sim, se você tiver que passar a matriz para uma função, ‘ precisará passar seu tamanho físico também:foo (array, sizeof array / sizeof *array);
. - @JohnBode – obrigado, e esses são bons pontos. Eu incorporei seu comentário em minha resposta.
- Mais precisamente, a maioria das referências ao nome da matriz
string
resulta em uma conversão implícita parachar*
, apontando para o primeiro elemento da matriz. Isso perde as informações de limites da matriz. Uma chamada de função é apenas um dos muitos contextos em que isso acontece.char *ptr = string;
é outro. Mesmostring[0]
é um exemplo disso; o operador[]
funciona em ponteiros, não diretamente em matrizes. Leitura sugerida: Seção 6 das perguntas frequentes do comp.lang.c . - Finalmente uma resposta que realmente se refere à pergunta!
Resposta
Uma coisa que nenhum dos tópicos traz é esta:
char whopping_great[8192] = "foo";
vs.
char whopping_great[8192]; memcpy(whopping_great, "foo", sizeof("foo"));
O primeiro fará algo como:
memcpy(whopping_great, "foo", sizeof("foo")); memset(&whopping_great[sizeof("foo")], 0, sizeof(whopping_great)-sizeof("foo"));
O último faz apenas o memcpy. O padrão C insiste que, se qualquer parte de um array for inicializada, tudo será. Portanto, neste caso, é melhor fazer você mesmo. Acho que pode ter sido isso que treuss queria chegar.
Com certeza
char whopping_big[8192]; whopping_big[0] = 0;
é melhor do que:
char whopping_big[8192] = {0};
ou
char whopping_big[8192] = "";
ps For pontos de bônus, você pode fazer:
memcpy(whopping_great, "foo", (1/(sizeof("foo") <= sizeof(whopping_great)))*sizeof("foo"));
para lançar uma divisão do tempo de compilação por erro zero se você estiver prestes a estourar o array.
Resposta
Acho que a ideia de “prática ruim” vem do fato de que este formulário:
char string[] = "october is a nice month";
cria implicitamente um strcpy do código fonte da máquina para a pilha.
É mais eficiente manipular apenas um link para aquela string. Como com:
char *string = "october is a nice month";
ou diretamente:
strcpy(output, "october is a nice month");
(mas é claro na maioria código provavelmente não importa)
Comentários
- Não ‘ faria apenas uma cópia se você tentar modificá-lo? Eu acho que o compilador seria mais inteligente do que isso
- E casos como
char time_buf[] = "00:00";
onde você ‘ você vai modificar um buffer? Umchar *
inicializado em uma string literal é definido como o endereço do primeiro byte, então tentar modificá-lo resulta em um comportamento indefinido porque o método do armazenamento literal da string ‘ s é desconhecido (implementação definida), enquanto a modificação dos bytes de umchar[]
é perfeitamente legal porque o a inicialização copia os bytes para um espaço gravável alocado na pilha. Para dizer que ‘ s ” menos eficiente ou ” má prática ” sem entrar em detalhes sobre as nuances dechar* vs char[]
é enganoso.
Resposta
Nunca é muito tempo, mas você deve evitar a inicialização char [] para string, porque “string” é const char *, e você está atribuindo a char *. Portanto, se você passar este char [] para o método que altera os dados, você pode ter um comportamento interessante.
Como recomendou, misturei um pouco de char [] com char *, isso não é bom, pois eles diferem um pouco.
Não há nada de errado em atribuir dados ao array char, mas como a intenção de usar esse array é usá-lo como “string” (char *), é fácil esquecer que você não deve modificar isso array.
Comentários
- Incorreto. A inicialização copia o conteúdo do literal de string para o array. O objeto de array isn ‘ t
const
a menos que você defina dessa forma.(E os literais de string em C não sãoconst
, embora qualquer tentativa de modificar um literal de string tenha comportamento indefinido.)char *s = "literal";
tem o tipo de comportamento que você ‘ está falando; ‘ é melhor escrito comoconst char *s = "literal";
- ” E geralmente ” asdf ” é uma constante, então deve ser declarada como const. ” – O mesmo raciocínio exigiria um
const
emint n = 42;
, porque42
é uma constante. - Não ‘ importa qual máquina você ‘ está ligada. O padrão de linguagem garante que
c
seja modificável. É ‘ uma garantia exatamente tão forte quanto a que1 + 1
avalia como2
. Se o programa ao qual vinculei acima fizer algo diferente de imprimirEFGH
, isso indica uma implementação C não conforme. - @Dainus: o compilador MSVC tem uma otimização chamada ‘ string pooling ‘ que colocará uma única cópia de strings idênticas em um segmento somente leitura se puder garantir que seus usos sejam somente leitura. Desative a otimização para ver o comportamento ‘ normal ‘. Para sua informação, o ” Editar e continuar ” requer que esta opção esteja ativada. Mais informações aqui: msdn.microsoft.com/en-us/library/s0s0asdt.aspx
- Acho que Dainius está sugerindo que em muitos casos, o erro é que a própria variável deve ser marcada
const char *const
para evitar a modificação dos bytes ou do próprio ponteiro, mas em muitos casos os programadores deixarão um ou ambos mutáveis, permitindo que algum código de tempo de execução modifique o que parece ser uma constante digitada (mas não é constante).
strncpy
raramente é a resposta certachar[8] str = "october";
é uma má prática. Eu tive que literalmente contar a mim mesmo para ter certeza de que não era ‘ um estouro e quebre durante a manutenção … por exemplo, corrigir um erro ortográfico deseprate
paraseparate
será interrompido se o tamanho não for atualizado.strlen()
não inclui o caractere nulo, usandoMAX_MONTH_LENGTH
para manter o tamanho máximo necessário parachar string[]
frequentemente parece errado. IMO,MAX_MONTH_SIZE
seria melhor aqui.