Comportement indéfini: lors de la tentative d’access au résultat de l’appel de fonction

Ce qui suit comstack et affiche “chaîne” en tant que sortie.

#include  struct S { int x; char c[7]; }; struct S bar() { struct S s = {42, "ssortingng"}; return s; } int main() { printf("%s", bar().c); } 

Apparemment, cela semble invoquer un comportement indéfini selon

C99 6.5.2.2/5 Si l’on tente de modifier le résultat d’un appel de fonction ou d’y accéder après le sharepoint séquence suivant, le comportement n’est pas défini.

Je ne comprends pas où il est question de “prochain sharepoint séquence”. Que se passe t-il ici?

Vous avez rencontré un coin subtil de la langue.

Une expression de type tableau est, dans la plupart des contextes, convertie implicitement en un pointeur sur le premier élément de l’object tableau. Les exceptions, dont aucune ne s’applique ici, sont les suivantes:

  • Lorsque l’expression du tableau est l’opérande d’un opérateur unaire & (ce qui donne l’adresse du tableau entier);
  • Lorsqu’il s’agit de l’opérande d’un sizeof unaire ou (à partir de C11), l’ opérateur _Alignof ( sizeof arr donne la taille du tableau, pas la taille d’un pointeur); et
  • Lorsqu’il s’agit d’un littéral de chaîne dans un initialiseur utilisé pour initialiser un object de tableau ( char str[6] = "hello"; ne convertit pas "hello" en char* .)

(Le brouillon N1570 ajoute à tort _Alignof à la liste des exceptions. En fait, pour des raisons _Alignof , _Alignof ne peut être appliqué qu’à un nom de type, pas à une expression.)

Notez qu’il existe une hypothèse implicite: que l’expression de tableau se réfère à un object de tableau en premier lieu. Dans la plupart des cas, c’est le cas (le cas le plus simple est celui où l’expression du tableau est le nom d’un object du tableau déclaré) – mais dans ce cas, il n’y a pas d’object du tableau .

Si une fonction retourne une structure, le résultat de la structure est renvoyé par valeur . Dans ce cas, la structure contient un tableau, ce qui nous donne une valeur de tableau sans object de tableau correspondant, du moins logiquement. Donc, la bar().c expression du tableau bar().c se décompose en un pointeur sur le premier élément de … euh, euh, … un object tableau qui n’existe pas.

La norme ISO C 2011 résout ce problème en introduisant la ” durée de vie temporaire “, qui s’applique uniquement à “Une expression de type non-valeur avec une structure ou un type d’union, où la structure ou l’union contient un membre de type tableau” ( N1570 6.2.4p8). Un tel object ne peut pas être modifié et sa durée de vie se termine à la fin de l’expression complète ou du déclarant complet.

Ainsi, à partir de C2011, le comportement de votre programme est bien défini. L’appel printf obtient un pointeur sur le premier élément d’un tableau faisant partie d’un object struct ayant une durée de vie temporaire. cet object continue d’exister jusqu’à la fin de l’appel printf .

Mais à partir de C99, le comportement n’est pas défini – pas nécessairement à cause de la clause que vous citez (pour autant que je sache, il n’y a pas de sharepoint séquence intermédiaire), mais parce que C99 ne définit pas l’object de tableau qui serait nécessaire pour le printf au travail.

Si votre objective est de faire fonctionner ce programme plutôt que de comprendre pourquoi il pourrait échouer, vous pouvez stocker le résultat de l’appel de fonction dans un object explicite:

 const struct s result = bar(); printf("%s", result.c); 

Vous avez maintenant un object struct avec une durée de stockage automatique plutôt que temporaire . Il existe donc pendant et après l’exécution de l’appel printf .

Le sharepoint séquence apparaît à la fin de l’expression complète, c’est-à-dire lorsque printf revient dans cet exemple. Il y a d’autres cas où des points de séquence se produisent

En réalité, cette règle indique que les fonctions temporaires ne vivent pas au-delà du sharepoint séquence suivant, ce qui se produit bien après son utilisation. Votre programme a donc un comportement assez bien défini.

Voici un exemple simple de comportement non bien défini:

 char* c = bar().c; *c = 5; // UB 

Ici, le sharepoint séquence est rencontré après la création de c et la mémoire sur laquelle il pointe est détruite, mais nous essayons ensuite d’accéder à c , ce qui aboutit à UB.

En C99, il y a un sharepoint séquence lors de l’appel d’une fonction, après l’évaluation des arguments (C99 6.5.2.2/10).

Ainsi, lorsque bar().c est évalué, il en résulte un pointeur sur le premier élément du tableau char c[7] dans la structure renvoyée par bar() . Cependant, ce pointeur est copié dans un argument (un argument sans nom, comme il arrive) à printf() , et au moment où l’appel est réellement passé à la fonction printf() , le sharepoint séquence mentionné ci-dessus est apparu. le pointeur pointé sur peut ne plus être en vie.

Comme le mentionne Keith Thomson , C11 (et C ++) offrent de meilleures garanties quant à la durée de vie des temporaires, de sorte que le comportement selon ces normes ne serait pas indéfini.