Em C, strings não são um tipo de dado nativo como em linguagens de alto nível. Em vez disso, uma string é representada como um array de caracteres (char) que termina com um caractere especial chamado terminador nulo ('\0'). Este modelo oferece controle total sobre a memória, mas exige atenção redobrada para evitar erros.

Nesta aula, exploraremos como declarar, inicializar e manipular strings, destacando os cuidados essenciais para escrever código seguro e eficiente.

Arrays de char

Uma string em C é essencialmente um array unidimensional de caracteres (char). Cada caractere ocupa um byte e é armazenado em posições consecutivas de memória. O final da string é marcado pelo caractere nulo ('\0'), que tem valor ASCII 0. Por exemplo, a string "Olá" é armazenada como:

char str[4] = {'O', 'l', 'á', '\0'};

O array deve ter espaço suficiente para todos os caracteres da string mais o terminador nulo. Se você declarar char nome[10], pode armazenar até 9 caracteres úteis, pois o 10º será o '\0'. É importante lembrar que strings em C são mutáveis (os caracteres individuais podem ser alterados), ao contrário de strings literais que são constantes.

Ao acessar um caractere, usamos índice como em qualquer array: str[0] é o primeiro caractere. A função strlen() da biblioteca <string.h> retorna o comprimento da string sem contar o terminador nulo.

Terminador nulo (\0)

O terminador nulo é um caractere com código ASCII 0, representado pela sequência de escape '\0'. Ele é crucial porque todas as funções de manipulação de strings (como strcpy, strcat, printf com %s) dependem dele para saber onde a string termina. Sem o terminador nulo, essas funções continuariam lendo memória além do array, causando comportamento indefinido e potenciais falhas de segurança.

Quando você declara uma string literal como "Hello", o compilador automaticamente adiciona o '\0' ao final. Por exemplo, "Hello" ocupa 6 bytes: 'H','e','l','l','o','\0'. Ao criar um array manualmente, você deve garantir que o terminador esteja presente.

Exemplo de perigo: se você copiar uma string para um array que não tem espaço para o '\0', a string resultante não será terminada corretamente.

char destino[5];
strcpy(destino, "Hello"); // ERRO! "Hello" precisa de 6 bytes (5 letras + '\0')

Isso causa estouro de buffer, um grave problema de segurança.

Inicialização

Existem várias formas de inicializar uma string em C:

1. Inicialização com string literal:

char str1[] = "Hello";       // tamanho automático: 6
char str2[6] = "Hello";     // tamanho explícito
char str3[10] = "Hello";    // espaço extra preenchido com zeros

2. Inicialização como array de caracteres:

char str4[] = {'H', 'e', 'l', 'l', 'o', '\0'}; // necessário incluir '\0'

3. Inicialização parcial (restante é zero):

char str5[10] = {'H', 'e', 'l'}; // posições 0,1,2 definidas; o resto é zero ('\0')

É importante notar que ao usar a sintaxe com chaves, você deve incluir explicitamente o '\0' se quiser que a string seja terminada. Caso contrário, o array não será uma string válida.

Também é possível usar ponteiros para strings literais, mas isso cria uma string constante (imutável) e o ponteiro aponta para uma área de memória somente leitura:

char *str6 = "Hello"; // string literal constante
// str6[0] = 'h'; // ERRO! comportamento indefinido (tentativa de modificar constante)

Cuidados

Trabalhar com strings em C exige atenção a diversos detalhes para evitar erros:

  • Estouro de buffer: Sempre garanta que o array de destino tenha tamanho suficiente para armazenar a string incluindo o '\0'. Use funções seguras como strncpy ou snprintf quando possível.
  • Terminador nulo: Nunca esqueça de incluir o '\0' ao construir strings manualmente. Funções como strcat e strcpy esperam que a origem tenha terminador.
  • Strings constantes: Não tente modificar o conteúdo de uma string literal através de um ponteiro. Isso causa comportamento indefinido.
  • Funções inseguras: Evite usar gets() (removido do padrão C11) e prefira fgets() para ler strings do teclado.
  • Comparação: Não use == para comparar strings; use strcmp().
  • Retorno de strings de funções: Cuidado ao retornar ponteiros para arrays locais (que deixam de existir após a função). Prefira alocar memória dinamicamente ou passar um buffer como parâmetro.

Exemplo de código que ilustra alguns cuidados:

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

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

Boas Práticas

Sempre inicialize arrays de caracteres com \0 para evitar lixo de memória. Use constantes para definir tamanhos máximos. Prefira funções que limitam o número de caracteres copiados, como strncpy (mas lembre-se de que ela não adiciona o terminador nulo se o limite for atingido).

Referências

Exercícios

  1. Escreva 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[] = "Maria";
        for (int i = 0; i <= sizeof(nome)/sizeof(nome[0]) - 1; i++) {
            printf("Caractere %d: '%c' (ASCII %d)\n", i, nome[i], nome[i]);
        }
        return 0;
    }
  2. O que acontece se você declarar char str[5] = "Hello";? Explique.

    ✓ Resposta: Isso é um erro de compilação porque a string literal "Hello" requer 6 bytes (5 letras + '\0'), mas o array tem apenas 5. O compilador rejeitará ou emitirá um aviso, e o comportamento é indefinido. Em alguns compiladores, pode compilar, mas o terminador nulo não será armazenado, causando problemas.
  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. Qual a diferença entre char s[] = "abc"; e char *s = "abc";?

    ✓ Resposta: A primeira declara um array mutável de 4 caracteres (incluindo '\0') na pilha. A segunda declara um ponteiro para uma string literal constante, que geralmente fica em uma área de memória somente leitura. Tentar modificar o conteúdo através do ponteiro causa comportamento indefinido. O array pode ser modificado, o ponteiro não.
  5. Escreva um programa que leia uma linha de texto (até 100 caracteres) e a imprima de trás para frente.

    ✓ Resposta:
    #include <stdio.h>
    #include <string.h>
    
    int main() {
        char linha[101];
        printf("Digite uma linha: ");
        fgets(linha, sizeof(linha), stdin);
        // Remove newline
        size_t len = strlen(linha);
        if (len > 0 && linha[len-1] == '\n') {
            linha[len-1] = '\0';
            len--;
        }
        for (int i = len - 1; i >= 0; i--) {
            putchar(linha[i]);
        }
        putchar('\n');
        return 0;
    }