Est-ce un comportement indéfini de former une plage de pointeurs à partir d’une adresse de stack?

Certains programmeurs C ou C ++ sont surpris de découvrir que même stocker un pointeur invalide est un comportement indéfini . Cependant, pour les masortingces de stack ou de tas, il est correct de stocker l’adresse de l’un après la fin de la masortingce, ce qui vous permet de stocker les positions “de fin” à utiliser dans les boucles.

Mais est-ce un comportement indéfini de former une plage de pointeurs à partir d’une seule variable de stack, comme:

char c = 'X'; char* begin = &c; char* end = begin + 1; for (; begin != end; ++begin) { /* do something */ } 

Bien que l’exemple ci-dessus soit plutôt inutile, cela peut être utile dans le cas où une fonction s’attend à une plage de pointeurs, et dans certains cas, vous n’avez qu’une seule valeur à transmettre.

Ce comportement est-il indéfini?

Cela est autorisé, le comportement est défini et begin et end sont des valeurs de pointeur dérivées en toute sécurité .

Dans la norme C ++, section 5.7 ( [expr.add] ), paragraphe 4:

Pour les besoins de ces opérateurs, un pointeur sur un object non-array se comporte de la même façon qu’un pointeur sur le premier élément d’un tableau de longueur un avec le type de l’object comme type d’élément.

Si vous utilisez C, une clause similaire peut être trouvée dans le paragraphe 7 de la norme C99 / N1256, section 6.5.6.

Pour les besoins de ces opérateurs, un pointeur sur un object qui n’est pas un élément d’un tableau se comporte de la même manière qu’un pointeur sur le premier élément d’un tableau de longueur un avec le type de l’object comme type d’élément.


En passant, dans la section 3.7.4.3 ( [basic.stc.dynamic.safety] ) ” [basic.stc.dynamic.safety] dérivés en toute sécurité”, il y a une note de bas de page:

Cette section n’impose pas de ressortingctions sur les pointeurs de déréférencement vers la mémoire non alloués par ::operator new . Cela permet à de nombreuses implémentations C ++ d’utiliser des bibliothèques binarys et des composants écrits dans d’autres langages. Ceci s’applique en particulier aux fichiers binarys C, car le déréférencement des pointeurs vers la mémoire allouée par malloc n’est pas limité.

Cela suggère que l’arithmétique de pointeur dans la stack est un comportement défini par la mise en œuvre, pas un comportement indéfini.

Je crois que légalement, vous pouvez traiter un seul object comme un tableau de taille 1. De plus, il est tout à fait légal de prendre un pointeur au-delà de la fin d’un tableau tant qu’il n’est pas dé-référencé. Donc, je crois que ce n’est pas UB.

Ce n’est pas un comportement indéfini tant que vous ne déréférenciez pas l’iterator invalide .
Vous êtes autorisé à conserver un pointeur en mémoire au-delà de votre allocation, mais pas à le déréférencer.

5.7-5 de la norme ISO14882: 2011 (e) stipule:

Lorsqu’une expression de type intégral est ajoutée ou soustraite à un pointeur, le résultat a le type de l’opérande de pointeur. Si l’opérande de pointeur pointe sur un élément d’un object tableau et que le tableau est suffisamment grand, le résultat pointe sur un élément décalé par rapport à l’élément d’origine, de sorte que la différence entre les indices des éléments du tableau résultant et original soit égale à l’expression intégrale. En d’autres termes, si l’expression P pointe sur le i-ème élément d’un object tableau, les expressions (P) + N (de manière équivalente, N + (P)) et (P) -N (où N a la valeur n) point à, respectivement, les i + n-ième et i-n-ième éléments de l’object tableau, à condition qu’ils existent. De plus, si l’expression P pointe vers le dernier élément d’un object tableau, l’expression (P) +1 pointe un après le dernier élément de l’object tableau et si l’expression Q pointe un après le dernier élément d’un object tableau, l’expression (Q) -1 pointe vers le dernier élément de l’object tableau. Si l’opérande de pointeur et le résultat pointent tous deux sur des éléments du même object de tableau, ou d’un élément passé le dernier élément de l’object de tableau, l’évaluation ne doit pas produire de dépassement de capacité; sinon, le comportement n’est pas défini.

Sauf si j’ai oublié quelque chose, l’addition ne s’applique qu’aux pointeurs pointant sur le même tableau. Pour tout le rest, la dernière phrase s’applique: “sinon, le comportement n’est pas défini”

edit: En effet, lorsque vous ajoutez 5.7-4, il s’avère que l’opération que vous faites est (virtuellement) sur un tableau. La phrase ne s’applique donc pas:

Pour les besoins de ces opérateurs, un pointeur sur un object non-array se comporte de la même façon qu’un pointeur sur le premier élément d’un tableau de longueur un avec le type de l’object comme type d’élément.

En règle générale, le comportement au-delà de l’espace mémoire constituerait un comportement non défini. Il existe toutefois une exception pour “un après la fin”, qui est valide conformément au standard.

Par conséquent, dans l’exemple particulier, &c+1 est un pointeur valide mais ne peut pas être déréférencé en toute sécurité.

Vous pouvez définir c comme un tableau de taille 1:

char c[1] = { 'X' };

Le comportement non défini deviendrait alors un comportement défini. Le code résultant doit être identique.