Esse idioma sai naturalmente da alocação de array 1D. Vamos começar alocando uma matriz 1D de algum tipo arbitrário T
:
T *p = malloc( sizeof *p * N );
Simples, certo? A expressão *p
tem tipo T
, então sizeof *p
dá o mesmo resultado que sizeof (T)
, então estamos alocando espaço suficiente para um N
array -element de T
. Isso é verdade para qualquer tipoT
.
Agora, vamos substituir T
por um tipo de array como R [10]
. Então nossa alocação se torna
R (*p)[10] = malloc( sizeof *p * N);
A semântica aqui é exatamente a mesma do método de alocação 1D; tudo o que mudou foi o tipo de p
. Em vez de T *
, é agora R (*)[10]
. A expressão *p
tem tipo T
que é tipo R [10]
, então sizeof *p
é equivalente a sizeof (T)
que é equivalente a sizeof (R [10])
. Portanto, estamos alocando espaço suficiente para um array N
por 10
elemento de R
.
Podemos levar isso ainda mais longe, se quisermos; suponha que R
seja um tipo de array int [5]
. Substitua isso R
e teremos
int (*p)[10][5] = malloc( sizeof *p * N);
Mesma coisa - sizeof *p
é o mesmo que sizeof (int [10][5])
, e nós acabam alocando um pedaço contíguo de memória grande o suficiente para segurar uma N
por 10
pelo 5
conjunto de int
.
Então esse é o lado da alocação; e o lado do acesso?
Lembre-se de que a []
operação subscrito é definida em termos de aritmética de ponteiro: a[i]
é definido como *(a + i)
1 . Assim, o operador subscrito desreferencia []
implicitamente um ponteiro. Se p
for um ponteiro para T
, você pode acessar o valor apontado desreferenciando explicitamente com o *
operador unário :
T x = *p;
ou usando o []
operador subscrito:
T x = p[0]; // identical to *p
Assim, se p
apontar para o primeiro elemento de uma matriz , você pode acessar qualquer elemento dessa matriz usando um subscrito no ponteiro 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
Agora, vamos fazer nossa operação de substituição novamente e substituir T
pelo tipo de array 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];
Uma diferença imediatamente aparente; estamos desreferenciando explicitamente p
antes de aplicar o operador subscrito. Não queremos subscrever em p
, queremos subscrever em que p
aponta (neste caso, o array arr[0]
). Desde unário *
tem precedência menor do que o índice []
operador, temos que usar parênteses para explicitamente grupo p
com *
. Mas lembre-se de cima que *p
é o mesmo que p[0]
, então podemos substituir isso por
R x = (p[0])[i];
ou apenas
R x = p[0][i];
Assim, se p
apontar para uma matriz 2D, podemos indexar nessa matriz da seguinte p
forma:
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
Levando isso à mesma conclusão acima e substituindo 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];
Isso funciona da mesma forma se p
apontar para um array regular ou se apontar para a memória alocada malloc
.
Esse idioma tem os seguintes benefícios:
- É simples - apenas uma linha de código, em oposição ao método de alocação fragmentada
T **arr = malloc( sizeof *arr * N );
if ( arr )
{
for ( size_t i = 0; i < N; i++ )
{
arr[i] = malloc( sizeof *arr[i] * M );
}
}
- Todas as linhas da matriz alocada são * contíguas *, o que não é o caso com o método de alocação fragmentada acima;
- Desalocar o array é tão fácil com uma única chamada para
free
. Novamente, isso não é verdade com o método de alocação fragmentada, onde você precisa desalocar cada arr[i]
antes de poder desalocar arr
.
Às vezes, o método de alocação fragmentada é preferível, como quando seu heap está muito fragmentado e você não pode alocar sua memória como um bloco contíguo ou deseja alocar uma matriz "denteada" em que cada linha pode ter um comprimento diferente. Mas, em geral, esse é o melhor caminho a seguir.
1. Lembre-se de que os arrays não são ponteiros - em vez disso, as expressões de vetor são convertidas em expressões de ponteiro, conforme necessário.