Este idioma cae naturalmente de la asignación de matrices 1D. Comencemos con la asignación de una matriz 1D de algún tipo arbitrario T
:
T *p = malloc( sizeof *p * N );
Simple, ¿verdad? La expresión *p
tiene tipo T
, por lo que sizeof *p
da el mismo resultado que sizeof (T)
, por lo que estamos asignando suficiente espacio para una N
matriz de elementos de T
. Esto es cierto para cualquier tipoT
.
Ahora, sustituyamos T
por un tipo de matriz como R [10]
. Entonces nuestra asignación se convierte en
R (*p)[10] = malloc( sizeof *p * N);
La semántica aquí es exactamente la misma que la del método de asignación 1D; todo lo que ha cambiado es el tipo de p
. En lugar de T *
, es ahora R (*)[10]
. La expresión *p
tiene tipo T
que es tipo R [10]
, por lo que sizeof *p
es equivalente a lo sizeof (T)
que es equivalente a sizeof (R [10])
. Así que estamos asignando suficiente espacio para una matriz N
por 10
elemento de R
.
Podemos llevar esto aún más lejos si queremos; supongamos que R
es en sí mismo un tipo de matriz int [5]
. Sustituye eso R
y obtenemos
int (*p)[10][5] = malloc( sizeof *p * N);
Lo mismo pasa - sizeof *p
es la misma que sizeof (int [10][5])
, y que terminan asignando un trozo contiguo de memoria lo suficientemente grande como para contener una N
por 10
por 5
variedad de int
.
Así que ese es el lado de la asignación; ¿qué pasa con el lado de acceso?
Recuerde que la []
operación de subíndice se define en términos de aritmética de punteros: a[i]
se define como *(a + i)
1 . Por tanto, el operador de subíndice desreferencia []
implícitamente un puntero. Si p
es un puntero a T
, puede acceder al valor apuntado ya sea desreferenciando explícitamente con el *
operador unario :
T x = *p;
o usando el []
operador de subíndice:
T x = p[0]; // identical to *p
Por lo tanto, si p
apunta al primer elemento de una matriz , puede acceder a cualquier elemento de esa matriz utilizando un subíndice en el puntero p
:
T arr[N];
T *p = arr; // expression arr "decays" from type T [N] to T *
...
T x = p[i]; // access the i'th element of arr through pointer p
Ahora, hagamos nuestra operación de sustitución nuevamente y reemplacemos T
con el tipo de matriz R [10]
:
R arr[N][10];
R (*p)[10] = arr; // expression arr "decays" from type R [N][10] to R (*)[10]
...
R x = (*p)[i];
Una diferencia inmediatamente aparente; estamos desreferenciando explícitamente p
antes de aplicar el operador de subíndice. No queremos subíndices en p
, queremos subíndices en lo que p
apunta (en este caso, la matriz arr[0]
). Dado que unario *
tiene menor precedencia que el []
operador de subíndice , tenemos que usar paréntesis para agrupar explícitamente p
con *
. Pero recuerde que desde arriba *p
es lo mismo que p[0]
, por lo que podemos sustituirlo por
R x = (p[0])[i];
o solo
R x = p[0][i];
Por lo tanto, si p
apunta a una matriz 2D, podemos indexar en esa matriz de la siguiente p
manera:
R x = p[i][j]; // access the i'th element of arr through pointer p;
// each arr[i] is a 10-element array of R
Llevando esto a la misma conclusión anterior y sustituyéndolo R
por int [5]
:
int arr[N][10][5];
int (*p)[10][5]; // expression arr "decays" from type int [N][5][10] to int (*)[10][5]
...
int x = p[i][j][k];
Esto funciona igual si p
apunta a una matriz regular o si apunta a la memoria asignada malloc
.
Este modismo tiene los siguientes beneficios:
- Es simple: solo una línea de código, a diferencia del método de asignación por partes
T **arr = malloc( sizeof *arr * N );
if ( arr )
{
for ( size_t i = 0; i < N; i++ )
{
arr[i] = malloc( sizeof *arr[i] * M );
}
}
- Todas las filas de la matriz asignada son * contiguas *, lo que no es el caso con el método de asignación por partes anterior;
- Desasignar la matriz es igual de fácil con una sola llamada a
free
. Nuevamente, no es cierto con el método de asignación por partes, donde debe desasignar cada uno arr[i]
antes de poder desasignar arr
.
A veces, es preferible el método de asignación por partes, como cuando su montón está muy fragmentado y no puede asignar su memoria como un fragmento contiguo, o desea asignar una matriz "irregular" donde cada fila puede tener una longitud diferente. Pero en general, esta es la mejor manera de hacerlo.
1. Recuerde que las matrices no son punteros; en cambio, las expresiones de matriz se convierten en expresiones de puntero según sea necesario.