Pourquoi est-il illégal d’aplatir un tableau multidimensionnel en C?

Mon livre (Pointers on C de Kenneth Reek) dit que ce qui suit est illégal bien que cela fonctionne bien.

int arr[5][5]; int *p=&arr[2][2]; p=p+3; // As array is stored in row major form I think this //should make p point to arr[3][0] 

Le livre dit que laisser une rangée à la rangée suivante est illégal. Mais je ne peux pas comprendre pourquoi.

Cela fait un moment que je me passionne pour le sujet et je ferai de mon mieux pour expliquer d’où il vient, mais si je ne lis pas le livre, ce sera au mieux une conjecture.

Premièrement, techniquement, l’augmentation que vous proposez (ou qu’il a proposée) n’est pas illégale; le déréférencement est. La norme vous permet de faire avancer un pointeur jusqu’au dernier élément de la séquence de tableau à partir duquel il est extrait pour évaluation, mais pas pour déréférencement. Changez-le en p = p + 4 et les deux sont illégaux.

Cela dit, l’empreinte linéaire du tableau ne résiste pas, ar[2] a un type, et c’est int[5] . Si vous ne le croyez pas, considérez les points suivants, qui sont tous correctement tapés:

 int ar[5][5]; int (*sub)[5] = ar+2; // sub points to 3rd row int *col = *sub + 2; // col points to 3rd column of third row. int *p = col + 3; // p points to 5th colum of third row. 

Que ce soit ou non sur ar[3][0] n’est pas pertinent Vous dépassez la magnitude déclarée de la dimension participant au pointeur-math. Le résultat ne peut pas être annulé légalement, et s’il était plus grand qu’un offset 3, il ne pourrait même pas être évalué légalement.

Rappelez-vous que le tableau en question est ar[2] ; pas simplement ar , et said-same est déclaré comme étant size = 5. Le fait qu’il soit renforcé contre deux autres tableaux du même genre n’est pas pertinent pour l’adressage en cours. Je pense que la réponse de Christoph à la question proposée sous forme de duplicata aurait dû être celle choisie pour une solution définitive. En particulier, la référence à C99 §6.5.6, p8 qui, bien que verbeuse, apparaît ci-dessous avec:

Lorsqu’une expression de type entier est ajoutée ou soustraite à un pointeur, le résultat a le type de l’opérande de pointeur. Si l’opérande de pointeur pointe sur un élément d’un object tableau et que le tableau est suffisamment grand, le résultat pointe sur un élément décalé par rapport à l’élément d’origine, de sorte que la différence entre les indices des éléments de tableau résultant et original est égale à l’expression entière. En d’autres termes, si l’expression P pointe sur le i-ème élément d’un object tableau, les expressions (P) + N (de manière équivalente, N + (P)) et (P) -N (où N a la valeur n) point à, respectivement, les i + n-ème et i-n-ème éléments de l’object tableau, à condition qu’ils existent . De plus, si l’expression P pointe vers le dernier élément d’un object tableau, l’expression (P) +1 pointe un après le dernier élément de l’object tableau et si l’expression Q pointe un après le dernier élément d’un object tableau, l’expression (Q) -1 pointe vers le dernier élément de l’object tableau. Si l’opérande de pointeur et le résultat pointent tous deux sur des éléments du même object de tableau, ou d’un élément passé le dernier élément de l’object de tableau, l’évaluation ne doit pas produire de dépassement de capacité; sinon, le comportement n’est pas défini. Si le résultat pointe un après le dernier élément de l’object tableau, il ne doit pas être utilisé comme opérande d’un opérateur unaire * évalué.

Désolé pour le spam, mais les faits saillants en gras sont ce que je pense être pertinent pour votre question. En vous adressant comme vous êtes, vous quittez le tableau qui est adressé et vous entrez ainsi dans UB. En bref, cela fonctionne (généralement), mais ce n’est pas légal.

La raison pour laquelle le livre dit que c’est illégal, c’est parce que l’arithmétique de pointeur ne fonctionne que sur les pointeurs d’éléments dans le même tableau, ou l’un après la fin.

arr est un tableau de 5 éléments, chaque élément étant un tableau de 5 entiers. Ainsi, théoriquement, si vous voulez avoir des pointeurs sur des éléments de tableau dans arr[i] , vous ne pouvez utiliser que l’arithmétique de pointeur &arr[i][0..4] pointeurs compris dans l’intervalle &arr[i][0..4] ou arr[i]+5 conservant i constante .

Par exemple, imaginez que arr était une dimension de 5 nombres entiers. Ensuite, un pointeur p ne pourrait pointer que vers &arr[0..4] ou arr+5 (un après la fin). C’est ce qui se produit également avec les tableaux multidimensionnels.

Avec int arr[5][5]; , vous pouvez uniquement faire de l’arithmétique de pointeur de telle sorte que vous ayez toujours un pointeur situé dans la plage &arr[i][0..4] ou arr[i]+5 – c’est ce que disent les règles. Cela peut sembler déroutant, car ce sont des tableaux dans des tableaux, mais la règle est la même, peu importe quoi. Conceptuellement, arr[0] et arr[1] sont des tableaux différents, et même si vous savez qu’ils sont contigus en mémoire, il est illégal de faire de l’arithmétique de pointeur entre les éléments d’ arr[0] et d’ arr[1] . Rappelez-vous que sur le plan conceptuel, chaque élément de arr[i] est un tableau différent.

Dans votre exemple, cependant, p+3 pointera un point après la fin de arr[2][2] , donc il me semble que c’est néanmoins valable. C’est un mauvais choix d’exemple, car cela fera pointer précisément le point final, ce qui le rendra toujours valide. Si l’auteur avait choisi p+4 , l’exemple serait correct.

Quoi qu’il en soit, je n’ai jamais eu de problèmes avec l’aplatissement de tableaux multidimensionnels en C utilisant des méthodes similaires.

Voir aussi cette question, elle contient d’autres informations utiles: Accès unidimensionnel à un tableau multidimensionnel: C bien défini?

Oui. C’est illégal en C. En fait, vous posez votre compilateur. p pointe sur l’élément arr[2][2] (et correspond au type int ), c’est-à-dire au 3ème élément de la troisième ligne. La déclaration p=p+3; incrémentera le pointeur p sur arr[2][5] , ce qui équivaut à arr[3][0] .
Mais cela échouera chaque fois que la mémoire est allouée en tant que puissance de 2 ( 2 n ) sur une architecture. Dans ce cas, l’allocation de mémoire serait arrondie à 2 n , c’est-à-dire que dans votre cas, chaque ligne serait arrondie à 64 octets.
Voir un programme de test dans lequel la mémoire allouée est de 5 allocations de 10 entiers. Sur certaines machines, les allocations de mémoire sont un multiple de 16 octets. Par conséquent, les 40 octets demandés sont arrondis à 48 octets par allocation:

 #include  #include  extern void print_numbers(int *num_ptr, int n, int m); extern void print_numbers2(int **nums, int n, int m); int main(void) { int **nums; int n = 5; int m = 10; int count = 0; // Allocate rows nums = (int **)malloc(n * sizeof(int *)); // Allocate columns for each row for (int i = 0; i < n; i++) { nums[i] = (int *)malloc(m * sizeof(int)); printf("%2d: %p\n", i, (void *)nums[i]); } // Populate table for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) nums[i][j] = ++count; // Print table puts("print_numbers:"); print_numbers(&nums[0][0], n, m); puts("print_numbers2:"); print_numbers2(nums, n, m); return 0; } void print_numbers(int *nums_ptr, int n, int m) { int (*nums)[m] = (int (*)[m])nums_ptr; for (int i = 0; i < n; i++) { printf("%2d: %p\n", i, (void *)nums[i]); for (int j = 0; j < m; j++) { printf("%3d", nums[i][j]); } printf("\n"); } } void print_numbers2(int **nums, int n, int m) { for (int i = 0; i < n; i++) { printf("%2d: %p\n", i, (void *)nums[i]); for (int j = 0; j < m; j++) printf("%3d", nums[i][j]); printf("\n"); } } 

Exemple de sortie sur Mac OS X 10.8.5; GCC 4.8.1:

  0: 0x7f83a0403a50 1: 0x7f83a0403a80 2: 0x7f83a0403ab0 3: 0x7f83a0403ae0 4: 0x7f83a0403b10 print_numbers: 0: 0x7f83a0403a50 1 2 3 4 5 6 7 8 9 10 1: 0x7f83a0403a78 0 0 11 12 13 14 15 16 17 18 2: 0x7f83a0403aa0 19 20 0 0 21 22 23 24 25 26 3: 0x7f83a0403ac8 27 28 29 30 0 0 31 32 33 34 4: 0x7f83a0403af0 35 36 37 38 39 40 0 0 41 42 print_numbers2: 0: 0x7f83a0403a50 1 2 3 4 5 6 7 8 9 10 1: 0x7f83a0403a80 11 12 13 14 15 16 17 18 19 20 2: 0x7f83a0403ab0 21 22 23 24 25 26 27 28 29 30 3: 0x7f83a0403ae0 31 32 33 34 35 36 37 38 39 40 4: 0x7f83a0403b10 41 42 43 44 45 46 47 48 49 50 

Exemple de sortie sur Win7; GCC 4.8.1:

entrez la description de l'image ici