Arrays de ponteiros são uma das construções mais versáteis e poderosas da linguagem C. Eles permitem armazenar e manipular coleções de dados de forma flexível, especialmente quando os elementos têm tamanho variável, como strings. Nesta aula, vamos aprofundar o conceito de arrays de ponteiros, com foco em char*[] (arrays de strings), na interface argv do main, na iteração sobre esses arrays e nos principais casos de uso no dia a dia do programador C.

Dominar arrays de ponteiros é essencial para entender como C lida com memória e ponteiros em um nível mais alto. Veremos como declarar, inicializar, percorrer e utilizar essas estruturas, além de discutir boas práticas para evitar erros comuns como vazamentos de memória e acesso indevido.

Array de strings (char*[])

Em C, uma string é um array de caracteres terminado em '\0'. Um array de strings é, portanto, um array de ponteiros para caracteres (char*). Cada elemento do array aponta para o primeiro caractere de uma string. Essa estrutura é extremamente útil para armazenar listas de nomes, mensagens, opções de configuração, etc.

A declaração típica é:

char *nomes[] = {"Ana", "Bruno", "Carla", "Diego"};

Aqui, nomes é um array de 4 ponteiros, cada um apontando para uma string literal. É importante notar que as strings literais são constantes, então tentar modificá-las pode causar um comportamento indefinido. Para strings modificáveis, usamos arrays de caracteres, mas para listas de strings que não serão alteradas, o padrão acima é eficiente e conciso.

Outra forma de declarar é sem inicialização, para preencher dinamicamente:

char *frutas[3];
frutas[0] = "Maçã";
frutas[1] = "Banana";
frutas[2] = "Laranja";

O tamanho do array deve ser conhecido em tempo de compilação, a menos que usemos alocação dinâmica (com malloc). Veremos isso mais adiante.

argv

A função main em C pode receber argumentos da linha de comando. A assinatura padrão é:

int main(int argc, char *argv[])

Onde argc é o número de argumentos (incluindo o nome do programa) e argv é um array de ponteiros para strings, cada uma contendo um argumento. argv[0] é o nome do programa, argv[1] é o primeiro argumento, e assim por diante. O último elemento é NULL (embora a convenção seja usar argc para saber o tamanho).

Exemplo de uso:

#include <stdio.h>

int main(int argc, char *argv[]) {
    printf("Número de argumentos: %d\n", argc);
    for (int i = 0; i < argc; i++) {
        printf("argv[%d] = %s\n", i, argv[i]);
    }
    return 0;
}

Se compilarmos e executarmos ./programa teste 123, a saída será:

Número de argumentos: 3
argv[0] = ./programa
argv[1] = teste
argv[2] = 123

É comum usar argv para passar opções e parâmetros a um programa, como flags (-v, -o arquivo). A análise desses argumentos geralmente é feita com loops e funções como strcmp.

Iteração

Iterar sobre um array de ponteiros é simples: usamos um laço for ou while percorrendo os índices, ou um ponteiro para o início do array, avançando até encontrar NULL (se o array for terminado por NULL).

Exemplo com índice:

char *cores[] = {"Vermelho", "Verde", "Azul", NULL};
for (int i = 0; cores[i] != NULL; i++) {
    printf("%s\n", cores[i]);
}

Exemplo com ponteiro:

char **p = cores;
while (*p != NULL) {
    printf("%s\n", *p);
    p++;
}

No caso de argv, podemos iterar até i < argc ou até argv[i] != NULL (mas o padrão é usar argc).

Iterar com ponteiro é eficiente e mostra a relação entre arrays e ponteiros em C.

Casos de uso

Arrays de ponteiros são usados em muitas situações práticas:

  • Listas de strings constantes: como nomes de meses, dias da semana, mensagens de erro.
  • Argumentos da linha de comando: já vimos argv.
  • Tabelas de lookup: por exemplo, uma lista de nomes e seus códigos.
  • Manipulação de múltiplas strings: ordenar, filtrar, etc.
  • Implementação de estruturas de dados: como um array de ponteiros para funções (embora não seja string).

Um exemplo prático: um programa que imprime os dias da semana:

#include <stdio.h>

int main() {
    char *dias[] = {"Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sábado", "Domingo"};
    int n = sizeof(dias) / sizeof(dias[0]);
    for (int i = 0; i < n; i++) {
        printf("Dia %d: %s\n", i+1, dias[i]);
    }
    return 0;
}

Outro caso: um mini interpretador de comandos que compara a entrada com uma lista:

#include <stdio.h>
#include <string.h>

int main() {
    char *comandos[] = {"help", "quit", "version"};
    char entrada[20];
    printf("Digite um comando: ");
    scanf("%s", entrada);
    for (int i = 0; i < 3; i++) {
        if (strcmp(entrada, comandos[i]) == 0) {
            printf("Comando reconhecido: %s\n", comandos[i]);
            return 0;
        }
    }
    printf("Comando desconhecido!\n");
    return 1;
}

Boas práticas e observações finais

Ao trabalhar com arrays de ponteiros, lembre-se de:

  • Não modificar strings literais (elas são armazenadas em região somente leitura).
  • Garantir que o array tenha um tamanho definido ou um terminador NULL para evitar estouros.
  • Quando alocar dinamicamente, liberar a memória com free.
  • Preferir const char * quando as strings não serão alteradas, para melhor documentação e segurança.
  • Usar sizeof(arr)/sizeof(arr[0]) para obter o número de elementos quando o tamanho é conhecido em tempo de compilação.

Essas práticas ajudam a evitar erros comuns e tornam o código mais robusto.

Referências

Exercícios

  1. Escreva um programa que declare um array de ponteiros para strings com os nomes de 5 cidades e imprima cada uma em uma linha, usando um loop for.

    ✓ Resposta:
    #include <stdio.h>
    
    int main() {
        char *cidades[] = {"São Paulo", "Rio de Janeiro", "Belo Horizonte", "Salvador", "Fortaleza"};
        int n = sizeof(cidades) / sizeof(cidades[0]);
        for (int i = 0; i < n; i++) {
            printf("%s\n", cidades[i]);
        }
        return 0;
    }
  2. Implemente um programa que receba argumentos pela linha de comando e imprima apenas os argumentos que começam com a letra 'a' (use argv e argc).

    ✓ Resposta:
    #include <stdio.h>
    #include <string.h>
    
    int main(int argc, char *argv[]) {
        for (int i = 1; i < argc; i++) {
            if (argv[i][0] == 'a') {
                printf("%s\n", argv[i]);
            }
        }
        return 0;
    }
  3. Crie um array de ponteiros para strings que represente os meses do ano e imprima o mês correspondente a um número digitado pelo usuário (1-12).

    ✓ Resposta:
    #include <stdio.h>
    
    int main() {
        char *meses[] = {"Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho",
                         "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"};
        int num;
        printf("Digite um número de 1 a 12: ");
        scanf("%d", &num);
        if (num >= 1 && num <= 12) {
            printf("%s\n", meses[num-1]);
        } else {
            printf("Número inválido!\n");
        }
        return 0;
    }
  4. Escreva uma função que receba um array de ponteiros para strings e o tamanho, e retorne o comprimento total de todas as strings somadas (usando strlen).

    ✓ Resposta:
    #include <stdio.h>
    #include <string.h>
    
    int total_length(char *arr[], int n) {
        int total = 0;
        for (int i = 0; i < n; i++) {
            total += strlen(arr[i]);
        }
        return total;
    }
    
    int main() {
        char *palavras[] = {"Casa", "Carro", "Moto"};
        int n = sizeof(palavras) / sizeof(palavras[0]);
        printf("Comprimento total: %d\n", total_length(palavras, n));
        return 0;
    }
  5. Implemente um programa que copie os argumentos da linha de comando para um novo array de ponteiros (alocado dinamicamente) e depois imprima os argumentos em ordem reversa.

    ✓ Resposta:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(int argc, char *argv[]) {
        char **copia = malloc(argc * sizeof(char *));
        if (copia == NULL) {
            fprintf(stderr, "Erro de alocação\n");
            return 1;
        }
        for (int i = 0; i < argc; i++) {
            copia[i] = malloc(strlen(argv[i]) + 1);
            if (copia[i] == NULL) {
                fprintf(stderr, "Erro de alocação\n");
                return 1;
            }
            strcpy(copia[i], argv[i]);
        }
        // Imprime em ordem reversa
        for (int i = argc - 1; i >= 0; i--) {
            printf("%s\n", copia[i]);
        }
        // Libera memória
        for (int i = 0; i < argc; i++) {
            free(copia[i]);
        }
        free(copia);
        return 0;
    }