web-dev-qa-db-fra.com

L'initialisation d'un char [] avec une chaîne littérale est-elle une mauvaise pratique?

Je lisais un fil de discussion intitulé "strlen vs sizeof" sur CodeGur , et l'une des réponses déclare que "c'est de toute façon [sic] une mauvaise pratique d'initialiser [sic] un char tableau avec un littéral de chaîne. "

Est-ce vrai, ou est-ce juste son opinion (bien qu'un "membre Elite")?


Voici la question d'origine:

#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;
}

droite. la taille doit être la longueur plus 1 oui?

c'est la sortie

the size of september is 8 and the length is 9

la taille devrait être 10 sûrement. c'est comme s'il calculait la taille de la chaîne avant qu'elle ne soit modifiée par strcpy mais la longueur après.

Y a-t-il un problème avec ma syntaxe ou quoi?


Voici la réponse :

C'est de toute façon une mauvaise pratique d'initialiser un tableau de caractères avec un littéral de chaîne. Effectuez donc toujours l'une des opérations suivantes:

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

C'est de toute façon une mauvaise pratique d'initialiser un tableau de caractères avec un littéral de chaîne.

L'auteur de ce commentaire ne le justifie jamais vraiment, et je trouve la déclaration déroutante.

En C (et vous l'avez étiqueté comme C), c'est à peu près le seul moyen d'initialiser un tableau de char avec un valeur de chaîne (l'initialisation est différente de l'affectation). Vous pouvez écrire soit

char string[] = "october";

ou

char string[8] = "october";

ou

char string[MAX_MONTH_LENGTH] = "october";

Dans le premier cas, la taille du tableau est tirée de la taille de l'initialiseur. Les littéraux de chaîne sont stockés sous forme de tableaux de char avec un octet de fin 0, de sorte que la taille du tableau est de 8 ('o', 'c', 't', 'o', 'b', 'e ',' r ', 0). Dans les deux derniers cas, la taille du tableau est spécifiée dans le cadre de la déclaration (8 et MAX_MONTH_LENGTH, Quoi qu'il arrive).

Ce que vous ne pouvez pas faire est d'écrire quelque chose comme

char string[];
string = "october";

ou

char string[8];
string = "october";

etc. Dans le premier cas, la déclaration de string est incomplète car aucune taille de tableau n'a été spécifiée et il n'y a pas d'initialiseur pour prendre le taille de. Dans les deux cas, = Ne fonctionnera pas car a) une expression de tableau telle que string peut ne pas être la cible d'une affectation et b) l'opérateur = N'est pas ' t défini pour copier le contenu d'un tableau dans un autre de toute façon.

De la même manière, vous ne pouvez pas écrire

char string[] = foo;

foo est un autre tableau de char. Cette forme d'initialisation ne fonctionnera qu'avec des littéraux de chaîne.

MODIFIER

Je devrais modifier cela pour dire que vous pouvez également initialiser des tableaux pour contenir une chaîne avec un initialiseur de style tableau, comme

char string[] = {'o', 'c', 't', 'o', 'b', 'e', 'r', 0};

ou

char string[] = {111, 99, 116, 111, 98, 101, 114, 0}; // assumes ASCII

mais il est plus facile pour les yeux d'utiliser des littéraux de chaîne.

ÉDITER2

Afin d'affecter le contenu d'un tableau en dehors d'une déclaration, vous devez utiliser soit strcpy/strncpy (Pour les chaînes terminées par 0) ) ou memcpy (pour tout autre type de tableau):

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!
62
John Bode

Le seul problème dont je me souvienne est l'attribution d'un littéral de chaîne à 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

Par exemple, prenez ce programme:

#include <stdio.h>

int main() {
  char *var1 = "september";
  char *var2 = "september";
  var1[0] = 'S';
  printf("%s\n", var2);
}

Cela sur ma plate-forme (Linux) se bloque lorsqu'il essaie d'écrire sur une page marquée en lecture seule. Sur d'autres plates-formes, il peut imprimer "septembre", etc.

Cela dit - l'initialisation par littéral fait le montant spécifique de la réservation, donc cela ne fonctionnera pas:

char buf[] = "May";
strncpy(buf, "September", sizeof(buf)); // Result "Sep"

Mais cela

char buf[32] = "May";
strncpy(buf, "September", sizeof(buf));

Comme dernière remarque - je n'utiliserais pas du tout strcpy:

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

Alors que certains compilateurs peuvent le changer en appel sûr strncpy est beaucoup plus sûr:

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';
11
Maciej Piechotka

Principalement parce que vous n'aurez pas la taille du char[] Dans une variable/construction que vous pouvez facilement utiliser dans le programme.

L'exemple de code du lien:

 char string[] = "october";
 strcpy(string, "september");

string est alloué sur la pile en 7 ou 8 caractères. Je ne me souviens pas si elle est terminée par un caractère nul de cette façon ou non - le fil auquel vous avez lié l'a déclaré.

Copier "septembre" sur cette chaîne est un dépassement de mémoire évident.

Un autre défi survient si vous passez string à une autre fonction pour que l'autre fonction puisse écrire dans le tableau. Vous devez dire à l'autre fonction combien de temps le tableau est ainsi il ne crée pas de dépassement. Vous pouvez passer string avec le résultat de strlen() mais le thread explique comment cela peut exploser si string n'est pas terminé par null.

Il vaut mieux allouer une chaîne avec une taille fixe (de préférence définie comme une constante), puis passer le tableau et la taille fixe à l'autre fonction. Les commentaires de @John Bode sont corrects et il existe des moyens d'atténuer ces risques. Ils nécessitent également plus d'efforts de votre part pour les utiliser.

D'après mon expérience, la valeur à laquelle j'ai initialisé le char[] Est généralement trop petite pour les autres valeurs que je dois y placer. L'utilisation d'une constante définie permet d'éviter ce problème.


sizeof string Vous donnera la taille du tampon (8 octets); utilisez le résultat de cette expression au lieu de strlen lorsque vous êtes préoccupé par la mémoire.
De même, vous pouvez vérifier avant l'appel à strcpy pour voir si votre tampon cible est suffisamment grand pour la chaîne source: if (sizeof target > strlen(src)) { strcpy (target, src); }.
Oui, si vous devez passer le tableau à une fonction, vous devrez également transmettre sa taille physique: foo (array, sizeof array / sizeof *array);. - John Bode

6
user53019

Une chose qu'aucun des deux fils n'évoque est la suivante:

char whopping_great[8192] = "foo";

vs.

char whopping_great[8192];
memcpy(whopping_great, "foo", sizeof("foo"));

Le premier fera quelque chose comme:

memcpy(whopping_great, "foo", sizeof("foo"));
memset(&whopping_great[sizeof("foo")], 0, sizeof(whopping_great)-sizeof("foo"));

Ce dernier ne fait que la memcpy. La norme C insiste sur le fait que si une partie d'un tableau est initialisée, c'est tout. Dans ce cas, il vaut mieux le faire vous-même. Je pense que c'est peut-être là où voulait en venir Treuss.

Pour sûr

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

vaut mieux que:

char whopping_big[8192] = {0};

ou

char whopping_big[8192] = "";

p.s. Pour les points bonus, vous pouvez faire:

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

pour jeter un temps de compilation divisé par une erreur de zéro si vous êtes sur le point de déborder le tableau.

6
Richard Fife

Je pense que l'idée de "mauvaise pratique" vient du fait que cette forme:

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

crée implicitement une strcpy du code machine source vers la pile.

Il est plus efficace de gérer uniquement un lien vers cette chaîne. Comme avec:

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

ou directement:

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

(mais bien sûr, dans la plupart des codes, cela n'a probablement pas d'importance)

2
toto