Prenez l'adresse d'un élément de tableau à une past-the-end via indice: juridique par la norme C ++ ou non?

StackOverflow https://stackoverflow.com/questions/988158

Question

Je l'ai vu à plusieurs reprises affirmé maintenant que le code suivant n'est pas autorisé par la norme C ++:

int array[5];
int *array_begin = &array[0];
int *array_end = &array[5];

est-il légal &array[5] C ++ code dans ce contexte?

Je voudrais une réponse avec une référence à la norme si possible.

Il serait également intéressant de savoir si elle répond à la norme C. Et si ce n'est pas standard C ++, pourquoi at-on décidé de le traiter différemment des array + 5 ou &array[4] + 1?

Était-ce utile?

La solution

Votre exemple est légal, mais seulement parce que vous ne l'utilisez en fait un pointeur hors des limites.

Traitons de pointeurs bounds premier (parce que ce que je l'origine interprété votre question, avant de remarquer que l'exemple utilise un pointeur d'un passé-la-end à la place):

En général, vous n'êtes même pas autorisé à créer un pointeur hors des limites du terrain. Un pointeur doit pointer sur un élément dans la matrice, ou une après la fin . Nulle part ailleurs.

Le pointeur est même pas permis d'exister, ce qui signifie que vous n'êtes évidemment pas autorisé à déréférencer soit.

Voici ce que la norme a à dire sur le sujet:

5,7: 5:

  

Si une expression qui a intégré   type est ajoutée à ou soustraite d'une   pointeur, le résultat a le type de   l'opérande de pointeur. Si le pointeur   Le secteur opérande à un élément d'un   objet de réseau, et le réseau est grand   assez, les points de résultat à une   élément décalé par rapport à l'original   élément de telle sorte que la différence de   les indices de la résultante et   éléments de réseau d'origine est égale à la   expression intégrale. En d'autres termes,   Si l'expression des points de P à la i-ième   élément d'un objet de réseau, la   expressions (P) + N (de façon équivalente,   N + (P)) et (P) -N (où N a la   La valeur n) de désigner, respectivement, le   i + n-ième éléments et e-i-n de la   objet de tableau, pour autant qu'ils existent.   De plus, si les points expression de P   pour le dernier élément d'un tableau   objet, l'expression (P) +1 points   une après le dernier élément de la matrice   objet, et si le point Q d'expression   une après le dernier élément d'un tableau   objet, l'expression (Q) -1 points à   le dernier élément de l'objet de réseau.   Si les deux opérandes de pointeur et la   point de résultat à des éléments de la même   objet de tableau ou une après le dernier   élément de l'objet de réseau, la   l'évaluation ne doit pas produire une   trop-plein; sinon, le comportement est   fi nis unde .

(Souligné par l'auteur)

Bien sûr, cela est pour l'opérateur +. Donc, juste pour être sûr, voici ce que la norme dit à propos souscriptage de tableau:

5.2.1: 1:

  

L'expression E1[E2] est identique (par dé fi nition) à *((E1)+(E2))

Bien sûr, il y a une mise en garde évidente: Votre exemple ne montre pas réellement un pointeur hors des limites du terrain. il utilise un pointeur « un après la fin », ce qui est différent. Le pointeur est autorisé à exister (comme dit ci-dessus), mais la norme, pour autant que je peux voir, rien dit à propos de déréférencement il. Le plus proche que je peux trouver est 3.9.2: 3:

  

[Note: par exemple, l'adresse d'un après la fin d'un tableau (5.7) serait considéré comme   pointer vers un objet sans rapport avec du type d'élément du tableau qui pourrait se trouver à cette adresse. Note -end]

Ce qui me semble laisser entendre que oui, vous pouvez légalement déréférencer, mais le résultat de la lecture ou l'écriture à l'emplacement est non spécifiée.

Merci à ilproxyil pour corriger le dernier bit ici, répondre à la dernière partie de votre question:

  • array + 5 ne fait pas déréférencer quoi que ce soit, simplement crée un pointeur vers un passé la fin de array.
  • &array[4] + 1 déréférence array+4 (ce qui est tout à fait sûr), prend l'adresse de ce lvalue, et ajoute un à cette adresse, qui se traduit par un pointeur d'un passé, l'extrémité (Mais ce pointeur ne fait jamais déréférencé.
  • &array[5] déréférence tableau + 5 (Qui, autant que je peux voir est légal, et les résultats dans « un objet sans rapport avec du type d'élément » du tableau, comme au-dessus de ladite) et prend alors la adresse de cet élément, qui a également semble assez juridique.

Alors, ils ne le font pas tout à fait la même chose, bien que dans ce cas, le résultat final est le même.

Autres conseils

Oui, il est légal. De la C99 projet de norme :

§6.5.2.1, paragraphe 2:

  

Une expression postfixe suivi d'une expression entre crochets est un [] subscripted   désignation d'un élément d'un objet de réseau. La définition de l'opérateur [] indice   est que E1[E2] est identique à (*((E1)+(E2))). En raison des règles de conversion   appliquer à l'opérateur de + binaire, si E1 est un objet de matrice (de manière équivalente, un pointeur vers la   élément initial d'un objet de réseau) et E2 est un nombre entier, désigne le E1[E2] E2-th   élément de E1 (en comptant à partir de zéro).

§6.5.3.2, paragraphe 3 (Souligné par l'auteur):

  

L'opérateur unaire & donne l'adresse de son opérande. Si l'opérande est de type « « type » »,   le résultat est de type « « pointeur type » ». Si l'opérande est le résultat d'un opérateur * unaire,   ni cet opérateur, ni l'opérateur & est évaluée et le résultat est comme si les deux étaient   omis, sauf que les contraintes qui pèsent sur les opérateurs appliquent encore et le résultat est pas   lvalue. De même, si l'opérande est le résultat d'un opérateur [], ni l'opérateur ni le & * unaire qui est impliqué par la [] est évaluée et le résultat est comme si l'opérateur &   ont été enlevés et l'opérateur de [] ont été modifiés à un opérateur + . Dans le cas contraire, le résultat est   un pointeur vers l'objet ou de la fonction désignée par l'opérande.

§6.5.6, paragraphe 8:

  

Si une expression est de type entier est ajouté ou soustrait d'un pointeur, le   résultat a le type de l'opérateur de pointeur. Si les points d'opérandes de pointeur sur un élément de   un objet de réseau, et le réseau est suffisamment grand, les points de résultat à un élément de décalage   l'élément d'origine de telle sorte que la différence des indices de la résultante et originale   éléments de réseau est égale à l'expression entière. En d'autres termes, si l'expression P points   l'élément de i-th d'un objet de réseau, les expressions (P)+N (de façon équivalente, N+(P)) et   (P)-N (où N a la valeur n) de désigner, respectivement, les éléments i+n-e et e-i−n de   l'objet de réseau, pour autant qu'ils existent. De plus, si l'expression P points la dernière   élément d'un objet de réseau, l'expression de (P)+1 souligne une après le dernier élément de la   objet tableau et, si la pointe d'expression Q une après le dernier élément d'un objet de réseau,   l'expression (Q)-1 points du dernier élément de l'objet de réseau. Si les deux le pointeur   opérande et le point de résultat à des éléments d'un même objet à réseau, ou une après le dernier   élément de l'objet tableau, l'évaluation ne doit pas produire un trop-plein; sinon, le   comportement est indéfini. Si le résultat indique un après le dernier élément de l'objet tableau, il   ne doit pas être utilisé comme opérateur d'un opérateur * unaire qui est évaluée.

Notez que la norme permet explicitement des pointeurs pour pointer un élément après la fin du tableau, à condition qu'ils ne soient pas déréférencé . Par 6.5.2.1 et 6.5.3.2, l'expression &array[5] est équivalente à &*(array + 5), ce qui équivaut à (array+5), ce qui indique une après la fin du tableau. Cela ne donne pas lieu à un déréférencement (par 6.5.3.2), il est donc légal.

est juridique.

Selon la documentation gcc C ++ , &array[5] est légal. Dans les deux C ++ et C vous pouvez en toute sécurité traiter l'élément d'un après la fin d'un tableau - vous obtiendrez un pointeur valide. Alors &array[5] comme une expression est légal.

Cependant, il est un comportement encore non défini pour tenter de pointeurs de déréférencer à mémoire non allouée, même si les points de pointeur vers une adresse valide. Ainsi, la tentative de déréférencement du pointeur généré par cette expression est encore un comportement non défini (à savoir illégale), même si le pointeur lui-même est valide.

Dans la pratique, j'imagine que ce serait généralement pas causer un accident, cependant.

Edit: Soit dit en passant, ce qui est généralement comment la fin () iterator pour conteneurs STL est mis en œuvre (comme un pointeur vers un passé, la fin), de sorte que c'est un très bon témoignage de la pratique étant légale <. / p>

Edit: Oh, maintenant je vois que vous ne demandez pas vraiment si vous teniez un pointeur vers cette adresse est légale, mais si cette façon exacte d'obtenir le pointeur est légal. Je vais céder la parole aux autres answerers à ce sujet.

Je crois que cela est légal, et cela dépend de la conversion 'lvalue à rvalue qui aura lieu. La dernière ligne prinicpal 232 a le suivant:

  

Nous avons convenu que l'approche de la norme semble bien: p = 0; * P; n'est pas en soi une erreur. Une conversion lvalue à rvalue serait-il donner un comportement non défini

Bien que ce soit par exemple un peu différent, ce qu'il fait spectacle est que le « * » ne donne pas lieu à lvalue à la conversion rvalue et ainsi, étant donné que l'expression est l'opérande immédiat de « & » qui attend un lvalue alors le comportement est défini.

Je ne crois pas qu'il est illégal, mais je pense que le comportement et tableau [5] est indéfini.

  • 5.2.1 [expr.sub] E1 [E2] est identique (par définition) à * ((E1) + (E2))

  • 5.3.1 [expr.unary.op] opérateur unaire * ... le résultat est une lvalue faisant référence à l'objet ou de la fonction à laquelle les points d'expression.

À ce stade, vous avez un comportement non défini, car l'expression ((E1) + (E2)) ne prouvaient pas en fait à un objet et la norme ne dit ce que le résultat devrait être à moins qu'il ne.

  • 1.3.12 [defns.undefined] comportement non défini peut également être prévu lorsque la présente Norme internationale omet la description d'une définition explicite du comportement.

Comme indiqué ailleurs, array + 5 et &array[0] + 5 sont des moyens valables et bien définies d'obtenir un pointeur d'un au-delà de la fin du tableau.

En plus des réponses ci-dessus, je vais souligner l'opérateur et peuvent être remplacés pour les classes. Donc, même si elle était valable pour PODs, il est probablement pas une bonne idée de faire pour un objet que vous savez n'est pas valide (un peu comme remplaçant opérateur & () en premier lieu).

est légal:

int array[5];
int *array_begin = &array[0];
int *array_end = &array[5];
  

Section 5.2.1 subscripting L'expression E1 [E2] est identique (par définition) à * ((E1) + (E2))

Donc, en ce que nous pouvons dire que array_end est équivalent aussi:

int *array_end = &(*((array) + 5)); // or &(*(array + 5))
  

Section 5.3.1.1 opérateur unaire « * »: L'opérateur unaire * effectue indirection: l'expression à laquelle il est appliqué est un pointeur sur un type d'objet, ou   un pointeur vers un type de fonction et le résultat est une lvalue faisant référence à l'objet ou la fonction dans laquelle les points d'expression.   Si le type de l'expression est « pointeur à T, » le type du résultat est « T. » [Note: un pointeur vers un type incomplet (autre   de vide cv) peut être déréférencé. Le lvalue ainsi obtenu peut être utilisé de façon limitée (pour initialiser une référence, pour   exemple); ce lvalue ne doit pas être converti en un rvalue, voir 4.1. - Note de fin]

La partie importante de ce qui précède:

  

« Le résultat est une lvalue faisant référence à l'objet ou une fonction ».

L'opérateur unaire '*' renvoie une lvalue se référant à l'int (pas de-refeference). L'opérateur unaire « & » obtient alors l'adresse du lvalue.

Tant qu'il n'y a pas de renvois d'un pointeur hors limites alors l'opération est entièrement couverte par la norme et tout comportement est défini. Donc, par ma lecture ce qui précède est tout à fait légal.

Le fait que beaucoup des algorithmes de STL dépendent du comportement étant bien défini, est une sorte d'allusion au fait que le comité de normalisation a déjà bien cela et je suis sûr qu'il ya une chose qui couvre explicitement.

La section commentaire ci-dessous présente deux arguments:

(s'il vous plaît lire: mais il est long et nous deux finissent par troll)

Argument 1

ce qui est illégal en raison de l'article 5.7 du paragraphe 5

  

Si une expression qui a un type entier est ajouté ou soustrait un pointeur, le résultat a le type de l'opérande de pointeur. Si les points d'opérande de pointeur vers un élément d'un objet de réseau, et le réseau est suffisamment grand, les points de résultat à un élément décalé par rapport à l'élément d'origine de telle sorte que la différence entre les indices des éléments de réseau résultant et originales est égale à l'expression intégrale. En d'autres termes, si les points expression de P à l'élément i-ième d'un objet de réseau, les expressions (P) + N (de façon équivalente, N + (P)) et (P) -N (où N a la valeur n) Point pour, respectivement, le i + n-ième et i - n-ième éléments de l'objet de réseau, pour autant qu'ils existent. De plus, si les points expression de P au dernier élément d'un objet de réseau, l'expression (P) +1 points une après le dernier élément de l'objet de réseau, et si les points Q de l'expression d'un passé le dernier élément d'un objet de réseau, l'expression (Q) -1 points au dernier élément de l'objet de réseau. Si les deux opérandes de pointeur et le point de résultat aux éléments du même objet tableau, ou un passé   le dernier élément de l'objet tableau, l'évaluation ne doit pas produire un trop-plein; sinon, le comportement est indéfini.

Et bien que la section est pertinente; il ne montre pas un comportement non défini. Tous les éléments du tableau que nous parlons sont soit dans le tableau ou un après la fin (qui est bien défini par le paragraphe ci-dessus).

Argument 2:

Le second argument présenté ci-dessous est: * est l'opérateur de référence
. Et bien que ce terme est couramment utilisé pour décrire l'opérateur « * »; ce terme est délibérément évité dans la norme comme le terme « de référence » est pas bien définie en termes de la langue et ce que cela signifie au matériel sous-jacent.

Bien que l'accès à la mémoire d'un au-delà de la fin du tableau est certainement un comportement non défini. Je ne suis pas convaincu que le unary * operator accède à la mémoire (lecture / écriture à la mémoire) dans ce contexte (pas d'une manière la norme définit). Dans ce contexte (tel que défini par la norme (voir 5.3.1.1)) le unary * operator retourne un lvalue referring to the object. Dans ma compréhension de la langue c'est pas l'accès à la mémoire sous-jacente. Le résultat de cette expression est alors immédiatement utilisé par l'opérateur unary & operator qui retourne l'adresse de l'objet visé par le lvalue referring to the object.

De nombreuses autres références à Wikipédia et les sources non canoniques sont présentées. Tous que je trouve hors de propos. C ++ est définie par la norme .

Conclusion:

Je suis wiling à concéder, il y a de nombreuses parties de la norme que je ne l'ai pas considéré et peut prouver mes arguments ci-dessus tort. NON sont fournis ci-dessous. Si vous me montrer une référence standard qui montre que c'est UB. Je vais

  1. Laissez la réponse.
  2. Mettre en majuscules cela est stupide et je me trompe pour tout lire.

Ce n'est pas un argument:

  

Pas tout dans le monde entier est défini par la norme C de. Ouvrez votre esprit.

projet de travail ( n2798 ):

  

"Le résultat de l'unaire et l'opérateur est   un pointeur vers son opérande. l'opérande   doit être une lvalue ou une quali fi ed-id.   Dans le premier cas, si le type de   expression est « T », le type de la   résultat est « pointeur vers T. » »(p. 103)

array [5] n'est pas un id qualifié mieux que je peux dire (la liste est à la page 87.); le plus proche semblerait être identifiant, mais tout en tableau est un tableau identifiant [5] n'est pas. Il ne constitue pas une lvalue parce que « une lvalue se réfère à un objet ou une fonction. » (P. 76). array [5] est évidemment pas une fonction, et ne garantit pas la référence à un objet valide (car tableau + 5 est après le dernier élément de tableau attribué).

De toute évidence, il peut fonctionner dans certains cas, mais ce n'est pas valide C ++ ou en toute sécurité.

Note: Il est légal d'ajouter à obtenir un passé tableau (p 113).

  

"si l'expression P [un pointeur]   pointe vers le dernier élément d'un tableau   objet, l'expression (P) +1 points   une après le dernier élément de la matrice   objet, et si le point Q d'expression   une après le dernier élément d'un tableau   objet, l'expression (Q) -1 points à   le dernier élément de l'objet de réseau.   Si les deux opérandes de pointeur et la   point de résultat à des éléments de la même   objet de tableau ou une après le dernier   élément de l'objet de réseau, la   l'évaluation ne doit pas produire une   fl ux sur "

Mais il est illégal de le faire en utilisant &.

Même si elle est légale, pourquoi partent de convention? tableau + 5 est plus courte de toute façon, et à mon avis, plus lisible.

Edit: Si vous voulez par symétrique vous pouvez écrire

int* array_begin = array; 
int* array_end = array + 5;

Il doit être un comportement non défini, pour les raisons suivantes:

  1. Essayer d'accéder aux éléments hors des limites du terrain entraîne un comportement non défini. D'où la norme ne défend pas une mise en œuvre lancer une exception dans ce cas (par exemple une mise en oeuvre avant la vérification des bornes d'un élément est accessible). Si & (array[size]) ont été définis à begin (array) + size, une mise en œuvre lancer une exception en cas d'accès hors limite ne serait pas conforme à la norme plus.

  2. Il est impossible de faire ce end (array) de rendement si le tableau est pas un tableau, mais plutôt un type de collection arbitraire.

norme C de 5,19, paragraphe 4:

Une adresse expression constante est un pointeur vers une lvalue .... Le pointeur doit être créé explicitement, en utilisant l'opérateur unaire & ... ou en utilisant une expression de tableau (4.2) ... le type. L'opérateur subscripting [] ... peut être utilisé dans la création d'une adresse expression constante, mais la valeur d'un objet ne doit pas être accessible par l'utilisation de ces opérateurs. Si l'opérateur est utilisé subscripting, l'un de ses opérandes est une expression constante intégrale.

Me semble & array [5] est légal C ++, étant une adresse expression constante.

Si votre exemple est pas un cas général, mais un particulier, il est permis. Vous pouvez légalement , autant que je sache, déplacer un passé bloc de mémoire alloué. Il ne fonctionne pas pour un cas générique si i.e. où vous essayez d'accéder à des éléments plus par 1 à partir de la fin d'un tableau.

Il suffit de rechercher C-Foire Aux Questions: texte du lien

Il est parfaitement légal.

Le vecteur <> classe modèle de la stl fait exactement quand vous appelez myVec.end (). Il vous obtient un pointeur (ici comme un itérateur) qui pointe un élément après la fin du tableau

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top