Pointeur vs tableau en C, différence non sortingviale

Je pensais avoir vraiment compris cela, et relire la norme (ISO 9899: 1990) ne fait que confirmer ma compréhension manifestement erronée, alors je pose la question ici.

Le programme suivant se bloque:

#include  #include  typedef struct { int array[3]; } type1_t; typedef struct { int *ptr; } type2_t; type1_t my_test = { {1, 2, 3} }; int main(int argc, char *argv[]) { (void)argc; (void)argv; type1_t *type1_p = &my_test; type2_t *type2_p = (type2_t *) &my_test; printf("offsetof(type1_t, array) = %lu\n", offsetof(type1_t, array)); // 0 printf("my_test.array[0] = %d\n", my_test.array[0]); printf("type1_p->array[0] = %d\n", type1_p->array[0]); printf("type2_p->ptr[0] = %d\n", type2_p->ptr[0]); // this line crashes return 0; } 

Comparaison des expressions my_test.array[0] et type2_p->ptr[0] selon mon interprétation de la norme:

6.3.2.1 Inscription en tableau

“La définition de l’opérateur en indice [] est que E1 [E2] est identique à (* ((E1) + (E2))).”

En appliquant cela, on obtient:

 my_test.array[0] (*((E1)+(E2))) (*((my_test.array)+(0))) (*(my_test.array+0)) (*(my_test.array)) (*my_test.array) *my_test.array type2_p->ptr[0] *((E1)+(E2))) (*((type2_p->ptr)+(0))) (*(type2_p->ptr+0)) (*(type2_p->ptr)) (*type2_p->ptr) *type2_p->ptr 

type2_p->ptr a le type “pointeur sur int” et la valeur est l’adresse de début de my_test . *type2_p->ptr donc considéré comme un object entier dont le stockage est à la même adresse que my_test .

Plus loin:

6.2.2.1 Lvalues, tableaux et désignateurs de fonctions

“Sauf s’il s’agit de l’opérande de l’opérateur sizeof ou de l’opérateur unary & …, une valeur lvalue ayant le type array of type est convertie en une expression de type pointer to type qui pointe vers l’élément initial de l’object tableau et n’est pas une lvalue. ”

my_test.array a le type “array of int” et est comme décrit ci-dessus converti en “pointeur sur int” avec l’adresse du premier élément comme valeur. *my_test.array donc à un object entier dont le stockage est à la même adresse que le premier élément du tableau.

et enfin

6.5.2.1 Spécificateurs de structure et d’union

Un pointeur sur un object de structure, converti de manière appropriée, pointe sur son membre initial … et inversement. Il peut y avoir un remplissage non nommé dans un object de structure, mais pas au début, comme nécessaire pour obtenir l’alignement approprié.

Étant donné que le premier membre de type1_t est le tableau, son adresse de début et celle de l’object type1_t ensemble sont identiques à celles décrites ci-dessus. J’avais donc compris que *type2_p->ptr à un entier dont le stockage est à la même adresse que le premier élément du tableau et est donc identique à *my_test.array .

Mais cela ne peut pas être le cas, car le programme plante systématiquement sous solaris, cygwin et linux avec les versions 2.95.3, 3.4.4 et 4.3.2 de gcc, de sorte que toute question environnementale est totalement hors de question.

Où est mon raisonnement faux / qu’est-ce que je ne comprends pas? Comment déclarer type2_t pour que ptr pointe sur le premier membre du tableau?

Un tableau est une sorte de stockage. Syntaxiquement, il est utilisé comme un pointeur, mais physiquement, il n’y a pas de variable “pointeur” dans cette structure – seulement les trois ints. D’autre part, le pointeur int est un type de données réel stocké dans la structure. Par conséquent, lorsque vous effectuez la conversion, vous faites probablement en sorte que ptr prenne la valeur du premier élément du tableau, à savoir 1.

* Je ne suis pas sûr qu’il s’agisse d’un comportement défini, mais c’est ainsi que cela fonctionnera au moins sur les systèmes les plus courants.

S’il vous plaît pardonnez-moi si je néglige quelque chose dans votre parsing. Mais je pense que le bug fondamental dans tout ce qui est cette hypothèse erronée

type2_p-> ptr a le type “pointeur sur int” et la valeur est l’adresse de début de my_test.

Il n’y a rien qui lui donne cette valeur. Au contraire, il est très probable que cela pointe quelque part vers

 0x00000001 

Parce que ce que vous faites est d’interpréter les octets constituant ce tableau entier comme un pointeur. Ensuite, vous ajoutez quelque chose et indice.

De plus, je doute fort que votre transtypage vers l’autre structure soit réellement valide (comme dans, garanti de fonctionner). Vous pouvez lancer et ensuite lire une séquence initiale commune de l’une ou l’autre des structures si elles sont toutes deux membres d’une union. Mais ils ne sont pas dans votre exemple. Vous pouvez également atsortingbuer un pointeur au premier membre. Par exemple:

 typedef struct { int array[3]; } type1_t; type1_t f = { { 1, 2, 3 } }; int main(void) { int (*arrayp)[3] = (int(*)[3])&f; (*arrayp)[0] = 3; assert(f.array[0] == 3); return 0; } 

Où est mon raisonnement faux / qu’est-ce que je ne comprends pas?

type_1::array (syntaxe non ssortingctement C) n’est pas un int * ; c’est un int [3] .

Comment déclarer type2_t pour que ptr pointe sur le premier membre du tableau?

 typedef struct { int ptr[]; } type2_t; 

Cela déclare un membre de tableau flexible. De la norme C (paragraphe 6.7.2.1 16):

Cependant, quand a. (ou ->) L’opérateur a un opérande gauche qui est (un pointeur sur) une structure avec un membre de tableau flexible et l’opérande de droite nomme ce membre. Il se comporte comme si ce membre était remplacé par le tableau le plus long (avec le même type d’élément ) qui ne rendrait pas la structure plus grande que l’object auquel on accède; le décalage du tableau doit restr celui du membre du tableau flexible, même s’il serait différent de celui du tableau de remplacement.

C’est-à-dire qu’il peut alias type1_t::array correctement.

Ce doit être un comportement défini. Pensez-y en termes de mémoire.

Pour plus de simplicité, supposons que my_test soit à l’adresse 0x80000000.

 type1_p == 0x80000000 &type1_p->my_array[0] == 0x80000000 // my_array[0] == 1 &type1_p->my_array[1] == 0x80000004 // my_array[1] == 2 &type1_p->my_array[2] == 0x80000008 // my_array[2] == 3 

Lorsque vous le convertissez en type2_t,

 type2_p == 0x80000000 &type2_p->ptr == 0x8000000 // type2_p->ptr == 1 type2_p->ptr[0] == *(type2_p->ptr) == *1 

Pour faire ce que vous voulez, vous devez soit créer une structure secondaire et affecter l’adresse du tableau à ptr (par exemple type2_p-> ptr = type1_p-> mon_array) ou déclarer ptr en tant que tableau (ou tableau de longueur variable, par exemple int ptr []).

Vous pouvez également accéder aux éléments de manière laide: (& type2_p-> ptr) [0] , (& type2_p-> ptr) [1] . Cependant, soyez prudent ici car (& type2_p-> ptr) [0] sera en fait un int * , pas un int . Sur les plates-formes 64 bits, par exemple, (& type2_p-> ptr) [0] sera en réalité 0x100000002 (4294967298).