Em C, strings não são um tipo de dado nativo como em linguagens de alto nível. Em vez disso, são representadas como arrays de caracteres (char[]) que terminam com um caractere especial: o terminador nulo ('\0'). Essa abordagem exige que o programador gerencie manualmente o espaço e o término da string, o que pode levar a erros se não for feito com cuidado.

Nesta aula, vamos explorar como declarar, inicializar e manipular strings em C, destacando a importância do terminador nulo e os cuidados necessários para evitar problemas como estouro de buffer e acesso inválido à memória.

Arrays de char

Uma string em C é armazenada como um array de caracteres do tipo char. Cada caractere ocupa um byte na memória. O array pode ser declarado com um tamanho fixo, e a string deve caber dentro desse espaço, incluindo o terminador nulo. Por exemplo, char nome[20]; declara um array que pode armazenar até 19 caracteres úteis mais o '\0'.

Os caracteres individuais podem ser acessados usando índices, começando em 0. É importante lembrar que o array é apenas um bloco de memória contíguo; não há verificação de limites em tempo de execução. Portanto, é responsabilidade do programador garantir que não se ultrapasse o tamanho declarado.

Exemplo de declaração e acesso:

#include <stdio.h>

int main() {
    char str[6] = {'O', 'l', 'a', '!', '\0'}; // array de 6 chars, mas usamos 5
    printf("%s\n", str);
    printf("Primeiro char: %c\n", str[0]);
    return 0;
}

Terminador nulo (\0)

O terminador nulo ('\0') é um caractere especial com valor ASCII 0 que marca o fim da string. Todas as funções da biblioteca padrão de C (como strlen, strcpy, printf com formato %s) dependem desse caractere para saber onde a string termina. Se ele estiver ausente, o comportamento é indefinido, podendo causar a leitura de memória além do array.

É crucial sempre reservar espaço para o terminador nulo ao declarar um array. Por exemplo, para armazenar a palavra "Casa" (4 letras), o array deve ter pelo menos 5 elementos: 4 para as letras e 1 para o '\0'.

Exemplo que demonstra a importância do terminador:

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

int main() {
    char sem_terminador[4] = {'T', 'e', 's', 't'}; // sem \0
    char com_terminador[5] = {'T', 'e', 's', 't', '\0'};
    
    printf("Sem terminador: %s\n", sem_terminador); // perigoso!
    printf("Com terminador: %s\n", com_terminador);
    
    // strlen pode retornar valor incorreto sem terminador
    printf("Tamanho (sem): %lu\n", strlen(sem_terminador));
    printf("Tamanho (com): %lu\n", strlen(com_terminador));
    return 0;
}

Inicialização

Existem várias formas de inicializar uma string em C. A mais comum é usando uma string literal entre aspas duplas, que já inclui o terminador nulo automaticamente. Exemplo: char str[] = "Hello"; cria um array de 6 caracteres (5 letras + '\0').

Outra forma é inicializar explicitamente cada caractere, como vimos anteriormente. Nesse caso, é necessário incluir manualmente o '\0'. Também é possível declarar um array de tamanho fixo e depois copiar uma string para ele usando strcpy, mas é preciso garantir que o tamanho seja suficiente.

Exemplos de inicialização:

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

int main() {
    // Inicialização com literal
    char s1[] = "C\n";
    
    // Inicialização explícita (com \0)
    char s2[4] = {'C', '\n', '!', '\0'};
    
    // Inicialização parcial: os elementos não especificados são zero (que é '\0')
    char s3[10] = "Oi"; // s3[0]='O', s3[1]='i', s3[2]='\0', resto zero
    
    printf("%s%s%s", s1, s2, s3);
    return 0;
}

Cuidados

Trabalhar com strings em C exige atenção redobrada para evitar erros comuns:

  • Estouro de buffer: ocorre quando se tenta armazenar mais caracteres do que o espaço reservado, incluindo o terminador. Funções como strcpy e scanf com %s não verificam limites. Use strncpy ou snprintf para limitar o número de caracteres.
  • Falta do terminador nulo: se o '\0' não for colocado, funções como printf e strlen podem ler memória além do array, causando comportamento imprevisível.
  • Tamanho insuficiente: ao declarar um array, lembre-se de incluir espaço para o terminador. Exemplo: para armazenar "abc", o array deve ter pelo menos 4 elementos.
  • Comparação de strings: não use o operador == para comparar strings, pois ele compara endereços de memória, não o conteúdo. Use strcmp.
  • Atribuição: não é possível atribuir uma string a um array após a declaração com = (exceto na inicialização). Use strcpy ou memcpy.

Exemplo de cuidados:

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

int main() {
    char buffer[5];
    // strcpy(buffer, "Hello!"); // estouro de buffer! (6 caracteres + \0)
    strncpy(buffer, "Hello!", sizeof(buffer)-1);
    buffer[sizeof(buffer)-1] = '\0'; // garante terminação
    printf("%s\n", buffer);
    
    // Comparação correta
    char str1[] = "abc";
    char str2[] = "abc";
    if (strcmp(str1, str2) == 0) {
        printf("Iguais\n");
    }
    return 0;
}

Boas práticas

Sempre inicialize strings com um valor conhecido, especialmente se forem globais ou estáticas. Use funções seguras como strncpy e snprintf em vez de suas versões sem limite. Considere usar a biblioteca string.h para manipulação segura. Ao ler entrada do usuário, evite scanf("%s") sem especificar largura; prefira fgets.

Exemplo de leitura segura:

#include <stdio.h>

int main() {
    char nome[50];
    printf("Digite seu nome: ");
    fgets(nome, sizeof(nome), stdin);
    // remove o \n se presente
    size_t len = strlen(nome);
    if (len > 0 && nome[len-1] == '\n') nome[len-1] = '\0';
    printf("Olá, %s!\n", nome);
    return 0;
}

Referências

Exercícios

  1. Crie um programa que declare uma string com seu nome e a imprima caractere por caractere (incluindo o terminador nulo) usando um loop.

    ✓ Resposta:
    #include <stdio.h>
    
    int main() {
        char nome[] = "João";
        for(int i = 0; i <= sizeof(nome)/sizeof(nome[0]); i++) {
            if(nome[i] == '\0')
                printf("\\0\n");
            else
                printf("%c\n", nome[i]);
        }
        return 0;
    }
  2. Explique por que o código abaixo está errado e corrija-o: char str[3] = "abc"; printf("%s", str);

    ✓ Resposta: O array tem tamanho 3, mas a string "abc" precisa de 4 caracteres (a, b, c, \0). Isso causa estouro de buffer. Corrigido: char str[4] = "abc"; ou char str[] = "abc";
  3. Escreva uma função que receba uma string e retorne seu comprimento sem usar strlen.

    ✓ Resposta:
    int meu_strlen(const char *s) {
        int len = 0;
        while(s[len] != '\0')
            len++;
        return len;
    }
  4. O que o seguinte trecho imprime? char s[] = {'a','b','c'}; printf("%s", s); Justifique.

    ✓ Resposta: O comportamento é indefinido porque o array não possui terminador nulo. O printf continuará lendo memória até encontrar um '\0' acidental, podendo imprimir lixo ou causar falha de segmentação.
  5. Usando fgets, leia uma linha do usuário e imprima seu comprimento (sem o \n).

    ✓ Resposta:
    #include <stdio.h>
    #include <string.h>
    
    int main() {
        char linha[100];
        printf("Digite algo: ");
        fgets(linha, sizeof(linha), stdin);
        linha[strcspn(linha, "\n")] = '\0'; // remove \n
        printf("Comprimento: %lu\n", strlen(linha));
        return 0;
    }