En langage C, si initialisez un tableau comme celui-ci:
int a[5] = {1,2};
alors tous les éléments du tableau qui ne sont pas initialisés explicitement seront initialisés implicitement avec des zéros.
Mais, si j'initialise un tableau comme celui-ci:
int a[5]={a[2]=1};
printf("%d %d %d %d %d\n", a[0], a[1],a[2], a[3], a[4]);
production:
1 0 1 0 0
Je ne comprends pas, pourquoi a[0]
imprime- t-il à la 1
place 0
? Est-ce un comportement non défini?
Remarque: Cette question a été posée lors d'une entrevue.
a[2]=1
évaluée à1
.a[2] = 1
est apparemment1
, mais je ne suis pas sûr que vous soyez autorisé à prendre le résultat d'une expression d'initialisation désignée comme valeur du premier élément. Le fait que vous ayez ajouté l'étiquette d'avocat signifie que je pense que nous avons besoin d'une réponse citant la norme.Réponses:
TL; DR: Je ne pense pas que le comportement de
int a[5]={a[2]=1};
soit bien défini, du moins en C99.Ce qui est amusant, c'est que le seul élément qui a du sens pour moi est la partie sur laquelle vous demandez:
a[0]
est définie sur1
parce que l'opérateur d'affectation renvoie la valeur qui a été affectée. C'est tout le reste qui n'est pas clair.Si le code avait été
int a[5] = { [2] = 1 }
, tout aurait été facile: c'est un paramètre d'initialisation désignéa[2]
à1
et tout le reste à0
. Mais avec{ a[2] = 1 }
nous avons un initialiseur non désigné contenant une expression d'affectation, et nous tombons dans un terrier de lapin.Voici ce que j'ai trouvé jusqu'à présent:
a
doit être une variable locale.a[2] = 1
n'est pas une expression constante, donca
doit avoir un stockage automatique.a
est dans la portée de sa propre initialisation.Le déclarateur est
a[5]
, donc les variables sont dans la portée de leur propre initialisation.a
est vivant dans sa propre initialisation.Il y a un point de séquence après
a[2]=1
.Notez que, par exemple, dans
int foo[] = { 1, 2, 3 }
la{ 1, 2, 3 }
partie se trouve une liste d'initialiseurs entre accolades, chacun d'entre eux étant suivi d'un point de séquence.L'initialisation est effectuée dans l'ordre de la liste d'initialisation.
Cependant, les expressions d'initialisation ne sont pas nécessairement évaluées dans l'ordre.
Cependant, cela laisse encore quelques questions sans réponse:
Les points de séquence sont-ils même pertinents? La règle de base est:
a[2] = 1
est une expression, mais l'initialisation ne l'est pas.Ceci est légèrement contredit par l'annexe J:
L'annexe J indique que toute modification compte, pas seulement les modifications par expressions. Mais étant donné que les annexes ne sont pas normatives, nous pouvons probablement l'ignorer.
Comment les initialisations de sous-objets sont-elles séquencées par rapport aux expressions d'initialisation? Tous les initialiseurs sont-ils évalués en premier (dans un certain ordre), puis les sous-objets sont initialisés avec les résultats (dans l'ordre de la liste des initialiseurs)? Ou peuvent-ils être entrelacés?
Je pense qu'il
int a[5] = { a[2] = 1 }
est exécuté comme suit:a
est alloué lorsque son bloc conteneur est entré. Le contenu est indéterminé à ce stade.a[2] = 1
), suivi d'un point de séquence. Ce magasins1
dansa[2]
et retours1
.1
est utilisé pour initialisera[0]
(le premier initialiseur initialise le premier sous-objet).Mais ici les choses deviennent floues parce que les autres éléments (
a[1]
,a[2]
,a[3]
,a[4]
) sont censés être initialisé à0
, mais il ne sait pas quand: - t - il arriver avanta[2] = 1
est évaluée? Si tel est le cas, est-a[2] = 1
ce que «gagner» et écrasera[2]
, mais cette affectation aurait-elle un comportement indéfini car il n'y a pas de point de séquence entre l'initialisation zéro et l'expression d'affectation? Les points de séquence sont-ils même pertinents (voir ci-dessus)? Ou est-ce que zéro initialisation se produit après que tous les initialiseurs sont évalués? Si c'est le cas,a[2]
devrait finir par l'être0
.Parce que la norme C ne définit pas clairement ce qui se passe ici, je pense que le comportement n'est pas défini (par omission).
la source
a[0]
sous - objet avant d'évaluer son initialiseur, et l'évaluation de tout initialiseur inclut un point de séquence (car c'est une "expression complète"). Par conséquent, je pense que la modification du sous-objet que nous initialisons est une bonne chose.a[2]=1
Initialise vraisemblablement ena[2]
premier et le résultat de l'expression est utilisé pour l'initialisationa[0]
.À partir de N2176 (projet C17):
Il semblerait donc que la sortie
1 0 0 0 0
aurait également été possible.Conclusion: n'écrivez pas d'initialiseurs qui modifient la variable initialisée à la volée.
la source
{...}
expression qui s'initialisea[2]
à0
, et laa[2]=1
sous-expression qui s'initialisea[2]
à1
.{...}
est une liste d'initialiseurs accolés. Ce n'est pas une expression.Je pense que la norme C11 couvre ce comportement et dit que le résultat n'est pas spécifié , et je ne pense pas que C18 ait apporté des changements pertinents dans ce domaine.
Le langage standard n'est pas facile à analyser. La section pertinente de la norme est le §6.7.9 Initialisation . La syntaxe est documentée comme suit:
Notez que l'un des termes est expression d'affectation , et comme il
a[2] = 1
s'agit indubitablement d'une expression d'affectation, il est autorisé à l'intérieur des initialiseurs pour les tableaux avec une durée non statique:L'un des paragraphes clés est:
Et un autre paragraphe clé est:
Je suis assez sûr que le paragraphe §23 indique que la notation dans la question:
conduit à un comportement non spécifié. L'affectation à
a[2]
est un effet secondaire et l'ordre d'évaluation des expressions est séquencé de manière indéterminée les uns par rapport aux autres. Par conséquent, je ne pense pas qu'il existe un moyen de faire appel à la norme et d'affirmer qu'un compilateur particulier gère cela correctement ou incorrectement.la source
Ma compréhension
a[2]=1
renvoie la valeur 1 donc le code devientint a[5]={1}
attribuer une valeur pour a [0] = 1Par conséquent, il imprime 1 pour un [0]
Par exemple
la source
J'essaie de donner une réponse courte et simple au puzzle:
int a[5] = { a[2] = 1 };
a[2] = 1
est réglé. Cela signifie que le tableau dit:0 0 1 0 0
{ }
entre crochets, qui sont utilisés pour initialiser le tableau dans l'ordre, il prend la première valeur (qui est1
) et la définit sura[0]
. C'est comme siint a[5] = { a[2] };
resterait là où nous en sommes déjàa[2] = 1
. Le tableau résultant est maintenant:1 0 1 0 0
Un autre exemple:
int a[6] = { a[3] = 1, a[4] = 2, a[5] = 3 };
- Même si l'ordre est quelque peu arbitraire, en supposant qu'il va de gauche à droite, il irait dans ces 6 étapes:la source
A = B = C = 5
n'est pas une déclaration (ou une initialisation). C'est une expression normale qui analyseA = (B = (C = 5))
parce que l'=
opérateur est associatif juste. Cela n'aide pas vraiment à expliquer le fonctionnement de l'initialisation. Le tableau commence réellement à exister lorsque le bloc dans lequel il est défini est entré, ce qui peut être long avant que la définition réelle ne soit exécutée.a[2] = 1
expression d'initialisation ne soit appliqué? Le résultat observé est comme si c'était le cas, mais la norme ne semble pas préciser que cela devrait être le cas. C'est le centre de la controverse, et cette réponse l'ignore complètement.L'affectation
a[2]= 1
est une expression qui a la valeur1
, et vous avez essentiellement écritint a[5]= { 1 };
(avec l'effet secondaire quia[2]
est également attribué1
).la source
Je crois que
int a[5]={ a[2]=1 };
c'est un bon exemple pour un programmeur qui se tire dans son propre pied.Je pourrais être tenté de penser que ce que vous vouliez dire était
int a[5]={ [2]=1 };
un élément de réglage d'initialisation désigné C99 2 à 1 et le reste à zéro.Dans le cas rare où vous pensiez vraiment vraiment
int a[5]={ 1 }; a[2]=1;
, alors ce serait une façon amusante de l'écrire. Quoi qu'il en soit, c'est à cela que se résume votre code, même si certains ont souligné que ce n'est pas bien défini lorsque l'écriturea[2]
est réellement exécutée. Le piège ici est qu'ila[2]=1
ne s'agit pas d'un initialiseur désigné mais d'une simple affectation qui a elle-même la valeur 1.la source