Juste pour mettre tout cela ensemble et pour que nous puissions comparer les différentes idées qui sont apparues dans les différentes réponses. Je vais commenter ce que je pense de tout ça. wiki communautaire, car il s'agit simplement d'une collection de pensées d'autres personnes :) Tous les accents sont mis par moi ci-dessous.
Tout d'abord, nous devons nous préoccuper de savoir si le pointeur sur l'avant-dernier élément fait référence à un objet. Un tableau d'objets liés N
a N
des sous-objets qui sont ses éléments, comme expliqué dans la section 8.3.4/1
Un objet de type tableau contient un ensemble non vide, alloué de manière contiguë, de N sous-objets de type T. - 8.3.4/1
A ma connaissance, il n'y a aucune mention dans la norme d'un objet situé juste après un tableau. Si s'il existe un tel objet, nous sommes autorisés à déréférencer le pointeur qui pointe une fois après la fin, en raison du texte et de la note de clarification suivants
Si un objet de type T est situé à une adresse A On dit d'un pointeur de type cv T* dont la valeur est l'adresse A qu'il pointe vers cet objet, quelle que soit la façon dont cette valeur a été obtenue. [Note : par exemple, l'adresse un après la fin d'un tableau (5.7) serait considérée comme pointant vers un objet non lié au type d'élément du tableau, à savoir pourrait être situé à cette adresse. ] - 3.9.2/3
Cela veut dire que que ce qui suit est bien défini, si l'implémentation pose les objets de manière à ce que le stockage des b
est alloué directement derrière l'objet tableau (que vous pouvez obtenir manuellement si vous surallouez une partie de la mémoire en utilisant malloc, en assignant un pointeur à un tableau ayant une taille plus petite - je vais rester simple et illustrer seulement en utilisant l'exemple suivant)
int a[3], b;
*(a + 3) = 0;
assert(b == 0 && (a + 3 == &b) && a[3] == 0);
Le consentement sur quelques personnes est que votre expression montrée, &array[5]
c'est comportement indéfini . Ceci est basé sur le fait, qui demeure, que la Norme dit à 3.10/2
y 5.3.1/1
Une lvalue fait référence à un objet ou à une fonction. - 3.10/2
L'opérateur unaire * effectue une indirection : l'expression à laquelle il est appliqué est un pointeur vers un type d'objet ou un pointeur vers un type de fonction et le résultat est une lvalue se référant à l'objet ou à la fonction vers laquelle l'expression pointe. - 5.3.1/1
Ci-dessus, nous avons vu que nous n'avons pas la garantie qu'il existe un objet (du même type) après le dernier élément d'un tableau alloué. Ceci doit être distingué d'un autre cas, qui se produit lorsque vous avez un objet alloué (mémoire réservée), mais que cet objet n'a pas encore commencé à vivre, comme cela se produit si vous allouez de la mémoire avec malloc, et que vous allez placer-nouveau un objet dans cette zone : Dans ce cas, vous êtes autorisé à déréférencer la zone. avant vous invoquez le constructeur, pour autant que vous respectiez certaines règles simples, comme ne pas essayer de lire une valeur à partir de la valeur lval générée ( 3.8/5
y 3.8/6
)
Ce qui est intéressant, c'est de savoir ce qui se passe quand la lvalue fait no se réfère à un objet ? Rappelez-vous qu'une lvalue a pour faire référence à un objet (ou une fonction).
La norme dessine cette opération bien définie à 5.2.8/2
en parlant de la typeid
qui évalue les opérandes d'expression de valeur l.
Si l'expression lvalue est obtenue en appliquant l'opérateur unaire * à un pointeur et que le pointeur est une valeur nulle (4.10), l'expression typeid lève l'exception bad_typeid. - 5.3.1/1
Ceci est contraire à 3.10/2
qui exige qu'une expression lvalue se réfère à un objet ou à une fonction, ce qui n'est pas le cas d'un pointeur nul. À ce stade, nous avons un défaut dans la norme : Un endroit permet de déréférencer un pointeur nul d'une manière qui contredit une autre partie de la norme. Cela a été observé il y a longtemps et fait l'objet d'une discussion dans le rapport sur les questions liées. Comme le fait remarquer l'un d'entre eux, il s'agit simplement de gérer les nullités déréférencées spéciales, afin de contourner le problème des lvalues sans objet. Puisque cela commence par parler d'une lvalue, c'est au moins une façon problématique de la gérer actuellement.
L'idée pour gérer cela de manière générale, est d'introduire une lvalue vide qui ne se réfère volontairement à aucun objet ou fonction. Si nous essayons d'y lire une valeur, nous obtenons un comportement indéfini. Tant que nous ne le faisons pas, nous ne le faisons pas. Le déréférencement d'une adresse passée à la fin pourrait produire une telle lvalue vide, puisque nous ne pouvons pas être sûrs qu'il y ait un objet localisé ou non.
Cependant, comme l'indiquent les discussions sur ce rapport, il reste encore des questions en suspens (par exemple, que se passe-t-il avec notre cas de surallocation ?) avant qu'il puisse être intégré dans la norme.
Conclusion
Je crois qu'il n'y a ni bonne ni mauvaise façon de faire. Bien que j'aie une légère tendance à considérer cela comme un comportement généralement non défini, parce qu'il n'y a pas de valeur l que n'a pas se référer à un objet, je vois aussi la manière actuelle, assez problématique, de typeid
la gestion de ce problème. Comme il s'agit d'un problème actif dans la norme, le mieux que vous puissiez faire est d'effectuer une addition pour obtenir la valeur du pointeur, au lieu de déréférencer après la fin, évitant ainsi complètement le problème.
Notez que tout ce qui précède n'est pas un problème en C. Le C rend tout cela bien formé en disant &*
est proche d'un no-op mais fait juste d'un pointeur une valeur r, donc vous ne pouvez pas faire
(&*a) = NULL;
Malheureusement, la même chose n'est pas vraie pour le C++.
7 votes
@Brian : Non, il ne faudrait vérifier les limites que si le runtime devait attraper l'erreur. Pour éviter cela, la norme peut simplement dire "non autorisé". C'est le comportement indéfini dans toute sa splendeur. Vous n'êtes pas autorisé à le faire, et le runtime et le compilateur ne sont pas tenus de le faire. dire à vous si vous le faites.
0 votes
Ok, juste pour clarifier un peu, parce que le titre m'a induit en erreur : Un pointeur qui dépasse d'une unité la fin d'un tableau n'est pas hors limites. Les pointeurs hors limites ne sont pas autorisés en général, mais la norme est beaucoup plus indulgente avec les pointeurs un au-delà de la fin. Vous pourriez vouloir modifier le titre si vous posez une question spécifique sur les pointeurs un-passé-de-la-fin. Si vous voulez en savoir plus sur les pointeurs hors limites en général vous devriez modifier votre exemple ;)
0 votes
Il ne demande pas de conseils sur le passé en général. Il demande comment utiliser l'opérateur & pour obtenir le pointeur.
1 votes
@Matthew : Mais la réponse à cette question dépend de l'endroit où pointe le pointeur. Vous êtes autorisé à prendre l'adresse dans le cas d'un dépassement de la fin, mais pas dans le cas d'un dépassement de limites.
0 votes
Section 5.3.1.1 Opérateur unaire '*' : 'le résultat est une valeur l faisant référence à l'objet ou à la fonction'. Section 5.2.1 Subscripting L'expression E1[E2] est identique (par dénition) à *((E1)+(E2)). D'après ma lecture de la norme ici. Il n'y a pas de déréférencement du pointeur résultant. (voir l'explication complète ci-dessous)
0 votes
Remarque : vous pouvez simplement dire int* array_begin = array ;
0 votes
"Un après la fin" est la façon dont les plages sont spécifiées, y compris les itérateurs. Il serait donc très étrange que cela ne soit pas légal.