Puis-je traiter un tableau 2D comme un tableau 1D contigu?

considère le code suivant:

int a[25][80];
a[0][1234] = 56;
int* p = &a[0][0];
p[1234] = 56;

la deuxième ligne invoque-t-elle un comportement non défini? Comment sur la quatrième ligne?

32
demandé sur fredoverflow 2011-09-01 14:35:14

6 réponses

ça dépend de l'interprétation. Bien que les exigences de contiguïté des tableaux ne laissent pas beaucoup à l'imagination en termes de comment mettre en page un tableau multidimensionnel (cela a été souligné précédemment), notez que lorsque vous faites p[1234] vous indexez le 1234E élément de la rangée de zéroth de seulement 80 colonnes. Certains interprètent les seuls indices valides comme étant 0..79 ( &p[80] étant un cas particulier).

informations de la C FAQ (Je ne pense pas que C et c++ diffèrent sur ce sujet et que c'est très pertinent.)

7
répondu Luc Danton 2011-09-01 16:47:25

Oui, vous pouvez (non, ce n'est pas UB), il est indirectement garanti par la norme. Voici comment: un tableau 2D est un tableau de tableaux. Un tableau est garanti d'avoir une mémoire contiguë et sizeof(array) est sizeof(elem) fois le nombre d'éléments. Il s'ensuit que ce que vous essayez de faire est parfaitement légal.

10
répondu Armen Tsirunyan 2011-09-01 10:37:47

les deux lignes do donnent lieu à un comportement non défini.

Subscripting est interprété comme pointeur de la plus, suivie par une indirection, qui est, a[0][1234] / p[1234] est équivalent à *(a[0] + 1234) / *(p + 1234) . Selon [expr.ajouter] / 4 (ici je cite le plus récent projet, alors que pour le moment OP est proposé, vous pouvez vous référer à ce commentaire , et la conclusion est la même):

si l'expression P pointe vers l'élément x[i] d'un objet de tableau x avec n éléments, les expressions P + J et J + P (où J a la valeur j) pointent vers l'élément (éventuellement-hypothétique) x[i+j] SI≤0 i+j≤n; sinon, le comportement n'est pas défini.

depuis a[0] (cède à un pointeur à a[0][0] ) / p pointe vers un élément de a[0] (comme un array), et a[0] n'a que la taille 80, le comportement n'est pas défini.


une chose intéressante est qu'au moment où OP est proposé (avant C++17), ce qui suit est bien défini:

int* p = &a[0][0];
p[80] = 56; 
p += 80; // p now passes the end of a[0], and points to a[1][0] at the same time
p[80] = 56; // ok because p points to an element of a[1]

selon

si un objet de type T est situé à une adresse A, un pointeur de type cv T* dont la valeur est l'adresse A est dit pointer vers cet objet, quelle que soit la façon dont la valeur a été obtenue. [ Remarque: Par exemple, l'adresse d'un passé la fin d'un tableau ([expr.add]) serait considéré pour pointer vers un objet non relié du type d'élément du tableau qui pourrait être localisé à cette adresse. il y a d'autres restrictions sur les pointeurs vers les objets avec une durée de stockage dynamique; voir [basic.STC.dynamique.sécurité.] - la note de fin ]

cependant, cela n'est plus autorisé après C++17. La formulation ci-dessus est modifié en

Une valeur d'un type de pointeur est un pointeur vers le passé ou la fin d'un objet représente l'adresse du premier octet dans la mémoire ([intro.mémoire]) occupé par l'objet ou le premier octet en mémoire après la fin du stockage occupé par l'objet, respectivement. [ Note: Un pointeur passé la fin d'un objet ([expr.add]) n'est pas considéré comme pointant vers un objet non relié du type de l'objet qui peut-être situé à cette adresse. une valeur de pointeur devient invalide lorsque le stockage qu'elle dénote atteint la fin de sa durée de stockage; voir [basic.STC.] - la note de fin ]

par P0137 .

1
répondu xskxzr 2018-06-11 10:24:23

la mémoire référencée par a est à la fois une int[25][80] et une int[2000] . C'est ce que dit la norme, 3.8p2:

[Note: la durée de vie d'un objet array commence dès que le stockage avec la taille et l'alignement appropriés est obtenu, et sa durée de vie se termine lorsque le stockage que le tableau occupe est réutilisé ou libéré. 12.6.2 décrit la durée de vie des sous-projets de la base et des membres. - la note de fin ]

a a un type particulier, c'est une valeur l de type int[25][80] . Mais p est juste int* . Ce n'est pas int* pointant vers un int[80] ou quelque chose comme ça. Donc, en fait , le int pointé est un élément de int[25][80] nommé a , et aussi un élément de int[2000] occupant le même espace.

Depuis p et p+1234 sont les deux éléments de la même int[2000] objet, l'arithmétique des pointeurs est bien définie. Et puisque p[1234] signifie *(p+1234) , il est aussi bien défini.

l'effet de cette règle pour la durée de vie du tableau est que vous pouvez librement utiliser l'arithmétique pointeur pour se déplacer à travers un objet complet.


Depuis std::array a obtenu mentionné dans les commentaires:

si on a std::array<std::array<int, 80>, 25> a; alors il n'existe pas a std::array<int, 2000> . Il existe un int[2000] . Je cherche tout ce qui nécessite sizeof (std::array<T,N>) == sizeof (T[N]) (et == N * sizeof (T) ). Sinon, vous devez supposer qu'il pourrait y avoir des lacunes qui gâcher la traversée de imbriquée std::array .

0
répondu Ben Voigt 2014-06-20 00:26:59

vous êtes libre de réinterpréter la mémoire comme vous le souhaitez. Aussi longtemps que les multiples ne dépasse pas la mémoire linéaire. Vous pouvez même déplacer a à 12, 40 et utiliser des indices négatifs.

-1
répondu Tavison 2011-09-01 10:39:10

votre compilateur va lancer un tas d'avertissements/erreurs en raison de l'indice hors de portée (ligne 2) et les types incompatibles (ligne 3), mais aussi longtemps que la variable réelle (int dans ce cas) est l'un des types de base intrinsèques Ce EST enregistrer à faire en C et C++. (Si la variable est une classe / struct, elle fonctionnera probablement encore en C, mais en C++ Tous les paris sont désactivés.)

pourquoi vous voulez faire ça.... Pour la 1ère variante: Si votre code s'appuie sur ce genre de vous embêter il sera sujet aux erreurs et difficile à maintenir à long terme.

je peux voir une certaine utilisation pour la deuxième variante lorsque la performance d'optimisation des boucles sur des tableaux 2D en les remplaçant par un pointeur 1D exécuter sur l'espace de données, mais un bon compilateur d'optimisation fera souvent cela par lui-même de toute façon. Si le corps de la boucle est si grand / complexe que le compilateur ne peut pas optimiser/remplacer la boucle par une exécution en 1D tout seul, le gain de performance en le faisant manuellement ne sera probablement pas significatif. soit.

-1
répondu Tonny 2011-09-01 10:46:50