web-dev-qa-db-fra.com

Ordre d'évaluation des paramètres avant une fonction appelant en C

Peut-on supposer un ordre d'évaluation des paramètres de la fonction lors de son appel en C? Selon le programme suivant, il semble qu'il n'y ait pas d'ordre particulier lorsque je l'ai exécuté.

#include <stdio.h>

int main()
{
   int a[] = {1, 2, 3};
   int * pa; 

   pa = &a[0];
   printf("a[0] = %d\ta[1] = %d\ta[2] = %d\n",*(pa), *(pa++),*(++pa));
   /* Result: a[0] = 3  a[1] = 2    a[2] = 2 */

   pa = &a[0];
   printf("a[0] = %d\ta[1] = %d\ta[2] = %d\n",*(pa++),*(pa),*(++pa));
   /* Result: a[0] = 2  a[1] = 2     a[2] = 2 */

   pa = &a[0];
   printf("a[0] = %d\ta[1] = %d\ta[2] = %d\n",*(pa++),*(++pa), *(pa));
   /* a[0] = 2  a[1] = 2 a[2] = 1 */

}
67
corto

Non, les paramètres de fonction ne sont pas évalués dans un ordre défini en C.

Voir les réponses de Martin York à Quels sont tous les comportements indéfinis courants que le programmeur c ++ devrait connaître? .

63
Grant Wagner

L'ordre d'évaluation des arguments de fonction n'est pas spécifié, à partir de C99 §6.5.2.2p10:

L'ordre d'évaluation de l'indicateur de fonction, des arguments réels et des sous-expressions dans les arguments réels n'est pas spécifié, mais il existe un point de séquence avant l'appel réel.

Un libellé similaire existe dans C89.

De plus, vous modifiez pa plusieurs fois sans intervenir des points de séquence qui invoque un comportement indéfini (l'opérateur de virgule introduit un point de séquence mais pas les virgules délimitant les arguments de fonction). Si vous affichez les avertissements sur votre compilateur, il devrait vous en avertir:

$ gcc -Wall -W -ansi -pedantic test.c -o test
test.c: In function ‘main’:
test.c:9: warning: operation on ‘pa’ may be undefined
test.c:9: warning: operation on ‘pa’ may be undefined
test.c:13: warning: operation on ‘pa’ may be undefined
test.c:13: warning: operation on ‘pa’ may be undefined
test.c:17: warning: operation on ‘pa’ may be undefined
test.c:17: warning: operation on ‘pa’ may be undefined
test.c:20: warning: control reaches end of non-void function
19
Robert Gamble

Juste pour ajouter quelques expériences.
Le code suivant:

int i=1;
printf("%d %d %d\n", i++, i++, i);

résulte en

2 1 3 - en utilisant g ++ 4.2.1 sur Linux.i686
1 2 3 - en utilisant SunStudio C++ 5.9 sur Linux.i686
2 1 3 - en utilisant g ++ 4.2.1 sur SunOS.x86pc
1 2 3 - en utilisant SunStudio C++ 5.9 sur SunOS.x86pc
1 2 3 - en utilisant g ++ 4.2.1 sur SunOS.Sun4u
1 2 3 - en utilisant SunStudio C++ 5.9 sur SunOS.Sun4u

14
Pitje Puck

Peut-on supposer un ordre d'évaluation des paramètres de la fonction lors de son appel en C?

Non, cela ne peut pas être supposé si, c'est comportement non spécifié , le projet de norme C99 dans la section 6.5 Paragraphe 3 Dit:

Le regroupement d'opérateurs et d'opérandes est indiqué par la syntaxe.74) Sauf comme spécifié plus loin (pour les opérateurs de fonction-call (), &&, ||,?: Et virgule), le l'ordre d'évaluation des sous-expressions et l'ordre dans lequel les effets secondaires se produisent ne sont pas spécifiés.

Il indique également, sauf comme spécifié plus tard et spécifiquement les sites function-call (), nous voyons donc que plus tard le projet de norme dans la section 6.5.2.2 Appels de fonction paragraphe 10 Dit:

L'ordre d'évaluation de l'indicateur de fonction, des arguments réels et des sous-expressions dans les arguments réels n'est pas spécifié , mais il existe un point de séquence avant l'appel réel .

Ce programme présente également comportement indéfini puisque vous modifiez pa plus d'une fois entre points de séquence . Du projet de section standard 6.5 Paragraphe 2:

Entre le point de séquence précédent et suivant, un objet doit voir sa valeur stockée modifiée au plus une fois par l'évaluation d'une expression. En outre, la valeur antérieure doit être lue uniquement pour déterminer la valeur à stocker.

il cite les exemples de code suivants comme non définis:

i = ++i + 1;
a[i++] = i; 

Il est important de noter que même si l'opérateur virgule introduit des points de séquence, la virgule utilisée dans les appels de fonction est un séparateur et non le comma operator. Si nous regardons la section 6.5.17 Opérateur virgule le paragraphe 2 Dit:

L'opérande gauche d'un opérateur virgule est évalué comme une expression vide; il y a un point de séquence après son évaluation.

mais le paragraphe 3 dit:

EXEMPLE Comme indiqué par la syntaxe, l'opérateur virgule (comme décrit dans ce paragraphe) ne peut pas apparaître dans des contextes où une virgule est utilisée pour séparer les éléments d'une liste (comme les arguments des fonctions ou les listes de initialiseurs ).

Sans le savoir, l'activation des avertissements avec gcc en utilisant au moins -Wall Aurait fourni un message similaire à:

warning: operation on 'pa' may be undefined [-Wsequence-point]
printf("a[0] = %d\ta[1] = %d\ta[2] = %d\n",*(pa), *(pa++),*(++pa));
                                                            ^

et par défaut clang avertira avec un message similaire à:

warning: unsequenced modification and access to 'pa' [-Wunsequenced]
printf("a[0] = %d\ta[1] = %d\ta[2] = %d\n",*(pa), *(pa++),*(++pa));
                                            ~         ^

En général, il est important de comprendre comment utiliser vos outils de la manière la plus efficace, il est important de connaître les indicateurs disponibles pour les avertissements, pour gcc vous pouvez trouver ces informations ici . Certains drapeaux qui sont utiles et vous feront économiser beaucoup de problèmes à long terme et qui sont communs aux deux gcc et clang sont -Wextra -Wconversion -pedantic. Pour clangcomprendre -fsanitize peut être très utile. Par exemple, -fsanitize=undefined Détectera de nombreuses instances de comportement indéfini lors de l'exécution.

9
Shafik Yaghmour

Comme d'autres l'ont déjà dit, l'ordre dans lequel les arguments de fonction sont évalués n'est pas spécifié, et il n'y a pas de point de séquence entre les évaluer. Parce que vous modifiez pa par la suite en passant chaque argument, vous modifiez et lisez pa deux fois entre deux points de séquence. C'est en fait un comportement indéfini. J'ai trouvé une très belle explication dans le manuel GCC, qui je pense pourrait être utile:

Les normes C et C++ définissent l'ordre dans lequel les expressions d'un programme C/C++ sont évaluées en termes de points de séquence, qui représentent un ordre partiel entre l'exécution de parties du programme: celles exécutées avant le point de séquence et celles exécutées après il. Celles-ci se produisent après l'évaluation d'une expression complète (qui ne fait pas partie d'une expression plus grande), après l'évaluation du premier opérande d'un &&, ||,? : ou, (virgule), avant qu'une fonction soit appelée (mais après l'évaluation de ses arguments et l'expression dénotant la fonction appelée), et à certains autres endroits. En dehors de ce qui est exprimé par les règles de point de séquence, l'ordre d'évaluation des sous-expressions d'une expression n'est pas spécifié. Toutes ces règles ne décrivent qu'un ordre partiel plutôt qu'un ordre total, car, par exemple, si deux fonctions sont appelées dans une même expression sans point de séquence entre elles, l'ordre dans lequel les fonctions sont appelées n'est pas spécifié. Cependant, le comité des normes a décidé que les appels de fonction ne se chevauchent pas.

Il n'est pas spécifié quand entre les points de séquence les modifications des valeurs des objets prennent effet. Les programmes dont le comportement en dépend ont un comportement indéfini; les normes C et C++ spécifient que "Entre le point de séquence précédent et suivant, un objet doit voir sa valeur stockée modifiée au plus une fois par l'évaluation d'une expression. En outre, la valeur antérieure ne doit être lue que pour déterminer la valeur à stocker. ". Si un programme enfreint ces règles, les résultats d'une implémentation particulière sont totalement imprévisibles.

Des exemples de code avec un comportement non défini sont a = a ++ ;, a [n] = b [n ++] et a [i ++] = i ;. Certains cas plus compliqués ne sont pas diagnostiqués par cette option, et cela peut donner un faux résultat occasionnel, mais en général, il a été trouvé assez efficace pour détecter ce type de problème dans les programmes.

La norme est formulée de manière confuse, il y a donc un débat sur la signification précise des règles de point de séquence dans les cas subtils. Des liens vers les discussions sur le problème, y compris les définitions formelles proposées, peuvent être trouvés sur la page des lectures du CCG, à http://gcc.gnu.org/readings.html .

5

Modifier une variable plusieurs fois dans une expression est un comportement non défini. Vous pouvez donc obtenir des résultats différents sur différents compilateurs. Évitez donc de modifier une variable plus d'une fois.

1
Pankaj Mahato