85 votes

Un pointeur avec la bonne adresse et le bon type reste-t-il toujours un pointeur valide depuis C ++17?

(En référence à cette question et la réponse.)

Avant le C++17 standard, la phrase suivante a été inclus dans [de base.composé]/3:

Si un objet de type T est situé à une adresse à l'Un, un pointeur de type cv T* dont la valeur est l'adresse d'Une est, dit-point de l'objet, indépendamment de la façon dont la valeur a été obtenue.

Mais depuis C++17, cette phrase a été supprimée.

Par exemple, je crois que cette phrase fait cet exemple de code défini, et que, depuis le C++17 c'est un comportement indéfini:

 alignas(int) unsigned char buffer[2*sizeof(int)];
 auto p1=new(buffer) int{};
 auto p2=new(p1+1) int{};
 *(p1+1)=10;

Avant le C++17, p1+1 contient l'adresse d' *p2 et a le droit, alors *(p1+1) est un pointeur vers *p2. En C++17 p1+1 est un pointeur passé-la-fin, de sorte qu'il n'est pas un pointeur vers un objet et je crois qu'il n'est pas dereferencable.

Est-ce l'interprétation de cette modification de la norme de droit ou il y en a d'autres règles que compenser la suppression de la cité de la phrase?

46voto

Barry Points 45207

Est-ce l'interprétation de cette modification de la norme de droit ou il y en a d'autres règles que compenser la suppression de cette cité phrase?

Oui, cette interprétation est correcte. Un pointeur passé la fin n'est pas simplement convertible à l'autre la valeur du pointeur qui arrive à point à cette adresse.

La nouvelle [de base.composé]/3 dit:

Chaque valeur de type pointeur est l'un des suivants:
(3.1) un pointeur vers un objet ou une fonction (le pointeur est dit pour pointer vers l'objet ou la fonction), ou
(3.2) un pointeur passé la fin d'un objet ([expr.ajouter]), ou

Ceux-ci sont mutuellement exclusifs. p1+1 est un pointeur passé à la fin, pas un pointeur vers un objet. p1+1 de points à une hypothétique x[1] d'un taille-1 tableau lors de la p1, pas à p2. Ces deux objets ne sont pas de pointeur interconvertibles.

Nous avons également la non-normatif note:

[ Note: Un pointeur passé la fin d'un objet ([expr.ajouter]) n'est pas considéré comme point à un autre objet du type de l'objet qui pourrait être situé à cette adresse. [...]

qui précise l'intention.


Comme T. C. points à de nombreux commentaires (notamment celui-ci), c'est vraiment un cas particulier du problème qui vient avec le fait d'essayer de mettre en œuvre std::vector - qui est qui [v.data(), v.data() + v.size()) doit être une plage valide et pourtant, vector ne pas créer un objet array, donc, la seule définis de l'arithmétique des pointeurs serait aller à partir de n'importe quel objet donné dans le vecteur passé la fin de son hypothétique d'un tableau de taille. Pour plus de ressources, voir GTC 2182, cette std discussion, et deux révisions d'un document sur le sujet: P0593R0 et P0593R1 (section 1.3, en particulier).

8voto

Serge Ballesta Points 12850

Dans votre exemple, *(p1 + 1) = 10; devrait être UB, parce que c'est un passé la fin de la matrice de taille 1. Mais nous sommes dans un cas très spécial ici, car le tableau a été construit dynamiquement dans un grand char tableau.

Dynamique de création de l'objet est décrit dans 4.5 Le C++ object model [intro.objet], §3 de la n4659 projet de la norme C++:

3 Si un objet est créé (8.3.4) dans le stockage associé à un autre objet e de type "tableau de N unsigned char" ou de type "tableau de N std::byte" (21.2.1), ce tableau fournit un espace de stockage pour le créé objet si:
(3.1) - la durée de vie de e a commencé et pas terminé, et
(3.2) - le stockage pour le nouvel objet s'adapte entièrement à l'intérieur de e, et
(3.3) - il n'y a pas de plus petit objet tableau qui répond à ces contraintes.

Le 3.3 semble pas claire, mais les exemples ci-dessous que l'intention soit plus clair:

struct A { unsigned char a[32]; };
struct B { unsigned char b[16]; };
A a;
B *b = new (a.a + 8) B; // a.a provides storage for *b
int *p = new (b->b + 4) int; // b->b provides storage for *p
// a.a does not provide storage for *p (directly),
// but *p is nested within a (see below)

Ainsi dans l'exemple, l' buffer tableau fournit un espace de stockage pour les deux *p1 et *p2.

Les paragraphes suivants prouver que l'objet, pour les deux *p1 et *p2 est buffer:

4 un objet est imbriqué dans un autre objet b si:
(4.1) - a est un sous-objet de b, ou
(4.2) - b fournit un espace de stockage pour un, ou
(4.3) - il existe un objet de c, où a est imbriquée à l'intérieur de c, et c est imbriquée dans b.

5 Pour chaque objet x, il existe un certain objet appelé l'objet complet de x, déterminé comme suit:
(5.1) - Si x est un objet, alors l'objet de x est lui-même.
(5.2) - dans le cas Contraire, l'objet de x est l'objet de la (unique) de l'objet qui contient x.

Une fois que cela est établi, l'autre partie du projet de n4659 pour C++17 est [de base.coumpound] §3(souligner le mien):

3 ... Tous les valeur de type pointeur est l'un des suivants:
(3.1) - un pointeur vers un objet ou une fonction (le pointeur est dit pour pointer vers l'objet ou la fonction), ou
(3.2) - un pointeur passé la fin d'un objet (8.7), ou
(3.3) - la valeur de pointeur null (7.11) pour ce type, ou
(3.4) - une valeur de pointeur non valide.

Une valeur de type pointeur est un pointeur vers le passé ou la fin d'un objet représente l'adresse de l' le premier octet dans la mémoire (4.4) occupée par l'objet ou le premier octet dans la mémoire après la fin de la de stockage occupée par l'objet, respectivement. [ Note: Un pointeur passé la fin d'un objet (8.7) n'est pas considéré comme point de un sans rapport avec l'objet du type de l'objet qui pourrait être situé à cette adresse. Une valeur de type pointeur devient non valide lors de l'entreposage, il dénote atteint la fin de sa durée de stockage; voir 6.7. -la note de fin ] Pour les fins de l'arithmétique des pointeurs (8.7) et de comparaison (8.9, 8.10), un pointeur passé à la fin du dernier élément d'un ensemble x de n éléments est considéré comme équivalent à un pointeur vers un hypothétique élément x[n]. L' la valeur de la représentation de type pointeur est mise en œuvre définies. Les pointeurs de mise en page compatible avec types de ont la même valeur de la représentation et de l'alignement des exigences (6.11)...

La note d'Un pointeur passé à la fin... ne s'applique pas ici, car les objets pointés par p1 et p2 et pas sans rapport, mais sont imbriqués dans le même objet, de sorte que le pointeur de l'arithmétique sens à l'intérieur de l'objet qui fournit une capacité de stockage: p2 - p1 est définie et est - (&buffer[sizeof(int)] - buffer]) / sizeof(int) 1.

Donc, p1 + 1 est un pointeur vers *p2, et *(p1 + 1) = 10; a défini le comportement et définit la valeur de *p2.


J'ai aussi lu le C4 annexe sur la compatibilité entre le C++14 et courant (C++17) des normes. Suppression de la possibilité d'utiliser le pointeur de l'arithmétique entre les objets créés dynamiquement dans un seul tableau de caractères serait un changement important qui à mon humble avis devrait être citée, car elle est couramment utilisée pour la fonction. Que rien à ce sujet existent dans la compatibilité des pages, je pense qu'il confirme qu'il n'était pas l'intention de la norme pour l'interdire.

En particulier, il irait à l'encontre de cette dynamique commune de la construction d'un tableau d'objets à partir d'une classe sans constructeur par défaut:

class T {
    ...
    public T(U initialization) {
        ...
    }
};
...
unsigned char *mem = new unsigned char[N * sizeof(T)];
T * arr = reinterpret_cast<T*>(mem); // See the array as an array of N T
for (i=0; i<N; i++) {
    U u(...);
    new(arr + i) T(u);
}

arr peut alors être utilisé comme un pointeur vers le premier élément d'un tableau...

1voto

Dan Allen Points 2417

Pour se développer sur les réponses données ici est un exemple de ce que je crois le texte révisé est à l'exclusion de:

Avertissement: Comportement Indéfini

#include <iostream>
int main() {
    int A[1]{7};
    int B[1]{10};
    bool same{(B)==(A+1)};

    std::cout<<B<< ' '<< A <<' '<<sizeof(*A)<<'\n';
    std::cout<<(same?"same":"not same")<<'\n';
    std::cout<<*(A+1)<<'\n';//!!!!!  
    return 0;
}

Pour entièrement dépendant de l'implémentation (et fragile) raisons possibles de sortie de ce programme est:

0x7fff1e4f2a64 0x7fff1e4f2a60 4
same
10

Cette sortie montre que les deux tableaux (dans ce cas) il arrive à être stockées dans la mémoire telles que "un passé la fin" de la A arrive à contenir la valeur de l'adresse du premier élément d' B.

La spécification révisée est de veiller à ce que, indépendamment A+1 n'est jamais un pointeur valide pour B. La vieille expression "quelle que soit la façon dont la valeur est obtenue' a dit que si Un "+1 "arrive à point à" B[0]' alors c'est un pointeur valide pour 'B[0]'. Qui ne peut pas être bon et il n'en a l'intention.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X