Strings em C: arrays de char e o terminador nulo
1. Conceitos Fundamentais: O que é uma String em C?
Em C, não existe um tipo de dado nativo chamado "string". O que chamamos de string é, na verdade, um array de caracteres (char) que termina obrigatoriamente com um caractere especial: o terminador nulo ('\0'). Esse caractere tem valor numérico zero e serve como marcador de fim da string.
A diferença crucial entre um array de char genérico e uma string é justamente a presença do '\0'. Um array de caracteres pode conter qualquer sequência de bytes, mas só é considerado uma string se for terminado por nulo.
// Array de char genérico (NÃO é uma string)
char nao_string[] = {'a', 'b', 'c'};
// String propriamente dita
char string_valida[] = {'a', 'b', 'c', '\0'};
2. Declaração e Inicialização de Strings
Existem várias formas de declarar e inicializar strings em C. A mais comum é usando literais entre aspas duplas:
#include <stdio.h>
int main() {
// Inicialização literal - o '\0' é adicionado automaticamente
char str1[] = "Hello";
// Equivalente manual - você adiciona o '\0' explicitamente
char str2[] = {'H', 'e', 'l', 'l', 'o', '\0'};
// Declaração com tamanho fixo - precisa reservar espaço para '\0'
char str3[10] = "Hello"; // espaço para 6 caracteres + 4 extras
// String constante via ponteiro (não modificável)
char *str4 = "Hello";
printf("str1: %s\n", str1);
printf("str2: %s\n", str2);
printf("str3: %s\n", str3);
printf("str4: %s\n", str4);
return 0;
}
No caso de str3[10] = "Hello", o array tem tamanho 10, mas a string ocupa apenas 6 posições (5 letras + '\0'). O restante fica preenchido com zeros.
3. Acessando e Percorrendo Strings
O acesso aos caracteres individuais é feito por índice, como em qualquer array. O percorrimento tradicional usa um laço que continua até encontrar o '\0':
#include <stdio.h>
int main() {
char mensagem[] = "C linguagem";
// Acesso por índice
printf("Primeiro caractere: %c\n", mensagem[0]);
printf("Quinto caractere: %c\n", mensagem[4]);
// Percorrendo com while até '\0'
int i = 0;
while (mensagem[i] != '\0') {
printf("mensagem[%d] = '%c'\n", i, mensagem[i]);
i++;
}
// CUIDADO: acesso fora dos limites - comportamento indefinido!
// printf("%c", mensagem[20]); // Pode crashar ou ler lixo
return 0;
}
Importante: C não faz verificação automática de limites. Acessar posições além do array pode causar comportamento indefinido, incluindo falhas de segmentação.
4. O Terminador Nulo: Guardião da Integridade
O '\0' é o que define onde a string termina, não o tamanho do array. Isso tem implicações profundas:
#include <stdio.h>
int main() {
// Array maior que a string
char buffer[50] = "Teste";
// O '\0' está na posição 5, mas o array tem 50 posições
printf("Tamanho do array: %zu\n", sizeof(buffer)); // 50
printf("String: %s\n", buffer); // "Teste"
// O PERIGO: string sem '\0'
char sem_terminador[] = {'O', 'l', 'a'}; // sem '\0'!
printf("%s\n", sem_terminador); // Pode imprimir "Ola" + lixo até encontrar um 0
return 0;
}
Funções como printf("%s") e todas da biblioteca string.h dependem do '\0' para saber onde parar. Sem ele, continuam lendo memória adjacente até encontrar um byte zero aleatório.
5. Principais Operações com Strings (Sem Funções Prontas)
Para entender como as funções padrão funcionam, vejamos implementações manuais:
#include <stdio.h>
// Cálculo manual do comprimento
int meu_strlen(char str[]) {
int len = 0;
while (str[len] != '\0') {
len++;
}
return len;
}
// Cópia manual
void meu_strcpy(char destino[], char origem[]) {
int i = 0;
while (origem[i] != '\0') {
destino[i] = origem[i];
i++;
}
destino[i] = '\0'; // Não esquecer o terminador!
}
// Concatenação manual
void meu_strcat(char destino[], char origem[]) {
int i = 0, j = 0;
// Encontra o fim da primeira string
while (destino[i] != '\0') {
i++;
}
// Copia a segunda string a partir daí
while (origem[j] != '\0') {
destino[i] = origem[j];
i++;
j++;
}
destino[i] = '\0';
}
int main() {
char str1[20] = "Bom ";
char str2[] = "dia!";
printf("Comprimento de '%s': %d\n", str2, meu_strlen(str2));
meu_strcat(str1, str2);
printf("Concatenado: %s\n", str1);
char copia[20];
meu_strcpy(copia, str1);
printf("Copia: %s\n", copia);
return 0;
}
6. Strings e Funções da Biblioteca Padrão (string.h)
A biblioteca <string.h> oferece funções que operam sobre strings terminadas por nulo:
#include <stdio.h>
#include <string.h>
int main() {
char str1[30] = "Programando";
char str2[] = " em C";
// strlen: retorna o comprimento (sem contar '\0')
printf("Tamanho de '%s': %zu\n", str1, strlen(str1));
// strcpy: copia str2 para str1 (CUIDADO com estouro!)
strcpy(str1, "Nova string");
printf("Após strcpy: %s\n", str1);
// strcat: concatena (também pode estourar buffer!)
strcat(str1, str2);
printf("Após strcat: %s\n", str1);
// strcmp: compara caractere a caractere
char a[] = "abc";
char b[] = "abd";
int resultado = strcmp(a, b);
printf("Comparação 'abc' vs 'abd': %d\n", resultado); // negativo
return 0;
}
Riscos: strcpy e strcat não verificam se o destino tem espaço suficiente. Se a string de origem for maior que o buffer de destino, ocorre estouro de buffer, uma das vulnerabilidades mais comuns em C.
7. Armadilhas Comuns e Boas Práticas
Armadilha 1: Esquecer o espaço para '\0'
char nome[5] = "Maria"; // ERRO: precisa de 6 posições (5 letras + '\0')
// Isso compila, mas pode corromper memória adjacente!
Armadilha 2: Confundir tamanho do array com comprimento da string
char texto[100] = "Curto";
printf("%zu\n", sizeof(texto)); // 100 (tamanho do array)
printf("%zu\n", strlen(texto)); // 5 (comprimento da string)
Boas práticas:
- Sempre reserve um caractere extra para o '\0'
- Use fgets em vez de gets para leitura segura
- Prefira funções com limite de tamanho como strncpy e strncat
#include <stdio.h>
#include <string.h>
int main() {
char nome[20];
// fgets é seguro: lê no máximo 19 caracteres + '\0'
printf("Digite seu nome: ");
fgets(nome, sizeof(nome), stdin);
// Remove o '\n' que fgets pode incluir
size_t len = strlen(nome);
if (len > 0 && nome[len-1] == '\n') {
nome[len-1] = '\0';
}
printf("Olá, %s!\n", nome);
// Versão segura com limite de tamanho
char destino[10];
strncpy(destino, nome, sizeof(destino) - 1);
destino[sizeof(destino) - 1] = '\0'; // Garante terminação
return 0;
}
Referências
- C Strings - cppreference.com — Documentação oficial sobre strings em C, incluindo funções da biblioteca padrão e manipulação de arrays de char
- Strings in C - GeeksforGeeks — Tutorial completo com exemplos práticos de declaração, inicialização e operações com strings
- C String Handling - Wikipedia — Visão geral sobre o tratamento de strings em C, incluindo histórico e questões de segurança
- The C Programming Language - Strings (Beej's Guide) — Guia prático e detalhado sobre strings, arrays de char e o terminador nulo
- Buffer Overflow in C - OWASP — Artigo sobre os riscos de estouro de buffer em strings C e como evitá-los com boas práticas
- C String Library - IBM Documentation — Documentação da IBM sobre as funções da biblioteca string.h e seu funcionamento