int main(int argc, char ** argv)
{
int i = 0;
i = i++ + ++i;
printf("%d\n", i); // 3
i = 1;
i = (i++);
printf("%d\n", i); // 2 Should be 1, no ?
volatile int u = 0;
u = u++ + ++u;
printf("%d\n", u); // 1
u = 1;
u = (u++);
printf("%d\n", u); // 2 Should also be one, no ?
register int v = 0;
v = v++ + ++v;
printf("%d\n", v); // 3 (Should be the same as u ?)
}
Réponses
Trop de publicités?Pourquoi ces "problèmes"? La langue dit clairement que certains éléments conduisent à un comportement indéfini. Il n'y a pas de problème, il n'est pas "doit". Si l'indéfini des changements de comportement lorsque l'une des variables concernées est déclarée volatile
, qui n'a pas de prouver ou de changer quoi que ce soit. Il est indéfini; on ne peut pas raisonner sur le comportement.
Votre plus intéressant-loooking exemple, l'une avec
u = (u++);
est un exemple de comportement indéfini (voir Wikipedia inscription sur la séquence de points).
Lisez cette Question à partir de la C FAQ.
R: d'Un point de séquence est un point dans le temps où la poussière est retombée et tous les effets indésirables qui ont été observés jusqu'à présent sont garantis pour être complet. La séquence des points énumérés dans la norme sont les suivants:
- à la fin de l'évaluation d'une pleine expression (une expression est une expression de la déclaration, ou toute autre expression qui n'est pas une sous-expression à l'intérieur de toute expression plus grande);
- à l'
||
,&&
,?:
, et par des virgules des opérateurs; et - lors d'un appel de fonction (après l'évaluation de tous les arguments, et juste avant l'appel).
La Norme précise que
Entre le précédent et suivant de la séquence point un objet est stocké valeur modifiée au plus une fois par le l'évaluation d'une expression. En outre, l'état de la valeur doit être accessible uniquement à déterminer la valeur pour être stocké.
Ces deux plutôt opaque peines dire plusieurs choses. Tout d'abord, ils parlent des opérations délimitée par le "précédent et suivant de la séquence de points"; ces opérations correspondent généralement aux plein d'expressions. (Dans une expression instruction, le "prochain point de séquence" est généralement un point virgule, et la "séquence précédente, le point" est à la fin de l'instruction précédente. Une expression peut également contenir intermédiaire de la séquence de points, comme indiqué ci-dessus.)
La première phrase de règles à la fois les exemples
i++ * i++
et
i = i++
à partir de questions 3.2 et 3.3--dans les deux cas, je a sa valeur modifiée à deux reprises dans l'expression, c'est à dire entre la séquence de points. (Si nous devions écrire une expression similaire qui a eu une séquence interne, comme
i++ && i++
il serait bien défini, si douteuse utile).
La deuxième phrase peut être assez difficile à comprendre. Il s'avère qu'il n'autorise pas un code comme
a[i] = i++
à partir de la question 3.1. (En fait, les autres expressions que nous avons discuté sont en violation de la deuxième phrase.) Pour comprendre pourquoi, nous allons tout d'abord examiner de plus près ce que la Norme est d'essayer d'autoriser et interdire.
Clairement, les expressions comme
a = b
et
c = d + e
qui lire certaines valeurs et de les utiliser pour écrire d'autres, sont bien définis et juridique. Clairement, [note de bas de page] des expressions comme
i = i++
modifier la même valeur à deux reprises sont des abominations qui n'a pas besoin d'être autorisé (ou en tout cas, n'a pas besoin d'être bien définie, c'est à dire que nous n'avons pas trouver le moyen de dire ce qu'ils font, et les compilateurs n'ont pas à supporter). Des Expressions comme celles-ci sont interdites par la première phrase.
Il est aussi clair [note de bas de page], que nous voulons interdire les expressions comme
a[i] = i++
qui modifient i et utiliser le long du chemin, mais pas interdire les expressions comme
i = i + 1
ce qui les utiliser et les modifier je mais seulement de le modifier plus tard, lorsqu'il est raisonnablement facile à assurer que la version finale de stocker de la valeur finale (en i
, dans ce cas) n'interfère pas avec les précédentes, accède.
Et c'est ce que la deuxième phrase dit: si un objet est écrit à l'intérieur d'une expression pleine de tout et de tous les accès à l'intérieur de la même expression doit être directement impliqués dans le calcul de la valeur à écrire. Cette règle limite l'expression juridique de celles dans lesquelles l'accès manifestement précéder la modification. Par exemple, la veille ancienne i = i + 1
est autorisé, parce que l'accès de l' i
est utilisé pour déterminer l' i
's la valeur finale. L'exemple
a[i] = i++
est rejetée parce que l'un des accès de i ( a[i]
) n'a rien à voir avec la valeur qui est stockée dans i
(ce qui arrive de plus, en i++
), et donc il n'y a pas de bonne façon de définir--soit pour notre compréhension ou le compilateur--si l'accès doit avoir lieu avant ou après la valeur incrémentée est stocké. Car il n'y a pas de bonne façon de le définir, la Norme déclare qu'il n'est pas défini, et que les programmes portables doivent tout simplement pas utiliser de telles constructions.
Je pense que les parties pertinentes de la norme C99 sont 6,5 Expressions, §2
Entre le précédent et suivant de la séquence de point d'un objet doit avoir sa valeur stockée modifié plus d'une fois par l'évaluation d'une expression. En outre, l'état de la valeur doit être en lecture seule pour déterminer la valeur à stocker.
et 6.5.16 opérateurs d'Affectation, §4:
L'ordre d'évaluation des opérandes est pas spécifié. Si une tentative est faite pour modifier le résultat de l'opérateur d'affectation ou d'y accéder après le prochain point de séquence, l' le comportement est indéfini.
Juste de compiler et de démonter votre ligne de code, si vous êtes si incliné de savoir exactement comment il est que vous obtenez ce que vous obtenez.
C'est ce que j'ai sur ma machine, avec ce que je pense est d'aller sur:
$ cat evil.c
void evil(){
int i = 0;
i+= i++ + ++i;
}
$ gcc evil.c -c -o evil.bin
$ gdb evil.bin
(gdb) disassemble evil
Dump of assembler code for function evil:
0x00000000 <+0>: push %ebp
0x00000001 <+1>: mov %esp,%ebp
0x00000003 <+3>: sub $0x10,%esp
0x00000006 <+6>: movl $0x0,-0x4(%ebp) // i = 0 i = 0
0x0000000d <+13>: addl $0x1,-0x4(%ebp) // i++ i = 1
0x00000011 <+17>: mov -0x4(%ebp),%eax // j = i i = 1 j = 1
0x00000014 <+20>: add %eax,%eax // j += j i = 1 j = 2
0x00000016 <+22>: add %eax,-0x4(%ebp) // i += j i = 3
0x00000019 <+25>: addl $0x1,-0x4(%ebp) // i++ i = 4
0x0000001d <+29>: leave
0x0000001e <+30>: ret
End of assembler dump.
(Je... suppose que le 0x00000014 instruction a été une sorte d'optimisation du compilateur?)
Le comportement ne peut pas vraiment être expliqué, car il évoque à la fois un comportement non spécifié et un comportement indéfini, donc nous ne pouvons pas faire quelque général des prédictions à propos de ce code, bien que si vous lisez Olve Maudal du travail telles que la Profondeur C et non précisées et Undefined vous pouvez parfois faire de bonnes estimations dans des cas très spécifiques avec un compilateur spécifique et de l'environnement, mais s'il vous plaît ne pas le faire n'importe où près de la production.
Afin de passer à un comportement non spécifié, dans le projet de standard c99 section6.5
de l'alinéa 3 dit: (c'est moi qui souligne):
Le groupement d'opérateurs et d'opérandes est indiqué par la syntaxe.74), Sauf comme indiqué plus tard (pour la fonction appel (), &&, ||, ?:, et la virgule opérateurs), l'ordre d'évaluation des sous-expressions et l'ordre dans lequel les effets secondaires sont à la fois non spécifié.
Ainsi, lorsque nous avons une ligne comme ceci:
i = i++ + ++i;
nous ne savons pas si i++
ou ++i
sera évalué en premier. C'est surtout pour donner le compilateur de meilleures options pour l'optimisation.
Nous avons aussi un comportement non défini ici comme le programme est en train de modifier des variables(i
, u
, etc..) plus d'une fois entre la séquence de points. De projet de norme section 6.5
le paragraphe 2(c'est moi qui souligne):
Entre le précédent et suivant de la séquence de point d'un objet doit avoir sa valeur stockée modifié plus d'une fois par l'évaluation d'une expression. En outre, l'état de la valeur doit être en lecture seule pour déterminer la valeur à stocker.
il cite les exemples de code suivants comme étant indéfini:
i = ++i + 1;
a[i++] = i;
Dans tous ces exemples, le code est de tenter de modifier un objet plus d'une fois dans le même point de séquence, qui prendra fin avec l' ;
dans chacun de ces cas:
i = i++ + ++i;
^ ^ ^
i = (i++);
^ ^
u = u++ + ++u;
^ ^ ^
u = (u++);
^ ^
v = v++ + ++v;
^ ^ ^
Un comportement non spécifié est défini dans le projet de standard c99 dans la section 3.4.4
comme:
l'utilisation d'une valeur indéterminée, ou tout autre comportement lorsque la présente Norme Internationale fournit des deux ou plus de possibilités et n'impose pas d'exigences supplémentaires qui est choisie dans toute exemple
et un comportement indéfini est défini dans la section 3.4.3
comme:
problème, lors de l'utilisation d'un non-compatibles ou erronées programme de construction ou de données erronées, pour lequel la présente Norme Internationale n'impose pas d'exigences
et note que:
Possible un comportement indéfini plages d'ignorer la situation complètement avec des résultats imprévisibles, à se comporter lors de la traduction ou de l'exécution du programme dans documenté de façon caractéristique de l'environnement (avec ou sans émission d'un message de diagnostic), à la terminaison d'une traduction ou d'exécution (avec l'émission d'un message de diagnostic).