Toda linguagem moderna trata texto como algo natural: você junta, corta e compara palavras sem pensar. O C não. Aqui, uma string é apenas um vetor de caracteres com uma regra secreta — e quem não conhece essa regra passa horas caçando bugs invisíveis. Hoje você vai conhecê-la, e a partir de agora vai enxergar texto em C como ele realmente é: memória bruta, um caractere por casa, encerrada por um marcador silencioso.
A regra secreta: o terminador nulo
Em C, uma string é um vetor de char que termina com um caractere especial: o nulo, escrito '\0' (o byte de valor zero). Esse marcador não é um detalhe — é o que define onde a string acaba. As funções de texto do C não sabem o tamanho da string; elas simplesmente leem caractere por caractere até esbarrar no '\0'.
Quando você escreve uma string entre aspas duplas, o C adiciona o terminador automaticamente:
#include <stdio.h>
int main(void) {
char nome[] = "Ana";
// Na memória: 'A' 'n' 'a' '\0' -> quatro bytes, não três!
printf("Texto: %s\n", nome);
printf("Ocupa %zu bytes na memória.\n", sizeof(nome)); // 4
return 0;
}
Repare: "Ana" tem três letras, mas ocupa quatro bytes. O quarto é o '\0' invisível. Esquecer de reservar espaço para ele é a causa número um de bugs com strings — se você declarar char nome[3] = "Ana";, não haverá lugar para o terminador, e as funções de texto vão continuar lendo memória alheia até encontrar um zero por acaso.
String literal via ponteiro vs. vetor de char
Há duas formas comuns de criar uma string, e a diferença entre elas importa:
char vetor[] = "mutável"; // uma CÓPIA, que você pode alterar
char *ponteiro = "constante"; // aponta para memória somente-leitura
O vetor[] cria seu próprio espaço, copiando os caracteres — você pode modificá-lo à vontade. Já o char *ponteiro aponta para uma string literal que costuma viver em memória somente-leitura; tentar alterá-la (ponteiro[0] = 'X';) é comportamento indefinido e frequentemente trava o programa. Regra prática: se pretende modificar a string, declare-a como vetor (char v[] = "...").
Lendo o tamanho com strlen
Como o C não guarda o comprimento, para descobri-lo é preciso contar até o '\0'. A função strlen, do cabeçalho <string.h>, faz isso:
#include <stdio.h>
#include <string.h>
int main(void) {
char frase[] = "Linguagem C";
printf("Comprimento: %zu\n", strlen(frase)); // 11
printf("Bytes na memória: %zu\n", sizeof(frase)); // 12 (inclui o '\0')
return 0;
}
Guarde a distinção: strlen conta os caracteres visíveis (11), sem o terminador; sizeof mede os bytes reservados (12), incluindo o '\0'. Confundir os dois gera erros clássicos de "off-by-one".
Vale entender como strlen funciona por dentro — é um belo exemplo de tudo que aprendemos sobre ponteiros:
size_t meu_strlen(const char *s) {
size_t n = 0;
while (s[n] != '\0') { // avança até o terminador
n++;
}
return n;
}
Ela simplesmente caminha pelo vetor contando, e para no '\0'. Toda a biblioteca de strings do C é feita dessa ideia.
Copiar e concatenar: as ferramentas do string.h
O C não deixa você fazer s1 = s2 para copiar strings — isso só copiaria ponteiros, não o conteúdo. Para trabalhar com texto, usamos funções do <string.h>. As mais comuns:
#include <stdio.h>
#include <string.h>
int main(void) {
char destino[50];
strcpy(destino, "Olá"); // copia "Olá" para destino
strcat(destino, ", mundo!"); // concatena ", mundo!" ao final
printf("%s\n", destino); // Olá, mundo!
printf("Tamanho: %zu\n", strlen(destino)); // 11 (bytes de acentos à parte)
// Comparação: retorna 0 se forem iguais
if (strcmp(destino, "Olá, mundo!") == 0) {
printf("As strings são iguais.\n");
}
return 0;
}
Três funções essenciais: strcpy copia, strcat concatena, strcmp compara. Uma observação sobre strcmp: ela devolve 0 quando as strings são iguais — o que confunde quem espera um "verdadeiro". Pense nela como "diferença": zero significa "nenhuma diferença".
O perigo mora aqui: estouro de buffer
Agora a parte séria. As funções strcpy e strcat não verificam se cabe. Se o destino não tiver espaço suficiente, elas escrevem além do fim do vetor, corrompendo a memória vizinha:
char pequeno[5];
strcpy(pequeno, "Esta frase é gigante"); // DESASTRE: escreve muito além dos 5 bytes
Esse é o infame buffer overflow, uma das falhas de segurança mais exploradas da história da computação. A defesa é usar as versões "com limite", que recebem o tamanho máximo do destino e param antes de estourar:
Para ver o estouro acontecer: cole o exemplo no Compiler Explorer com -fsanitize=address. Sem a flag, um strcpy grande demais costuma "funcionar" — e é esse silêncio que fez do estouro de buffer a falha mais explorada da história. Com ela, o programa para na hora e informa quantos bytes passaram do fim do vetor.
#include <stdio.h>
#include <string.h>
int main(void) {
char destino[10];
strncpy(destino, "Texto muito longo", sizeof(destino) - 1);
destino[sizeof(destino) - 1] = '\0'; // garante o terminador
printf("%s\n", destino); // Texto mui
return 0;
}
O strncpy copia no máximo n caracteres, evitando o estouro. Mas ele tem uma pegadinha: se o texto for maior que o limite, ele não adiciona o '\0' sozinho — por isso a linha seguinte força o terminador manualmente. Sempre garanta o terminador ao usar strncpy. Essa disciplina defensiva é o preço de trabalhar com texto em C.
Percorrendo uma string caractere a caractere
Como uma string é um vetor, você a percorre exatamente como aprendeu — o '\0' é a condição de parada natural:
#include <stdio.h>
int main(void) {
char texto[] = "C é elegante";
int vogais = 0;
for (int i = 0; texto[i] != '\0'; i++) {
char c = texto[i];
if (c=='a'||c=='e'||c=='i'||c=='o'||c=='u'||
c=='A'||c=='E'||c=='I'||c=='O'||c=='U') {
vogais++;
}
}
printf("O texto tem %d vogais.\n", vogais);
return 0;
}
O laço não precisa saber o comprimento antecipadamente: ele anda enquanto o caractere atual não for o terminador. Esse padrão — "vá até o '\0'" — é a assinatura de todo processamento de texto em C.
Um utilitário completo
Vamos juntar tudo numa função que conta as palavras de uma frase:
#include <stdio.h>
int contar_palavras(const char *s) {
int palavras = 0;
int dentro = 0; // estamos no meio de uma palavra?
for (int i = 0; s[i] != '\0'; i++) {
if (s[i] != ' ') {
if (!dentro) { // acabamos de entrar numa palavra
palavras++;
dentro = 1;
}
} else {
dentro = 0; // espaço: saímos da palavra
}
}
return palavras;
}
int main(void) {
printf("%d\n", contar_palavras("dominando a linguagem C")); // 4
return 0;
}
Repare como o algoritmo só precisa de um caractere por vez e do terminador para saber quando parar — todo o resto é lógica sobre o que já dominamos.
O que vem a seguir
Hoje desmistificamos as strings: são vetores de char fechados por um '\0', e a biblioteca <string.h> nos dá ferramentas para copiá-las, concatená-las e compará-las — sempre com o cuidado de não estourar buffers. Na próxima aula, subimos mais um nível na hierarquia de ponteiros: vamos ao ponteiro para ponteiro, e usá-lo num contexto que você encontra em todo programa C do mundo real — os argumentos de linha de comando, aquele int main(int argc, char *argv[]) que até agora evitamos.
Fontes e leituras recomendadas
- The C Programming Language (K&R), Kernighan & Ritchie — Cap. 5.5, Character Pointers and Functions
- cppreference — Null-terminated byte strings — https://en.cppreference.com/w/c/string/byte
- cppreference —
<string.h>— referência destrcpy,strcat,strcmp,strlen— https://en.cppreference.com/w/c/string - Modern C, Jens Gustedt — capítulo sobre strings e o terminador nulo — https://gustedt.gitlabpages.inria.fr/modern-c/
- CERT C — STR31-C, sobre garantir espaço para o terminador e evitar estouros — https://wiki.sei.cmu.edu/confluence/display/c
Exercícios
Exercício 1
Declare char saudacao[] = "Bom dia";. Imprima o texto com %s, seu comprimento com strlen e o total de bytes que ocupa com sizeof. Explique por que os dois números diferem em 1.
Ver resposta
✓ Resposta:
#include <stdio.h>
#include <string.h>
int main(void) {
char saudacao[] = "Bom dia";
printf("%s\n", saudacao);
printf("strlen: %zu\n", strlen(saudacao)); // 7
printf("sizeof: %zu\n", sizeof(saudacao)); // 8
return 0;
}
strlen conta os 7 caracteres visíveis; sizeof mede os 8 bytes reservados, incluindo o terminador '\0' que o C acrescentou. A diferença de 1 é exatamente esse terminador.
Exercício 2
Escreva sua própria versão de strlen, chamada int comprimento(const char *s), sem usar a função da biblioteca. Teste com três strings diferentes.
Ver resposta
✓ Resposta:
#include <stdio.h>
int comprimento(const char *s) {
int n = 0;
while (s[n] != '\0') {
n++;
}
return n;
}
int main(void) {
printf("%d\n", comprimento("C")); // 1
printf("%d\n", comprimento("Rust")); // 4
printf("%d\n", comprimento("")); // 0 (string vazia)
return 0;
}
A string vazia "" tem comprimento 0 porque seu primeiro caractere já é o '\0'.
Exercício 3
Escreva uma função void para_maiuscula(char *s) que converta uma string para letras maiúsculas, modificando-a no lugar. Dica: para letras minúsculas de 'a' a 'z', subtraia 32 (a diferença ASCII) ou use toupper de <ctype.h>.
Ver resposta
✓ Resposta:
#include <stdio.h>
#include <ctype.h>
void para_maiuscula(char *s) {
for (int i = 0; s[i] != '\0'; i++) {
s[i] = (char) toupper((unsigned char) s[i]);
}
}
int main(void) {
char texto[] = "linguagem c";
para_maiuscula(texto);
printf("%s\n", texto); // LINGUAGEM C
return 0;
}
Usar toupper de <ctype.h> é mais seguro e portável do que subtrair 32 na mão, pois lida corretamente com caracteres que não são letras (deixa-os intactos). O (unsigned char) no argumento evita comportamento indefinido com caracteres de valor negativo.
Exercício 4
Explique o que há de perigoso no trecho char buf[8]; strcpy(buf, "sequência longa demais");. Reescreva-o de forma segura usando strncpy.
Ver resposta
✓ Resposta: O buffer buf tem só 8 bytes, mas o texto a ser copiado tem muito mais que isso. O strcpy não verifica o tamanho do destino: ele vai escrever a string inteira além dos 8 bytes, corrompendo a memória vizinha — um clássico buffer overflow, que pode travar o programa ou virar uma brecha de segurança. Versão segura:
char buf[8];
strncpy(buf, "sequência longa demais", sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0'; // garante o terminador
Copiamos no máximo 7 caracteres e forçamos o '\0' na última posição, respeitando os limites do buffer.
Exercício 5
Escreva uma função int eh_palindromo(const char *s) que retorne 1 se a string for um palíndromo (lê-se igual de trás para frente, considerando só letras e ignorando maiúsculas/minúsculas) e 0 caso contrário. Teste com "arara" e "casa".
Ver resposta
✓ Resposta:
#include <stdio.h>
#include <ctype.h>
#include <string.h>
int eh_palindromo(const char *s) {
int i = 0;
int j = (int) strlen(s) - 1;
while (i < j) {
if (tolower((unsigned char) s[i]) != tolower((unsigned char) s[j])) {
return 0; // caracteres diferentes: não é palíndromo
}
i++;
j--;
}
return 1;
}
int main(void) {
printf("%d\n", eh_palindromo("arara")); // 1
printf("%d\n", eh_palindromo("casa")); // 0
return 0;
}
Usamos dois índices que caminham das pontas para o centro (i sobe, j desce), comparando os caracteres correspondentes. Se todos baterem até se cruzarem, é um palíndromo. O tolower torna a comparação insensível a maiúsculas/minúsculas.