ProgrammationDéveloppeur C senior, Ingénieur système

Comment les effets secondaires sont-ils gérés lors du calcul des arguments d'une fonction en C ? Quel est l'ordre de calcul des arguments, et quelles surprises peuvent survenir ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse

En C, l'ordre de calcul des arguments d'une fonction n'est pas défini par la norme (jusqu'à C99 inclus). Les arguments peuvent être calculés de gauche à droite, de droite à gauche ou dans tout autre ordre (à la discrétion du compilateur ou de l'architecture).

  • Toutes les expressions/effets secondaires dans les arguments doivent être complétés avant l'appel de la fonction, mais il n'y a aucune garantie qu'ils s'exécutent dans un ordre spécifique.
  • Cela signifie que l'utilisation de variables avec modification de valeur dans plusieurs arguments est une source potentielle de comportement indéfini (undefined behavior) ou simplement de différences entre architectures.

Exemple

void fn(int a, int b) { /* ... */ } int x = 1; fn(x++, x++); // l'ordre de calcul de x++ et x++ n'est pas défini !

Question piège

"Dans quel ordre les arguments d'une fonction sont-ils calculés en C et peut-on s'y fier lors de l'écriture du code ?"

Une erreur fréquente est de penser que les arguments sont calculés de gauche à droite (par analogie avec les expressions). En pratique, chaque appel de fonction est compilé à la discrétion du compilateur (et de la plateforme).

void foo(int a, int b, int c); int x = 1; foo(x++, x++, x++); // le résultat dépend de l'ordre de calcul des arguments

Véritable réponse : on ne peut pas s'y fier — le comportement n'est pas défini !

Exemples d'erreurs réelles dues à l'ignorance des subtilités du sujet


Histoire

Dans un produit multiplateforme, un programmeur a écrit push(stack, stack->size++, data);. Sur la plupart des plateformes, tout fonctionnait, mais sur une, la taille de la pile augmentait avant la transmission des données, tandis que sur une autre, après. Les données "se perdaient" ou étaient adressées incorrectement, l'erreur se manifestait rarement et était très difficile à déboguer.


Histoire

Dans une bibliothèque de protocole réseau, la fonction de journalisation était appelée avec des expressions-arguments incrémentant des compteurs statistiques. Les rapports statistiques étaient générés de manière incorrecte : pour différents clients, les indices des compteurs différaient, car ils n'étaient pas exécutés dans l'ordre prévu.


Histoire

Dans une interface de gestion de matériel, la fonction d'initialisation passait des pointeurs et des décalages avec incrément à l'intérieur des arguments (du type init(ptr++, cnt++);). Sur certains processeurs, le matériel s'initialisait correctement, tandis que sur d'autres, un échec survenait, la cause étant longtemps recherchée dans le matériel, alors que le problème se situait dans un code C incorrect.