A inicialização de um char [] com uma prática literal literal de cadeia de caracteres?


44

Eu estava lendo um tópico intitulado "strlen vs sizeof" no CodeGuru , e uma das respostas afirma que "de qualquer maneira [sic] é uma prática recomendada inicializar [sic] uma charmatriz 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;
}

direito. o tamanho deve ser o comprimento mais 1 sim?

esta é a saída

the size of september is 8 and the length is 9

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 uma matriz de caracteres com uma string literal. Portanto, sempre siga um destes procedimentos:

const char string1[] = "october";
char string2[20]; strcpy(string2, "september");

Observe o "const" na primeira linha. Será que o autor assumiu c ++ em vez de c? No c ++, é uma "má prática", porque um literal deve ser const e qualquer compilador c ++ recente emitirá um aviso (ou erro) sobre a atribuição de um literal const a uma matriz não const.
André

@ André C ++ define literais de string como matrizes const, porque essa é a única maneira segura de lidar com eles. Que C não é o problema, então você tem uma regra social que impõe a coisa segura
Caleth

@Caleth. Eu sei, eu estava mais tentando argumentar que o autor da resposta estava abordando a "má prática" de uma perspectiva c ++.
André

@ André, não é uma prática ruim em C ++, porque não é uma prática , é um erro de tipo direto. Ele deve ser um erro de tipo em C, mas não é, por isso você tem que ter uma regra de guia de estilo dizendo que "É proibido"
Caleth

Respostas:


59

De qualquer forma, é uma má prática inicializar uma matriz de caracteres com uma string literal.

O autor desse comentário nunca realmente o justifica, e acho a afirmação intrigante.

Em C (e você marcou isso como C), essa é a única maneira de inicializar uma matriz charcom um valor de sequência (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 charcom um byte final de 0, portanto, o tamanho da matriz é 8 ('o', 'c', 't', 'o', 'b', 'e', ​​'r', 0) Nos dois segundos casos, o tamanho da matriz é especificado como parte da declaração (8 e MAX_MONTH_LENGTH, o que quer que seja).

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 stringestá incompleta porque nenhum tamanho de matriz foi especificado e não há inicializador para o tamanho. Nos dois casos, =isso não funcionará porque a) uma expressão de matriz, como stringpode 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 de qualquer maneira.

Da mesma maneira, você não pode escrever

char string[] = foo;

Onde fooestá outra matriz de char. Essa forma de inicialização funcionará apenas com literais de string.

EDITAR

Devo alterar isso para dizer que você também pode inicializar matrizes para conter uma string com um inicializador no 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.

EDIT 2

Para atribuir o conteúdo de uma matriz fora de uma declaração, você precisará usar strcpy/strncpy(para cadeias 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!


@ KeithThompson: não discordo, apenas o adicionei por uma questão de completude.
John Bode

16
Por favor, note que char[8] str = "october";é uma má prática. Eu tive que literalmente me contar para ter certeza de que não havia transbordamento e quebras sob manutenção ... por exemplo, corrigir um erro ortográfico de sepratepara separatequebrará se o tamanho não for atualizado.
djechlin

1
Eu concordo com djechlin, é uma prática ruim pelas razões expostas. A resposta de JohnBode não comenta nada sobre o aspecto "má prática" (que é a parte principal da pergunta !!), apenas explica o que você pode ou não fazer para inicializar a matriz.
Mašťov

Menor: valor como 'comprimento" voltou de strlen()não incluir o carácter nulo, usando MAX_MONTH_LENGTHpara manter o tamanho máximo necessário para char string[]muitas vezes parece . Errado IMO, MAX_MONTH_SIZEseria melhor aqui.
Chux - Reintegrar Monica

10

O único problema que recordo é atribuir literal de cadeia 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 na minha plataforma (Linux) falha ao tentar gravar na página marcada como somente leitura. Em outras plataformas, pode imprimir 'setembro' etc.

Dito isto - a inicialização por literal faz a quantidade específica de reserva para que isso não funcione:

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 strcpynada:

char buf[8];
strcpy(buf, "very long string very long string"); // Oops. We overwrite some random memory

Enquanto alguns compiladores podem transformá-lo em 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';

Ainda existe o risco de saturação de buffer, strncpyporque ele não encerra a seqüência de caracteres nula quando o comprimento de something_elseé maior que sizeof(buf). Normalmente, defino o último caractere buf[sizeof(buf)-1] = 0a ser protegido ou, se buffor inicializado com zero, use sizeof(buf) - 1como comprimento da cópia.
24616 syockit

Use strlcpyou strcpy_sou mesmo snprintfse for necessário.
usar o seguinte comando

Fixo. Infelizmente, não há uma maneira fácil e portátil de fazer isso, a menos que você tenha o luxo de trabalhar com os compiladores mais recentes ( strlcpye snprintfnão esteja diretamente acessível no MSVC, pelo menos pedidos e strcpy_snão no * nix).
Maciej Piechotka 23/01

@MaciejPiechotka: Bem, graças a Deus o Unix rejeitou o anexo k, patrocinado pela Microsoft.
Deduplicator

6

Uma coisa que nenhum dos tópicos traz é o seguinte:

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 uma matriz é inicializada, tudo é. Portanto, neste caso, é melhor fazer você mesmo. Eu acho que pode ter sido o que Treuss estava chegando.

Com certeza

char whopping_big[8192];
whopping_big[0] = 0;

é melhor que:

char whopping_big[8192] = {0};

ou

char whopping_big[8192] = "";

ps Para pontos de bônus, você pode:

memcpy(whopping_great, "foo", (1/(sizeof("foo") <= sizeof(whopping_great)))*sizeof("foo"));

para lançar um tempo de compilação dividido por zero de erro se você estiver prestes a sobrecarregar a matriz.


5

Principalmente porque você não terá o tamanho de char[]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 me lembro se é terminado por nulo dessa maneira ou não - o segmento ao qual você vinculou afirmou que é.

Copiar "setembro" sobre essa sequência é um óbvio excesso de memória.

Outro desafio ocorre se você passar stringpara outra função para que a outra função possa gravar na matriz. Você precisa dizer a outra função de quanto tempo a matriz é tão -lo não cria uma superação. Você pode transmitir stringo resultado de, strlen()mas o thread explica como isso pode explodir se stringnão for terminado por nulo.

É melhor alocar uma string com um tamanho fixo (de preferência definido como 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 existem maneiras de atenuar esses riscos. Eles também exigem mais esforço de sua parte para usá-los.

Na minha experiência, o valor que eu inicializei char[]é geralmente muito pequeno para os outros valores que eu preciso colocar lá. O uso de uma constante definida ajuda a evitar esse problema.


sizeof stringfornecerá o tamanho do buffer (8 bytes); use o resultado dessa expressão em vez de strlense preocupar com memória.
Da mesma forma, você pode fazer um teste antes da chamada para strcpyver se o seu buffer de destino é grande o suficiente para a cadeia de origem: if (sizeof target > strlen(src)) { strcpy (target, src); }.
Sim, se você tem que passar a matriz para uma função, você precisa passar seu tamanho físico, bem como: foo (array, sizeof array / sizeof *array);. - John Bode


2
sizeof stringfornecerá o tamanho do buffer (8 bytes); use o resultado dessa expressão em vez de strlenquando estiver preocupado com a memória. Da mesma forma, você pode fazer um teste antes da chamada para strcpyver se o seu buffer de destino é grande o suficiente para a cadeia de origem: if (sizeof target > strlen(src)) { strcpy (target, src); }. Sim, se você tem que passar a matriz para uma função, você precisa passar seu tamanho físico, bem como: foo (array, sizeof array / sizeof *array);.
John Bode

1
@ JohnBode - obrigado, e esses são bons pontos. Eu incorporei o seu comentário na minha resposta.

1
Mais precisamente, a maioria das referências ao nome da matriz stringresulta em uma conversão implícita em char*, apontando para o primeiro elemento da matriz. Isso perde as informações dos limites da matriz. Uma chamada de função é apenas um dos muitos contextos em que isso acontece. char *ptr = string;é outro. Even string[0]é um exemplo disso; o []operador trabalha em ponteiros, não diretamente em matrizes. Sugestão de leitura: Secção 6 do FAQ comp.lang.c .
perfil completo de Keith Thompson

Finalmente, uma resposta que realmente se refere à pergunta!
Mašťov

2

Eu acho que a idéia de "má prática" vem do fato de que esta forma:

char string[] = "october is a nice month";

cria implicitamente um strcpy do código da máquina de origem para a pilha.

É mais eficiente manipular apenas um link para essa sequência. Como com:

char *string = "october is a nice month";

ou diretamente:

strcpy(output, "october is a nice month");

(mas é claro que na maioria dos códigos provavelmente não importa)


Não seria apenas uma cópia se você tentar modificá-la? Eu pensaria que o compilador seria mais esperto do que isso
Cole Johnson

1
E os casos em char time_buf[] = "00:00";que você modifica um buffer? Uma char *inicializada para um literal de cadeia é definida como o endereço do primeiro byte, portanto, tentar modificá-lo resulta em comportamento indefinido, porque o método de armazenamento da literal de cadeia é desconhecido (implementação definida), enquanto a modificação dos bytes de a char[]é perfeitamente legal porque a inicialização copia os bytes para um espaço gravável alocado na pilha. Dizer que é "menos eficiente" ou "má prática" sem elaborar as nuances de char* vs char[]é enganoso.
Braden Best

-3

Nunca é realmente muito tempo, mas você deve evitar a inicialização char [] para string, porque "string" é const char * e está sendo atribuída a char *. Portanto, se você passar esse caractere [] para o método que altera os dados, poderá ter um comportamento interessante.

Como recomendado, misturei um pouco char [] com char *, isso não é bom, pois difere um pouco.

Não há nada errado em atribuir dados à matriz de caracteres, mas como a intenção de usá-la é usá-la como 'string' (caractere *), é fácil esquecer que você não deve modificar essa matriz.


3
Incorreta. A inicialização copia o conteúdo da cadeia literal na matriz. O objeto da matriz não é, a constmenos que você o defina dessa maneira. (E os literais de string em C não são const, embora qualquer tentativa de modificar um literal de string tenha um comportamento indefinido.) char *s = "literal";Tem o tipo de comportamento que você está falando; é melhor escrito comoconst char *s = "literal";
Keith Thompson

de fato minha culpa, misturei char [] com char *. Mas eu não teria tanta certeza de copiar o conteúdo para a matriz. A verificação rápida com o compilador MS C mostra que 'char c [] = "asdf";' criará 'string' no segmento const e depois atribuirá esse endereço à variável da matriz. Essa é realmente uma razão pela qual eu disse sobre evitar atribuições a uma matriz não constante de char.
Dainius

Eu sou cético. Experimente este programa e deixe-me saber qual é o resultado obtido.
Keith Thompson

2
"E, geralmente," asdf "é uma constante, portanto deve ser declarado como const." - O mesmo raciocínio exigiria um conston int n = 42;, porque 42é uma constante.
Keith Thompson

1
Não importa em que máquina você está. O padrão de linguagem garante que cé modificável. É exatamente uma garantia tão forte quanto a que é 1 + 1avaliada 2. Se o programa ao qual vinculei acima fizer algo diferente de imprimir EFGH, isso indica uma implementação C não conforme.
Keith Thompson
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.