Je lisais un fil de discussion intitulé "strlen vs sizeof" sur CodeGuru , et l' une des réponses indique qu '"il est de toute façon [sic] mauvaise pratique d'initialiser un char
tableau avec un littéral de chaîne".
Est-ce vrai ou s'agit-il simplement de son opinion (bien que "membre d'élite")?
Voici la question initiale:
#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 devrait ê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 si on calculait la taille de la chaîne avant qu'elle ne soit modifiée par strcpy mais par la longueur suivante.
Y at-il quelque chose qui ne va pas 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. Alors, faites toujours l’une des choses suivantes:
const char string1[] = "october";
char string2[20]; strcpy(string2, "september");
la source
Réponses:
L'auteur de ce commentaire ne le justifie jamais vraiment et je trouve la déclaration déconcertante.
En C (et vous l'avez étiqueté C), c'est à peu près la seule façon d' initialiser un tableau
char
avec une valeur de chaîne (l'initialisation est différente de l'attribution). Vous pouvez écrire soitou
ou
Dans le premier cas, la taille du tableau est prise à partir de la taille de l'initialiseur. Les littéraux de chaîne sont stockés en tant que tableaux de
char
0 octet de fin; la taille du tableau est donc 8 ('o', 'c', 't', 'o', 'b', 'e', 'r', 0) Dans les deux autres cas, la taille du tableau est spécifiée dans la déclaration (8 etMAX_MONTH_LENGTH
quoi que ce soit).Ce que vous ne pouvez pas faire, c'est écrire quelque chose comme
ou
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 aucun initialiseur à prendre la taille. Dans les deux cas,=
cela ne fonctionnera pas car a) une expression de tableau telle questring
pouvant ne pas être la cible d'une affectation et b) l'=
opérateur n'est pas défini pour copier le contenu d'un tableau dans un autre.De même, vous ne pouvez pas écrire
où
foo
est un autre tableau dechar
. Cette forme d'initialisation ne fonctionnera qu'avec les 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 type tableau, comme
ou
mais il est plus facile pour les yeux d'utiliser des littéraux de chaîne.
EDIT 2
Pour 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), soitmemcpy
(pour tout autre type de tableau):ou
la source
strncpy
est rarement la bonne réponsechar[8] str = "october";
c'est une mauvaise pratique. Je devais littéralement compter les caractères pour m'assurer que ce n'était pas un débordement et qu'il se cassait sous la maintenance ... par exemple, corriger une erreur d'orthographe deseprate
àseparate
se briserait si la taille n'était pas mise à jour.strlen()
n'inclut pas le caractère null. UtiliserMAX_MONTH_LENGTH
pour conserver la taille maximale requise semblechar string[]
souvent incorrect. IMO serait préférable ici.MAX_MONTH_SIZE
Le seul problème que je me rappelle est l’assignation du littéral chaîne à
char *
:Par exemple, prenez ce programme:
Ceci 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 le littéral fait le montant spécifique de la réservation pour que cela ne fonctionne pas:
Mais ce sera
Dernière remarque - je n’utiliserais pas
strcpy
du tout:Bien que certains compilateurs puissent le transformer en appel sécurisé, cela
strncpy
est beaucoup plus sûr:la source
strncpy
cause de cela, car la chaîne copiée n'est pas terminée lorsque la longueursomething_else
est supérieure àsizeof(buf)
. J'ai l'habitude de définir le dernier caractèrebuf[sizeof(buf)-1] = 0
à protéger, ou, s'ilbuf
est initialisé à zéro, de l'utilisersizeof(buf) - 1
comme longueur de copie.strlcpy
oustrcpy_s
ou mêmesnprintf
si vous devez.strlcpy
etsnprintf
ne sont pas directement accessibles sur MSVC, du moins sur les commandes etstrcpy_s
ne sont pas sur * nix).Une chose que ni l'un ni l'autre des fils ne soulève est la suivante:
contre.
Le premier fera quelque chose comme:
Ce dernier ne fait que la mémoire. Le standard C insiste sur le fait que si une partie d’un tableau est initialisée, c’est le cas. Donc, dans ce cas, il vaut mieux le faire soi-même. Je pense que c’est peut-être à cela que voulait arriver Treuss.
Pour sûr
est mieux que soit:
ou
ps Pour les points bonus, vous pouvez faire:
générer une erreur de division du temps de compilation par zéro si vous êtes sur le point de déborder du tableau.
la source
Principalement parce que vous n'avez pas la taille de la
char[]
variable / construction que vous pouvez facilement utiliser dans le programme.L'échantillon de code du lien:
string
est alloué sur la pile sur 7 ou 8 caractères. Je ne me souviens pas si c'est terminé de cette façon ou non - le fil que vous avez lié a déclaré qu'il en était ainsi.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 afin que cette dernière puisse écrire dans le tableau. Vous devez dire à l'autre fonction combien de temps le tableau est si elle ne crée pas un dépassement. Vous pourriez passerstring
avec le résultat de,strlen()
mais le fil de discussion explique comment cela peut exploser s'ilstring
n'est pas terminé par null.Il vaut mieux allouer une chaîne de taille fixe (définie de préférence 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 que j'ai initialisée
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 plutôt questrlen
lorsque vous vous préoccupez de mémoire.De même, vous pouvez faire un chèque avant l'appel
strcpy
pour voir si votre tampon cible est assez grand pour la chaîne source:if (sizeof target > strlen(src)) { strcpy (target, src); }
.Oui, si vous devez passer le tableau à une fonction, vous aurez besoin de passer sa taille physique ainsi:
foo (array, sizeof array / sizeof *array);
. - John Bodela source
sizeof string
vous donnera la taille du tampon (8 octets); utilisez le résultat de cette expression plutôt questrlen
lorsque vous vous préoccupez de mémoire. De même, vous pouvez faire un chèque avant l'appelstrcpy
pour voir si votre tampon cible est assez grand pour la chaîne source:if (sizeof target > strlen(src)) { strcpy (target, src); }
. Oui, si vous devez passer le tableau à une fonction, vous aurez besoin de passer sa taille physique ainsi:foo (array, sizeof array / sizeof *array);
.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 n'est que l'un des nombreux contextes dans lesquels cela se produit.char *ptr = string;
est un autre. Even enstring[0]
est un exemple. l'[]
opérateur travaille sur des pointeurs, pas directement sur des tableaux. Suggestion de lecture: L' article 6 de la FAQ comp.lang.c .Je pense que l'idée de "mauvaise pratique" vient du fait que cette forme:
fait implicitement une strcpy du code de la machine source à la pile.
Il est plus efficace de gérer uniquement un lien vers cette chaîne. J'aime avec:
ou directement:
(mais bien sûr dans la plupart des codes cela n'a probablement pas d'importance)
la source
char time_buf[] = "00:00";
où vous allez modifier un tampon? Unchar *
initialisé sur un littéral de chaîne est défini sur l'adresse du premier octet. Essayer de le modifier entraîne donc un comportement indéfini car la méthode de stockage du littéral de chaîne est inconnue (implémentation définie), tandis que modifier les octets de achar[]
est parfaitement légal car l'initialisation copie les octets dans un espace inscriptible alloué sur la pile. Dire que c'est «moins efficace» ou «mauvaise pratique» sans préciser les nuances dechar* vs char[]
est trompeur.Il ne faut jamais trop de temps, mais évitez d’initialiser char [] en chaîne, car "chaîne" est const char *, et vous l’assignez à char *. Donc, si vous passez ce caractère [] à la méthode qui modifie les données, vous pouvez avoir un comportement intéressant.
Comme recommandé, j'ai mélangé un peu char [] avec char *, ce n'est pas bien, car ils diffèrent un peu.
Il n'y a rien de mal à assigner des données à un tableau de caractères, mais comme l'intention d'utiliser ce tableau est de l'utiliser comme 'chaîne' (char *), il est facile d'oublier que vous ne devez pas modifier ce tableau.
la source
const
moins que vous ne le définissiez de cette façon. (Et les littéraux de chaîne en C ne le sont pasconst
, bien que toute tentative de modification d'un littéral de chaîne ait un comportement indéfini.) Achar *s = "literal";
le genre de comportement dont vous parlez; c'est mieux écrit commeconst char *s = "literal";
const
àint n = 42;
, car42
est une constante.c
est modifiable. C’est une garantie aussi forte que celle qui a été1 + 1
évaluée2
. Si le programme auquel j'ai lié ci - dessus fait autre chose que l'impressionEFGH
, il indique une implémentation en C non conforme.