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 *pdá o mesmo resultado que sizeof (T), então estamos alocando espaço suficiente para um Narray -element de T. Isso é verdade para qualquer tipoT .
Agora, vamos substituir Tpor 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 *ptem tipo Tque é 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 Npor 10elemento de R.
Podemos levar isso ainda mais longe, se quisermos; suponha que Rseja um tipo de array int [5]. Substitua isso Re 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 Npor 10pelo 5conjunto 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 pfor 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 papontar 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 Tpelo 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 pantes 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 pcom *. 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 papontar para uma matriz 2D, podemos indexar nessa matriz da seguinte pforma:
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 Rpor 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 papontar 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.