Que sont les points de séquence en C++ et quel est le rapport avec le comportement indéfini ?

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

L'arme secrète du débogueur : ce que les programmeurs C++ négligent

\n\n

Avez-vous déjà écrit quelque chose comme i = i++ + ++i; et obtenu des résultats différents selon les compilateurs ? Ou attrapé un bug étrange qui ne se reproduisait que sur le serveur, mais pas sur votre ordinateur portable ? Faites connaissance avec : undefined behavior (comportement indéfini) — l'un des ennemis les plus sournois du développeur C++. Et au centre de ce chaos se trouvent les points de séquence (sequence points).

\n\n

Dans cet article, nous allons voir ce que sont les sequence points, comment ils sont liés à l'ordre d'évaluation en C++, et — surtout — comment éviter de marcher sur ce râteau. Le contenu est basé sur une question culte de Stack Overflow qui a recueilli des milliers de vues. C'est parti !

\n\n

Que sont les points de séquence (sequence points) ?

\n\n

Imaginez que vous êtes le chef d'orchestre. Chaque musicien (instruction du processeur) doit entrer à un moment précis. Un point de séquence est un moment où tous les « effets secondaires » précédents (modifications de variables, appels de fonctions) sont déjà garantis comme terminés, et les suivants n'ont pas encore commencé.

\n\n

En C++, la norme 98/03 utilisait le terme sequence point. En C++11, il a été remplacé par les relations plus strictes « sequenced before / sequenced after », mais le principe reste le même : entre deux points de séquence, le compilateur a parfaitement le droit de réorganiser les opérations, si cela ne change pas le résultat (selon la règle « as-if »).

\n\n

En termes simples : si vous essayez de modifier la même variable deux fois entre deux points de séquence — vous obtenez un undefined behavior. Le compilateur peut faire n'importe quoi : d'un résultat correct au formatage de votre disque dur (blague, mais presque vrai).

\n\n

Exemples classiques de points de séquence

\n
    \n
  • Le point-virgule ; — fin d'une expression
  • \n
  • La virgule , en tant qu'opérateur (mais pas dans les arguments d'une fonction !)
  • \n
  • La fin de chaque expression dans for (après l'initialisation, la vérification et l'incrémentation)
  • \n
  • Les opérateurs logiques && et ||
  • \n
  • L'opérateur ternaire ?:
  • \n
  • L'appel de fonction (avant que le corps de la fonction ne commence à s'exécuter)
  • \n
\n\n

Comment les points de séquence engendrent-ils un comportement indéfini ?

\n\n

Voici un exemple classique que tout développeur C++ devrait connaître :

\n\n
int i = 0;\ni = i++ + ++i;  // UB ! Comportement indéfini
\n\n

Pourquoi est-ce UB ? Parce qu'entre deux points de séquence (dans ce cas, entre le début et la fin de l'expression), la variable i est modifiée deux fois :

\n
    \n
  1. L'incrémentation postfixée i++
  2. \n
  3. L'incrémentation préfixée ++i
  4. \n
  5. L'affectation i = ...
  6. \n
\n\n

Le compilateur ne sait pas dans quel ordre effectuer ces opérations. Le résultat ? Un compilateur donnera 2, un autre 3, un troisième 0. Et en mode optimisation, il pourrait même supprimer ce code.

\n\n

Un autre cas piège : les arguments d'une fonction

\n\n
int foo(int a, int b) { return a + b; }\n\nint x = 5;\nint result = foo(++x, ++x);  // UB !
\n\n

Ici, la virgule entre les arguments est pas un opérateur virgule, mais un séparateur. Il n'y a pas de point de séquence entre l'évaluation des arguments ++x ! Les deux incrémentations sont effectuées « en parallèle » (du point de vue de la norme), et nous obtenons à nouveau un undefined behavior.

\n\n

Exemples pratiques : à quoi ressemble UB dans le code réel

\n\n

Exemple 1 : Bug non évident avec l'indexation

\n\n
int arr[5] = {0, 10, 20, 30, 40};\nint idx = 1;\narr[idx] = idx++;  // UB !
\n\n

À première vue, tout est simple : on veut écrire 1 dans arr[1], puis incrémenter idx. Mais la norme ne garantit pas l'ordre d'évaluation de l'index idx et de la partie droite idx++. Le résultat ? Cela peut s'écrire dans arr[1], ou dans arr[2].

\n\n

Exemple 2 : Compl

Blogs

Book Recommendations