Publicidade

Arrays Multidimensionais em C

Layout row-major, passagem para funções, alocação dinâmica, exemplos com matrizes e imagens. Continua a aula básica de arrays.

💪 Não pegou de primeira? Totalmente normal.
As quatro formas de passar um array multidimensional confundem todo mundo. Só aprenda primeiro o Padrão A (bloco contíguo) e você fica bem.
Como estudar de novo
  1. Revise arrays 1D e ponteiros
  2. Grave a imagem da memória "row-major" na cabeça
  3. Escolha uma forma de passar arrays e fique com ela (array plano + cols é o mais fácil)
  4. Pule otimização de performance — acerte primeiro
💡 Dica: int a[3][4] é na verdade só 12 ints contíguos. A parte "2D" é como você pensa neles.

Declaração / inicialização 2D e 3D

Array 2D

// 3 linhas x 4 colunas
int m[3][4] = {
    { 1,  2,  3,  4},
    { 5,  6,  7,  8},
    { 9, 10, 11, 12}
};

printf("%d\n", m[1][2]);  // → 7 (linha 1, coluna 2, indexado a partir de 0)

Array 3D

// 2 camadas x 3 linhas x 4 colunas
int cube[2][3][4];

for (int l = 0; l < 2; l++)
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 4; j++)
            cube[l][i][j] = l * 100 + i * 10 + j;
Inicializadores abreviados: int m[3][4] = {0}; preenche tudo com zero. Com int m[][4] = {{1,2,3,4},{5,6,7,8}};, só o tamanho externo pode ser omitido — a contagem de colunas ainda é obrigatória.

Camadas do sizeof

int m[3][4];
sizeof(m)        // 48  (3 * 4 * 4 bytes)
sizeof(m[0])     // 16  (uma linha = 4 * 4 bytes)
sizeof(m[0][0])  // 4   (um int)
// rows = sizeof(m)    / sizeof(m[0])
// cols = sizeof(m[0]) / sizeof(m[0][0])

Layout de memória row-major

Em C, um array 2D é armazenado linha por linha na memória — isso se chama ordem row-major. Fortran e MATLAB usam column-major, então não confunda os dois.

Visão lógica (3×4)

1
2
3
4
5
6
7
8
9
10
11
12

Memória real (12 ints contíguos)

1
2
3
4
5
6
7
8
9
10
11
12

Fórmula do índice

m[i][j] equivale a: *((int*)m + i * COLS + j)
ou: int *p = (int *)m; p[i*COLS + j]
// Percorre um array 2D como se fosse 1D
int m[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int *p = (int *)m;
for (int k = 0; k < 12; k++)
    printf("%d ", p[k]);
// → 1 2 3 4 5 6 7 8 9 10 11 12
Dica de performance: num laço duplo, coloque as linhas no laço externo e colunas no interno. Isso bate com o armazenamento row-major e é cache-friendly. Inverter os laços pode ser significativamente mais lento em arrays grandes.

Quatro formas de passar para uma função

Passar arrays multidimensionais é um dos cantos mais confusos de C. Aqui estão suas quatro opções.

① Fixar a contagem de colunas no tipo (disponível desde C89)

// contagem de colunas eh obrigatoria; linhas pode ser omitido
void print_34(int m[][4], int rows) {
    for (int i = 0; i < rows; i++)
        for (int j = 0; j < 4; j++)
            printf("%d ", m[i][j]);
}

int a[3][4];
print_34(a, 3);
Prós: direto. Contras: só aceita arrays com exatamente quatro colunas.

② Parâmetro VLA — C99+

// Declare linhas/colunas primeiro, depois aceite o array VLA
void print_any(int rows, int cols, int m[rows][cols]) {
    for (int i = 0; i < rows; i++)
        for (int j = 0; j < cols; j++)
            printf("%d ", m[i][j]);
}

int a[3][4], b[2][5];
print_any(3, 4, a);
print_any(2, 5, b);
Prós: qualquer tamanho, e você ainda tem a sintaxe m[i][j]. Contras: requer C99+; MSVC mais antigo não suporta VLAs.

③ Array plano + indexação manual

// "Achate" para 1D e calcule o indice voce mesmo.
void print_flat(int *m, int rows, int cols) {
    for (int i = 0; i < rows; i++)
        for (int j = 0; j < cols; j++)
            printf("%d ", m[i * cols + j]);
}

int a[3][4];
print_flat((int *)a, 3, 4);
Prós: funciona em qualquer lugar e combina bem com malloc. Contras: você perde a sintaxe m[i][j].

④ Ponteiro para ponteiro (int **) — completamente diferente!

// Passa um array de ponteiros de linha.
void print_pp(int **m, int rows, int cols) {
    for (int i = 0; i < rows; i++)
        for (int j = 0; j < cols; j++)
            printf("%d ", m[i][j]);
}

// O chamador precisa montar um array de ponteiros de linha primeiro.
int row0[] = {1,2,3};
int row1[] = {4,5,6};
int *rows[] = {row0, row1};
print_pp(rows, 2, 3);
Crítico: fazer cast de int a[3][4] para int ** NÃO funciona. Seus layouts de memória são completamente diferentes — um é um bloco de ints, o outro é um array de ponteiros. Essa é a maior fonte de confusão com arrays multidimensionais.

Dois padrões de alocação dinâmica

Quando o tamanho só é conhecido em tempo de execução, use malloc. Há dois padrões comuns.

Padrão A: bloco contíguo (preferido)

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int rows = 3, cols = 4;

    // aloca todos os elementos num unico bloco
    int *m = malloc(sizeof(int) * rows * cols);
    if (!m) return 1;

    // indexacao manual: m[i*cols + j]
    for (int i = 0; i < rows; i++)
        for (int j = 0; j < cols; j++)
            m[i * cols + j] = i * 10 + j;

    free(m);
    return 0;
}
Vantagens: uma única chamada a malloc, sem vazamentos, e memória contígua significa cache-friendly.

Padrão A com ponteiro VLA para sintaxe m[i][j]

// Bloco contiguo E indexacao 2D.
int (*m)[cols] = malloc(sizeof(int[rows][cols]));
m[1][2] = 42;   // indices normais funcionam
free(m);

Padrão B: array de ponteiros de linha (int **)

int **m = malloc(sizeof(int *) * rows);    // array de ponteiros de linha
for (int i = 0; i < rows; i++)
    m[i] = malloc(sizeof(int) * cols);       // uma linha cada

m[1][2] = 42;   // sintaxe m[i][j] funciona

// libere em ordem reversa
for (int i = 0; i < rows; i++) free(m[i]);
free(m);
Desvantagens: rows + 1 chamadas a malloc (mais lento e fragmenta memória), e as linhas acabam espalhadas na RAM (pior comportamento de cache). Prefira o Padrão A a menos que você realmente precise da estrutura irregular.

Exemplo: produto de matrizes

Calcula C = A × B onde A é m×k e B é k×n. C[i][j] = Σ A[i][l] * B[l][j].
#include <stdio.h>

#define M 2
#define K 3
#define N 2

void matmul(int A[M][K], int B[K][N], int C[M][N]) {
    for (int i = 0; i < M; i++)
        for (int j = 0; j < N; j++) {
            int sum = 0;
            for (int l = 0; l < K; l++)
                sum += A[i][l] * B[l][j];
            C[i][j] = sum;
        }
}

int main(void) {
    int A[M][K] = {{1,2,3},{4,5,6}};
    int B[K][N] = {{7,8},{9,10},{11,12}};
    int C[M][N];
    matmul(A, B, C);

    for (int i = 0; i < M; i++) {
        for (int j = 0; j < N; j++)
            printf("%4d", C[i][j]);
        putchar('\n');
    }
    // esperado:
    //   58  64
    //  139 154
}
A ordem dos laços importa: mudar para a ordem ilj (acumulando l no laço mais interno) percorre B linha a linha, o que pode ser significativamente mais rápido em matrizes grandes graças ao cache row-major.

Exemplo: espelhamento horizontal (escala de cinza)

Trate a imagem como um array 2D e espelhe cada linha.
#include <stdio.h>

#define W 6
#define H 4

void flip_horizontal(unsigned char img[H][W]) {
    for (int i = 0; i < H; i++)
        for (int j = 0; j < W / 2; j++) {
            unsigned char t = img[i][j];
            img[i][j] = img[i][W - 1 - j];
            img[i][W - 1 - j] = t;
        }
}

int main(void) {
    unsigned char img[H][W] = {
        {10,20,30,40,50,60},
        {11,21,31,41,51,61},
        {12,22,32,42,52,62},
        {13,23,33,43,53,63},
    };
    flip_horizontal(img);
    for (int i = 0; i < H; i++) {
        for (int j = 0; j < W; j++) printf("%3d ", img[i][j]);
        putchar('\n');
    }
}
saída: 60 50 40 30 20 10 61 51 41 31 21 11 62 52 42 32 22 12 63 53 43 33 23 13
Extensões: para RGB, use um array 3D como unsigned char img[H][W][3]. Para espelhamento vertical, troque linhas inteiras com memcpy.

Desafios

Desafio 1: transposição
Escreva uma função que calcule a transposta int B[4][3] de int A[3][4].
Desafio 2: tabuada
Preencha int table[9][9] com (i+1)*(j+1) e imprima.
Desafio 3: soma de matrizes dinâmica
Leia linhas/colunas de argumentos de linha de comando, aloque duas matrizes contíguas (Padrão A), preencha com números aleatórios e imprima a soma.
Desafio 4: espelhamento vertical
Reescreva o exemplo de espelhamento horizontal para espelhar verticalmente. Depois escreva outra versão que use memcpy para trocar linhas inteiras de uma vez.
Desafio 5: traço
Implemente int trace(int n, int m[n][n]) (usando parâmetro VLA) que retorna a soma das entradas da diagonal.

Veja também

Teste de Revisão

Teste seu entendimento desta aula!

Q1. Como int a[3][4]; é disposto na memória?

Row-major: 12 ints contíguos
Column-major
Cada elemento numa região separada

C usa layout row-major. A ordem é a[0][0], a[0][1], ..., a[0][3], a[1][0], ....

Q2. Qual o parâmetro formal correto ao passar um array 2D para uma função?

void f(int a[][4])
void f(int a[][])
void f(int **a)

Você precisa especificar pelo menos a segunda dimensão. int ** é um array de ponteiros — um tipo diferente de um array 2D.

Q3. Qual expressão de aritmética de ponteiros equivale a a[i][j]?

*(*(a+i)+j)
*(a+i*j)
*(a+i)+j

a+i dá o endereço da linha i, +j seleciona a coluna j, e * desreferencia para o valor.