¿Por qué el aplanamiento de una matriz multidimensional en C es ilegal?

Mi libro (Punteros en C por Kenneth Reek) dice que lo siguiente es ilegal, aunque funciona 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] 

El libro dice que salir de una fila a la siguiente es ilegal. Pero no puedo entender por qué.

Me arrepentí de esto por un tiempo, y haré lo posible por explicar de dónde creo que viene, aunque sin leer el libro, será la mejor conjetura.

Primero, técnicamente, el incremento que propones (o él propuso) no es ilegal; desreferenciación es. El estándar le permite avanzar un puntero a uno más allá del último elemento de la secuencia de la matriz de la que se obtiene para la valoración, pero no para la desreferencia. Cámbielo a p = p + 4 y ambos son ilegales.

Dejando de lado, la huella lineal de la matriz no soporta, ar[2] tiene un tipo, y es int[5] . Si no crees eso, considera lo siguiente, todo lo cual está escrito correctamente:

 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. 

Si esto cae en ar[3][0] no es relevante. Está excediendo la magnitud declarada de la dimensión que participa en el puntero matemático. El resultado no puede ser referenciado legalmente, y si fuera más grande que una compensación de 3, ni podría ser legalmente evaluado.

Recuerde, la matriz que se está ar[2] es ar[2] ; no solo ar , y dicho-mismo se declara que es tamaño = 5. El hecho de que se refuerce contra otras dos matrices del mismo tipo no es relevante para el direccionamiento que se está realizando actualmente . Creo que la respuesta de Christoph a la pregunta propuesta como duplicado debería haber sido la que se seleccionó para una solución directa. En particular, la referencia a C99 §6.5.6, p8 que, aunque prolija, aparece a continuación con:

Cuando una expresión que tiene un tipo entero se agrega o se resta de un puntero, el resultado tiene el tipo del operando puntero. Si el operando puntero apunta a un elemento de un objeto de matriz, y la matriz es lo suficientemente grande, el resultado apunta a un elemento desplazado desde el elemento original, de manera que la diferencia de los subíndices de los elementos de la matriz resultante y original sea igual a la expresión entera. En otras palabras, si la expresión P apunta al elemento i-th de un objeto de matriz, las expresiones (P) + N (equivalentemente, N + (P)) y (P) -N (donde N tiene el valor n) punto a, respectivamente, los elementos i + n-th e i-n-th del objeto de matriz, siempre que existan . Además, si la expresión P apunta al último elemento de un objeto de matriz, la expresión (P) +1 apunta uno más allá del último elemento del objeto de matriz, y si la expresión Q apunta uno más allá del último elemento de un objeto de matriz, la expresión (Q) -1 apunta al último elemento del objeto de matriz. Si tanto el operando puntero como el resultado apuntan a elementos del mismo objeto de matriz, o uno más allá del último elemento del objeto de matriz, la evaluación no producirá un desbordamiento; de lo contrario, el comportamiento es indefinido. Si el resultado apunta uno más allá del último elemento del objeto de la matriz, no se utilizará como el operando de un operador unario * que se evalúa.

Lo siento por el spam, pero lo más destacado en negrita es lo que creo que es relevante para su pregunta. Al dirigirse como está, está dejando que se direccione la matriz , y como tal entrando a UB. En resumen, funciona (normalmente), pero no es legal.

La razón por la que el libro dice que es ilegal es porque se garantiza que la aritmética de punteros funcionará solo en los punteros a elementos de la misma matriz, o una pasada al final.

arr es una matriz de 5 elementos, en la que cada elemento es una matriz de 5 enteros. Por lo tanto, teóricamente, si desea tener punteros a elementos de matriz en arr[i] , solo puede hacer aritmética de punteros que produzca punteros en el rango &arr[i][0..4] o arr[i]+5 manteniendo i constante

Por ejemplo, imagine arr era un unidimensional de 5 enteros. Luego, un puntero p solo podría apuntar a cada uno de &arr[0..4] o arr+5 (uno más allá del final). Esto es lo que sucede con las matrices multidimensionales también.

Con int arr[5][5]; , solo puede hacer aritmética de punteros de manera que siempre tenga un puntero que esté en el rango &arr[i][0..4] o arr[i]+5 , eso es lo que dicen las reglas. Simplemente puede ser confuso porque son matrices dentro de matrices, pero la regla es la misma sin importar qué. Conceptualmente, arr[0] y arr[1] son arreglos diferentes, y aunque usted sabe que son contiguos en la memoria, es ilegal hacer aritmética de punteros entre los elementos de arr[0] y arr[1] . Recuerde que, conceptualmente, cada elemento en arr[i] es una matriz diferente.

Sin embargo, en tu ejemplo, p+3 apuntará a uno más allá del final de arr[2][2] , por lo que me parece que es válido, no obstante. Es una mala elección de un ejemplo porque hará que el punto p exacto hasta el final, por lo que sigue siendo válido. Si el autor hubiera elegido p+4 , el ejemplo sería correcto.

De cualquier manera, nunca he tenido ningún problema con el aplanamiento de matrices multidimensionales en C usando métodos similares.

También vea esta pregunta, tiene otra información útil: Acceso unidimensional a una matriz multidimensional: ¿C bien definida?

Sí. Es ilegal en C. De hecho, al hacerlo, le estás asignando a tu comstackdor. p apunta al elemento arr[2][2] (y es de puntero a tipo int ), es decir, tercer elemento de la tercera fila. La statement p=p+3; incrementará el puntero p a arr[2][5] , que es equivalente a arr[3][0] .
Pero esto fallará siempre que la memoria se asigne como una potencia de 2 ( 2 n ) en alguna architecture. Ahora, en este caso, la asignación de memoria se redondearía hasta 2 n , es decir, en su caso, cada fila redondearía hasta 64 bytes.
Vea un progtwig de prueba en el que la memoria asignada es de 5 asignaciones de 10 enteros. En algunas máquinas, las asignaciones de memoria son un múltiplo de 16 bytes, por lo que los 40 bytes solicitados se redondean a 48 bytes por asignación:

 #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"); } } 

Salida de muestra en 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 

Salida de muestra en Win7; GCC 4.8.1:

introduzca la descripción de la imagen aquí