Linitialisation dun char [] avec une chaîne est-elle une mauvaise pratique?

Je lisais un fil de discussion intitulé « strlen vs sizeof » sur CodeGuru , et lune des réponses indique que « cest de toute façon [sic] une mauvaise pratique dinitialiser [sic] un char tableau avec une chaîne littérale. « 

Est-ce vrai, ou est-ce juste son opinion (quoique celle dun » membre élite « )?


Voici la question dorigine:

#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?

cest la taille de la sortie

the size of september is 8 and the length is 9

doit sûrement être de 10. Cest comme si elle calculait la taille de la chaîne avant quelle 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 :

Cest de toute façon une mauvaise pratique dinitialiser un tableau de caractères avec une chaîne littérale. Faites donc toujours lune des choses suivantes:

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

Commentaires

  • Notez le  » const  » sur la première ligne. Se pourrait-il que lauteur ait supposé c ++ au lieu de c? En C ++, cest  » mauvaise pratique « , car un littéral doit être const et tout compilateur C ++ récent donnera un avertissement (ou une erreur) à propos de laffectation dun littéral const à un tableau non const.
  • @Andr é C ++ définit les littéraux de chaîne comme des tableaux const, car cest le seul moyen sûr de traiter avec eux. Que C ne ‘ t est le problème, vous avez donc une règle sociale qui applique la chose sûre
  • @Caleth. Je sais, jessayais davantage de faire valoir que lauteur de la réponse abordait la  » mauvaise pratique  » dun point de vue c ++.
  • @Andr é ce nest ‘ une mauvaise pratique en C ++, car elle nest pas ‘ à la pratique , ‘ est une simple erreur de type. Cela devrait être une erreur de type en C, mais ce nest pas ‘ t, vous devez donc avoir une règle de guide de style vous indiquant  » Il ‘ est interdit  »

Réponse

Cest de toute façon une mauvaise pratique dinitialiser un tableau de caractères avec une chaîne littérale.

Lauteur de ce commentaire ne le justifie jamais vraiment, et je trouve cette déclaration déroutante.

En C (et vous « avez marqué ceci comme C), cela » Cest à peu près le seul moyen d initialiser un tableau de char avec une valeur de chaîne (linitialisation est différente de laffectation). Vous pouvez écrire lun ou lautre

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 linitialiseur. Les chaînes littérales sont stockées sous forme de tableaux de char avec un octet de fin de 0, donc la taille du tableau est 8 (« o », « c », « t », « o », « b », « e », « r », 0). Dans les deux seconds cas, la taille du tableau est spécifiée dans le cadre de la déclaration (8 et MAX_MONTH_LENGTH, quoi que ce soit).

Ce que vous ne pouvez pas faire, cest é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 na été spécifiée et il ny a pas dinitialiseur à partir duquel prendre la taille. Dans les deux Dans certains cas, = ne fonctionnera pas car a) une expression de tableau telle que string peut ne pas être la cible dune affectation et b) lopérateur = nest pas défini pour copier le contenu dun tableau dans un autre de toute façon.

Avec ce même token, vous ne pouvez pas « t écrire

char string[] = foo; 

foo est un autre tableau de char. Cette forme dinitialisation ne fonctionnera quavec des chaînes littérales.

EDIT

Je devrais modifier ceci pour dire que vous pouvez également initialiser 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 dutiliser des chaînes littérales.

EDIT 2

Afin de attribuer le contenu dun tableau en dehors dune déclaration, vous devrez utiliser soit strcpy/strncpy (pour les chaînes terminées par 0) soit 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! 

Commentaires

  • strncpy est rarement la bonne réponse
  • @KeithThompson: pas en désaccord, je viens de lajouter par souci dexhaustivité ‘.
  • Veuillez noter que char[8] str = "october"; est une mauvaise pratique. Jai dû littéralement compter moi-même pour massurer que ce nétait ‘ t un débordement et que cela tombe en panne sous maintenance … la correction dune faute dorthographe de seprate à separate sera cassée si la taille nest pas mise à jour.
  • Je suis daccord avec djechlin, ça est une mauvaise pratique pour les raisons invoquées. La réponse de JohnBode ‘ ne fait ‘ aucun commentaire sur la  » mauvaise pratique  » (qui est la partie principale de la question !!), il explique simplement ce que vous pouvez ou ne pouvez pas faire pour initialiser le tableau.
  • Mineur: Comme ‘ length  » valeur renvoyée par strlen() ninclut pas le caractère nul, en utilisant MAX_MONTH_LENGTH pour contenir la taille maximale nécessaire pour char string[] souvent semble faux. OMI, MAX_MONTH_SIZE serait mieux ici.

Réponse

Le seul problème dont je me souviens est dattribuer une chaîne littérale à 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); } 

Ceci sur ma plate-forme (Linux) se bloque alors quil essaie décrire sur la page marquée en lecture seule. Sur dautres plates-formes, il peut afficher « septembre », etc.

Cela dit – linitialisation par littéral rend le montant spécifique de la réservation, donc cela ne fonctionnera pas:

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

Mais ce sera

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

Comme dernière remarque – je nutiliserais pas strcpy du tout:

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écurisé 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"; 

Commentaires

  • Il y a ‘ toujours un risque de dépassement de tampon sur ce strncpy car il ne ‘ t null termine la chaîne copiée lorsque la longueur de something_else est supérieur à sizeof(buf). Je règle généralement le dernier caractère buf[sizeof(buf)-1] = 0 pour me protéger de cela, ou si buf est initialisé à zéro, utilisez sizeof(buf) - 1 comme longueur de copie.
  • Utilisez strlcpy ou strcpy_s ou même snprintf si vous devez le faire.
  • Corrigé. Malheureusement, il n’existe pas de moyen portable simple de le faire, sauf si vous avez le luxe de travailler avec les derniers compilateurs (strlcpy et snprintf ne sont pas directement accessibles sur MSVC, au moins les commandes et strcpy_s ne sont pas sur * nix).
  • @MaciejPiechotka: Eh bien, Dieu merci, Unix a rejeté lannexe k sponsorisée par microsoft.

Réponse

Principalement parce que vous naurez « pas la taille du char[] dans une variable / construction que vous pouvez facilement utiliser dans le programme.

Lexemple de code du lien:

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

string est alloué sur la pile avec une longueur de 7 ou 8 caractères. Je ne me souviens pas sil est terminé par un nul de cette façon ou non – le fil de discussion auquel vous avez lié a déclaré quil était .

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

Un autre défi survient si vous passez string à une autre fonctionafin que lautre fonction puisse écrire dans le tableau. Vous devez indiquer à lautre fonction la durée du tableau pour que cela ne crée pas de dépassement. Vous pouvez transmettre string avec le résultat de strlen() mais le fil explique comment cela peut exploser si string nest pas terminé par un nul.

Vous êtes mieux lotis 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 à lautre fonction. Les commentaires de @John Bode sont corrects et il existe des moyens datténuer ces risques. Ils nécessitent également plus defforts de votre part pour les utiliser.

Daprès mon expérience, la valeur jai initialisé le char[] to est généralement trop petit pour les autres valeurs que je dois y placer. Lutilisation dune 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 effectuer une vérification avant lappel à 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 « ll doit également transmettre sa taille physique: foo (array, sizeof array / sizeof *array);. – John Bode

Commentaires

  • sizeof string vous donnera la taille du buffer (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 effectuer une vérification avant lappel à 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 transmettre le tableau à une fonction, vous ‘ devez également transmettre sa taille physique: foo (array, sizeof array / sizeof *array);.
  • @JohnBode – merci, et ce sont de bons points. Jai intégré votre commentaire dans ma réponse.
  • Plus précisément, la plupart des références au nom du tableau string entraînent une conversion implicite en char*, pointant vers le premier élément du tableau. Cela perd les informations sur les limites du tableau. Un appel de fonction nest que lun des nombreux contextes dans lesquels cela se produit. char *ptr = string; en est une autre. Même string[0] en est un exemple; lopérateur [] fonctionne sur les pointeurs, pas directement sur les tableaux. Suggestion de lecture: Section 6 de la FAQ comp.lang.c .
  • Enfin une réponse qui fait réellement référence à la question!

Réponse

Une chose quaucun fil 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 le memcpy. Le standard C insiste sur le fait que si une partie dun tableau est initialisée, tout lest. Donc, dans ce cas, il est préférable de le faire vous-même. Je pense que cest peut-être ce à quoi treuss voulait en venir.

Bien sûr

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

vaut mieux que:

char whopping_big[8192] = {0}; 

ou

char whopping_big[8192] = ""; 

ps Pour points bonus, vous pouvez faire:

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

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

Réponse

Je pense que lidée de « mauvaise pratique » vient du fait que cette forme:

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

crée implicitement un 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 code ça na probablement pas dimportance)

Commentaires

  • Ne ferait pas ‘ t il ne ferait quune copie si vous essayez de le modifier? Je pense que le compilateur serait plus intelligent que ça
  • Et les cas comme char time_buf[] = "00:00"; où vous ‘ allez-vous modifier un tampon? Un char * initialisé à une chaîne littérale est défini sur ladresse du premier octet, donc essayer de le modifier entraîne un comportement indéfini car la méthode de stockage de la chaîne littérale ‘ est inconnue (implémentation définie), alors que la modification des octets dun char[] est parfaitement légale car le linitialisation copie les octets dans un espace inscriptible alloué sur la pile. Pour dire que ‘ s  » moins efficace ou  » mauvaise pratique  » sans élaborer sur les nuances de char* vs char[] est trompeur.

Réponse

Jamais est vraiment long, mais vous devriez éviter linitialisation char [] à string, car « string » est const char *, et vous laffectez à char *. Donc, si vous passez ce char [] à la méthode qui change les données, vous pouvez avoir un comportement intéressant.

Comme le dit commend, jai mélangé un peu char [] avec char *, ce nest pas bon car ils diffèrent un peu.

Il ny a rien de mal à affecter des données à un tableau de caractères, mais comme lintention dutiliser ce tableau est de lutiliser comme « chaîne » (char *), il est facile doublier que vous ne devez pas le modifier tableau.

Commentaires

  • Incorrect. Linitialisation copie le contenu de la chaîne littérale dans le tableau. Lobjet tableau est n ‘ t const sauf si vous le définissez de cette façon.(Et les littéraux de chaîne en C ne sont pas const, bien que toute tentative de modification dun littéral de chaîne ait un comportement indéfini.) char *s = "literal"; a le type de comportement dont vous ‘ parlez; il ‘ est mieux écrit comme const char *s = "literal";
  •  » Et en général  » asdf  » est une constante, elle doit donc être déclarée const.  » – Le même raisonnement appellerait un const sur int n = 42;, car 42 est une constante.
  • Cela na ‘ quelle machine que vous ‘ allumée. La norme de langue garantit que c est modifiable. Elle ‘ est une garantie exactement aussi forte que celle que 1 + 1 évalue à 2. Si le programme auquel jai lié ci-dessus fait autre chose que limpression de EFGH, cela indique une implémentation C non conforme.
  • @Dainus: le compilateur MSVC a une optimisation appelée ‘ string pooling ‘ qui mettra une seule copie de chaînes identiques dans un segment en lecture seule sil peut garantir que leur utilisation est en lecture seule. Désactivez loptimisation pour voir le comportement ‘ normal ‘. Pour info,  » Modifier et continuer  » nécessite que cette option soit activée. Plus dinformations ici: msdn.microsoft.com/en-us/library/s0s0asdt.aspx
  • Je pense que Dainius suggère que dans de nombreux cas lerreur est que la variable elle-même doit être marquée const char *const pour empêcher la modification des octets ou du pointeur lui-même, mais dans de nombreux cas, les programmeurs laisseront lun ou les deux mutables permettant à un certain code dexécution de modifier ce qui semble être une constante typée (mais pas une constante).

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *