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;
où 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 deseprate
à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 utilisantMAX_MONTH_LENGTH
pour contenir la taille maximale nécessaire pourchar 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 desomething_else
est supérieur àsizeof(buf)
. Je règle généralement le dernier caractèrebuf[sizeof(buf)-1] = 0
pour me protéger de cela, ou sibuf
est initialisé à zéro, utilisezsizeof(buf) - 1
comme longueur de copie. - Utilisez
strlcpy
oustrcpy_s
ou mêmesnprintf
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
etsnprintf
ne sont pas directement accessibles sur MSVC, au moins les commandes etstrcpy_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 destrlen
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 enchar*
, 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êmestring[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? Unchar *
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 dunchar[]
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 dechar* 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 pasconst
, 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 commeconst 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
surint n = 42;
, car42
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 que1 + 1
évalue à2
. Si le programme auquel jai lié ci-dessus fait autre chose que limpression deEFGH
, 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).