Arrays multidimensionais em C permitem armazenar dados em estruturas com duas ou mais dimensões, como tabelas (linhas e colunas). Eles são amplamente utilizados em computação gráfica, simulações e processamento de dados. Nesta aula, vamos explorar como declarar, acessar e manipular esses arrays, além de entender como eles são organizados na memória e como passá-los para funções.

Dominar arrays multidimensionais é essencial para trabalhar com dados tabulares e matrizes em problemas científicos e de engenharia. Veremos desde conceitos básicos até técnicas avançadas de passagem para funções.

Matrizes

Uma matriz é um array bidimensional, ou seja, um array de arrays. Em C, declaramos uma matriz especificando o número de linhas e colunas entre colchetes. Por exemplo, int matriz[3][4]; cria uma matriz com 3 linhas e 4 colunas, totalizando 12 elementos do tipo int.

A inicialização pode ser feita na declaração, usando chaves aninhadas. Exemplo: int mat[2][3] = {{1,2,3}, {4,5,6}}; atribui valores linha por linha. Se houver menos valores que o tamanho, os elementos restantes são inicializados com zero.

#include <stdio.h>

int main() {
    int matriz[2][3] = {{1, 2, 3}, {4, 5, 6}};
    // Acessando e imprimindo
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", matriz[i][j]);
        }
        printf("\n");
    }
    return 0;
}

É possível declarar arrays com mais de duas dimensões, como int cubo[3][4][5]; (3 faces, 4 linhas, 5 colunas). O padrão é similar: cada dimensão adicional adiciona um nível de aninhamento.

Acesso

Para acessar um elemento de uma matriz, usamos índices separados por colchetes: matriz[linha][coluna]. Os índices começam em 0. No exemplo anterior, matriz[1][2] acessa o elemento da segunda linha (índice 1) e terceira coluna (índice 2), que é 6.

É importante não ultrapassar os limites da matriz, pois C não faz verificação de limites, podendo causar comportamentos indefinidos. O acesso é feito por aritmética de ponteiros: matriz[i][j] é equivalente a *(*(matriz + i) + j).

#include <stdio.h>

int main() {
    int mat[2][3] = {{10, 20, 30}, {40, 50, 60}};
    int *ptr = &mat[0][0];
    printf("Elemento [1][2] via índice: %d\n", mat[1][2]);
    printf("Elemento [1][2] via ponteiro: %d\n", *(ptr + 1*3 + 2));
    return 0;
}

Note que o cálculo do deslocamento usa o número de colunas (3) porque a memória é organizada em ordem row-major.

Memória (row-major)

Em C, arrays multidimensionais são armazenados em ordem row-major, ou seja, os elementos de cada linha são armazenados consecutivamente na memória. Por exemplo, uma matriz 2×3 é armazenada como: [0][0], [0][1], [0][2], [1][0], [1][1], [1][2].

Isso significa que a última dimensão varia mais rapidamente. Conhecer a organização é útil para otimizar loops: percorrer os elementos na ordem de armazenamento melhora a localidade de referência e o desempenho.

#include <stdio.h>

int main() {
    int mat[2][3] = {{1,2,3}, {4,5,6}};
    int *p = &mat[0][0];
    printf("Endereços (row-major):\n");
    for (int i = 0; i < 2*3; i++) {
        printf("%p: %d\n", (void*)&p[i], p[i]);
    }
    return 0;
}

Isso contrasta com linguagens como Fortran, que usam column-major. Em C, loops aninhados com o índice da coluna no loop interno são mais eficientes.

Passagem para funções

Passar arrays multidimensionais para funções requer cuidado. O parâmetro deve especificar o número de colunas (a segunda dimensão) porque o compilador precisa saber o tamanho de cada linha para calcular endereços. A primeira dimensão pode ser omitida.

#include <stdio.h>

void imprimirMatriz(int linhas, int colunas, int mat[][colunas]) {
    for (int i = 0; i < linhas; i++) {
        for (int j = 0; j < colunas; j++) {
            printf("%d ", mat[i][j]);
        }
        printf("\n");
    }
}

int main() {
    int mat[2][3] = {{1,2,3}, {4,5,6}};
    imprimirMatriz(2, 3, mat);
    return 0;
}

Outra forma é usar ponteiro para array de tamanho fixo: void func(int (*mat)[3]). Isso só funciona se o número de colunas for conhecido em tempo de compilação.

Para arrays com mais de duas dimensões, é necessário especificar todas as dimensões, exceto a primeira. Exemplo: void func(int arr[][4][5]).

Boas práticas

  • Sempre verifique os limites dos índices para evitar estouro de buffer.
  • Prefira loops aninhados com o índice da coluna no loop interno para melhor desempenho devido à localidade de referência.
  • Ao passar para funções, use VLA (Variable Length Arrays) como parâmetro, disponível a partir do C99, para maior flexibilidade.
  • Evite arrays multidimensionais muito grandes na pilha (stack); prefira alocação dinâmica.

Referências

Exercícios

  1. Declare uma matriz 3x3 de inteiros chamada identidade e inicialize-a como uma matriz identidade (1 na diagonal principal, 0 nos demais). Em seguida, imprima a matriz.
  2. ✓ Resposta:
    #include <stdio.h>
    
    int main() {
        int identidade[3][3] = {{1,0,0}, {0,1,0}, {0,0,1}};
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                printf("%d ", identidade[i][j]);
            }
            printf("\n");
        }
        return 0;
    }
  3. Escreva uma função somaMatrizes que recebe duas matrizes 2x3 e retorna a soma (elemento a elemento) em uma terceira matriz. Teste no main.
  4. ✓ Resposta:
    #include <stdio.h>
    
    void somaMatrizes(int a[][3], int b[][3], int resultado[][3], int linhas) {
        for (int i = 0; i < linhas; i++) {
            for (int j = 0; j < 3; j++) {
                resultado[i][j] = a[i][j] + b[i][j];
            }
        }
    }
    
    int main() {
        int A[2][3] = {{1,2,3}, {4,5,6}};
        int B[2][3] = {{7,8,9}, {10,11,12}};
        int C[2][3];
        somaMatrizes(A, B, C, 2);
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 3; j++) {
                printf("%d ", C[i][j]);
            }
            printf("\n");
        }
        return 0;
    }
  5. Explique por que o loop abaixo é ineficiente para uma matriz 1000x1000 e reescreva-o de forma otimizada. Código original: for(j=0; j<1000; j++) for(i=0; i<1000; i++) mat[i][j] = 0;
  6. ✓ Resposta:O loop original percorre a matriz por colunas (j externo, i interno), o que não segue a ordem row-major, causando muitos cache misses. A versão otimizada percorre por linhas (i externo, j interno):
    for(i=0; i<1000; i++) for(j=0; j<1000; j++) mat[i][j] = 0;
  7. Dada a declaração int arr[3][4] = {0};, escreva uma expressão usando aritmética de ponteiros que acesse o elemento na linha 2, coluna 3.
  8. ✓ Resposta:*(*(arr + 2) + 3) ou *(&arr[0][0] + 2*4 + 3).
  9. Crie um array tridimensional de inteiros com dimensões 2x3x4, inicialize todos os elementos com o valor 1 e imprima todos os elementos.
  10. ✓ Resposta:
    #include <stdio.h>
    
    int main() {
        int arr[2][3][4];
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 3; j++) {
                for (int k = 0; k < 4; k++) {
                    arr[i][j][k] = 1;
                }
            }
        }
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 3; j++) {
                for (int k = 0; k < 4; k++) {
                    printf("%d ", arr[i][j][k]);
                }
                printf("\n");
            }
            printf("\n");
        }
        return 0;
    }