AFAIK, bien que nous ne puissions pas créer un tableau de mémoire statique de taille 0, mais nous pouvons le faire avec des tableaux dynamiques:
int a[0]{}; // Compile-time error
int* p = new int[0]; // Is well-defined
Comme je l'ai lu, p
agit comme un élément d'un bout à l'autre. Je peux imprimer l'adresse qui p
pointe vers.
if(p)
cout << p << endl;
Bien que je sois sûr de nous ne pouvons pas déréférencer ce pointeur (passé-dernier-élément) comme nous ne pouvons pas avec les itérateurs (passé-dernier élément), mais ce dont je ne suis pas sûr est de savoir si l'incrémentation de ce pointeur
p
? Un comportement non défini (UB) est-il comme avec les itérateurs?p++; // UB?
c++
pointers
undefined-behavior
dynamic-arrays
Itachi Uchiwa
la source
la source
std::vector
avec 0 élément.begin()
est déjà égal àend()
donc vous ne pouvez pas incrémenter un itérateur qui pointe au début.Réponses:
Les pointeurs vers des éléments de tableaux sont autorisés à pointer vers un élément valide ou après la fin. Si vous incrémentez un pointeur d'une manière qui va plus d'un après la fin, le comportement n'est pas défini.
Pour votre tableau de taille 0, il
p
pointe déjà au-delà de la fin, il n'est donc pas autorisé de l'incrémenter.Voir C ++ 17 8.7 / 4 concernant l'
+
opérateur (++
a les mêmes restrictions):la source
x[i]
est le même quex[i + j]
lorsque les deuxi
etj
ont la valeur 0?x[i]
est le même élément quex[i+j]
sij==0
.Je suppose que vous avez déjà la réponse; Si vous regardez un peu plus profondément: Vous avez dit que l'incrémentation d'un itérateur off-the-end est UB ainsi: Cette réponse est dans ce qu'est un itérateur?
L'itérateur est juste un objet qui a un pointeur et incrémentant cet itérateur qui incrémente vraiment le pointeur qu'il a. Ainsi, dans de nombreux aspects, un itérateur est traité en termes de pointeur.
Il s'agit de l'édition C ++ primer 5 de Lipmann.
Il est donc UB ne le faites pas.
la source
Au sens strict, ce n'est pas un comportement indéfini, mais défini par l'implémentation. Ainsi, bien que déconseillé si vous prévoyez de prendre en charge des architectures non traditionnelles, vous pouvez probablement le faire.
La citation standard donnée par interjay est bonne, indiquant UB, mais ce n'est que le deuxième meilleur résultat à mon avis, car elle traite de l'arithmétique pointeur-pointeur (curieusement, l'un est explicitement UB, tandis que l'autre ne l'est pas). Il y a un paragraphe traitant directement de l'opération dans la question:
Oh, attendez un instant, un type d'objet complètement défini? C'est tout? Je veux dire, vraiment, taper ? Vous n'avez donc pas du tout besoin d'un objet?
Il faut pas mal de lecture pour trouver un indice que quelque chose là-dedans pourrait ne pas être aussi bien défini. Parce que jusqu'à présent, il se lit comme si vous étiez parfaitement autorisé à le faire, sans restrictions.
[basic.compound] 3
fait une déclaration sur le type de pointeur que l'on peut avoir, et n'étant aucun des trois autres, le résultat de votre opération tomberait clairement sous 3.4: pointeur invalide .Cependant, cela ne dit pas que vous n'êtes pas autorisé à avoir un pointeur non valide. Au contraire, il répertorie certaines conditions normales très courantes (par exemple, la fin de la durée de stockage) où les pointeurs deviennent régulièrement invalides. C'est donc apparemment une chose admissible. Et en effet:
Nous faisons un "tout autre" là-bas, donc ce n'est pas un comportement indéfini, mais défini par l'implémentation, donc généralement autorisé (sauf si l'implémentation dit explicitement quelque chose de différent).
Malheureusement, ce n'est pas la fin de l'histoire. Bien que le résultat net ne change plus à partir de maintenant, il devient plus déroutant, plus vous recherchez "pointeur":
Lire comme: OK, peu importe! Tant qu'un pointeur pointe quelque part dans la mémoire , je vais bien?
Lire comme: OK, dérivé en toute sécurité, peu importe. Cela n'explique pas ce que c'est, ni ne dit que j'en ai réellement besoin. Dérivé en toute sécurité. Apparemment, je peux toujours avoir des pointeurs dérivés sans sécurité. Je suppose que le déréférencement ne serait probablement pas une si bonne idée, mais il est parfaitement possible de les avoir. Cela ne dit pas le contraire.
Oh, donc ça n'a peut-être pas d'importance, juste ce que je pensais. Mais attendez ... "peut-être pas"? Cela signifie que cela peut aussi bien . Comment puis-je savoir?
Attendez, il est donc même possible que je doive appeler
declare_reachable()
chaque pointeur? Comment puis-je savoir?Maintenant, vous pouvez convertir en
intptr_t
, qui est bien défini, donnant une représentation entière d'un pointeur dérivé en toute sécurité. Pour lequel, bien sûr, étant un entier, il est parfaitement légitime et bien défini de l'incrémenter à votre guise.Et oui, vous pouvez convertir le
intptr_t
dos en un pointeur, qui est également bien défini. Juste, n'étant pas la valeur d'origine, il n'est plus garanti que vous ayez un pointeur dérivé en toute sécurité (évidemment). Pourtant, dans l'ensemble, à la lettre de la norme, tout en étant défini par l'implémentation, c'est une chose 100% légitime à faire:La prise
Les pointeurs ne sont que des entiers ordinaires, vous seul les utilisez comme pointeurs. Oh si seulement c'était vrai!
Malheureusement, il existe des architectures où ce n'est pas vrai du tout, et générer simplement un pointeur invalide (ne pas le déréférencer, simplement l'avoir dans un registre de pointeur) provoquera un piège.
Voilà donc la base de la «mise en œuvre définie». Cela, et le fait que l'incrémentation d'un pointeur quand vous le souhaitez, comme vous le souhaitez, pourrait bien sûr provoquer un débordement, ce que la norme ne veut pas traiter. La fin de l'espace d'adressage de l'application peut ne pas coïncider avec l'emplacement du débordement, et vous ne savez même pas s'il existe un débordement pour les pointeurs sur une architecture particulière. Dans l'ensemble, c'est un gâchis cauchemardesque qui n'a aucun rapport avec les avantages possibles.
La gestion de la condition d'un objet passé de l'autre côté est simple: l'implémentation doit simplement s'assurer qu'aucun objet n'est jamais alloué afin que le dernier octet de l'espace d'adressage soit occupé. C'est donc bien défini car il est utile et trivial de garantir.
la source
+
opérateur (d'où++
découle) ce qui signifie que le pointage après "un après la fin" n'est pas défini.