C Funções de String

Na aula Strings você viu que um texto em C é um array de char com um '\0' no fim, e que não dá para comparar com == nem trocar com =. Para essas tarefas existe uma caixa de ferramentas pronta, o arquivo string.h: funções para medir, copiar, juntar, comparar e procurar dentro de textos. Nesta aula você vai conhecer as principais, e principalmente quais são perigosas: algumas delas escrevem além do fim do array sem perguntar nada, e esse é um dos erros de segurança mais famosos da história da computação.

Você vai aprender
  • strlen: contar os caracteres (sem o '\0'), e a diferença para sizeof.
  • Copiar texto: strcpy (perigoso), strncpy (traiçoeiro) e snprintf (o jeito seguro).
  • O estouro de buffer do strcpy, visto de verdade com -fsanitize=address.
  • Juntar textos com strcat e strncat.
  • Comparar com strcmp: o que quer dizer o retorno menor que zero, zero ou maior que zero.
  • Procurar com strchr e strstr, e quebrar um texto em pedaços com strtok (com cuidado).

Todos os programas foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa.


A ideia: uma caixa de ferramentas para texto

Pense na caixa de ferramentas da casa da Ana: trena para medir, cola para juntar, lupa para procurar. O string.h é isso para strings. Para usar qualquer ferramenta dele, a primeira linha do programa (depois do stdio.h) é:

#include <string.h>

Todas as funções desta aula seguem duas regras, herdadas da aula anterior:

  1. Elas trabalham até o '\0'. Se ele faltar, elas continuam pela memória afora.
  2. Elas não sabem o tamanho dos seus arrays. Quando uma função escreve num array, cabe a você garantir que há espaço, ou escolher uma função que receba o tamanho.

Os nomes parecem siglas porque são: o C antigo limitava os nomes a poucas letras. Quase todos começam com str ("string"), e o resto é uma abreviação em inglês que a aula traduz.


1. strlen: quantos caracteres?

strlen = "string length", comprimento da string. Ela anda pelo array contando até achar o '\0', exatamente o laço que você escreveu na aula Strings.

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

int main(void) {
    char item[20] = "arroz";
    printf("strlen: %zu\n", strlen(item));
    printf("sizeof: %zu\n", sizeof item);
    printf("strlen(\"\"): %zu\n", strlen(""));
    return 0;
}
$ gcc -std=c23 -Wall -Wextra tamanho.c -o tamanho
$ ./tamanho
strlen: 5               « a-r-r-o-z: o '\0' não entra na conta
sizeof: 20              « o tamanho do array, cheio ou não
strlen(""): 0           « string vazia
strlen(item)
Devolve o número de caracteres antes do '\0'. O texto "arroz" usa 6 bytes do array (5 letras + '\0'), mas o strlen diz 5.
%zu
O resultado de strlen é do tipo size_t (um inteiro sem sinal próprio para tamanhos, aula Tamanho dos Tipos). O formato certo é %zu; com %d o gcc avisa (veja "Quando dá errado").
sizeof item
Mede a fileira inteira: 20 bytes, por mais curto que seja o texto.
Guarde as duas perguntas

strlen(s) responde "quantas letras tem o texto agora?". sizeof s responde "quantos bytes cabem no array?". Para saber se um texto de n letras cabe, a conta é n + 1 <= sizeof s: o + 1 é o '\0', que o strlen não conta. Esse "+ 1" esquecido é a causa de muitos estouros.


2. Copiar: strcpy, strncpy e snprintf

Você viu que nome = "Bia"; não compila. Para colocar um texto dentro de um array já criado, é preciso copiar as letras, uma por uma, e o '\0'. Há três ferramentas, e elas não são igualmente seguras.

strcpy: copia tudo, sem perguntar

strcpy = "string copy". A ordem dos argumentos é a mesma de uma atribuição: destino primeiro, origem depois (como em destino = origem).

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

int main(void) {
    char destino[10];
    strcpy(destino, "feijão");
    printf("%s\n", destino);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra copia.c -o copia
$ ./copia
feijão

Funcionou porque "feijão" cabe em 10 bytes (são 7 bytes mais o '\0': o ã ocupa dois, como explica Acentos e UTF-8). O problema é que o strcpy não sabe que o destino tem 10 bytes. Se a origem for maior, ele escreve além do fim. Veja com um texto que vem de fora do programa, como acontece de verdade.

O estouro de buffer

Neste programa, o item vem da linha de comando: o que a Ana digita depois do nome do programa chega em argv[1] (aula Argumentos da Linha de Comando). O programador reservou 8 bytes, achando que nenhum item teria nome comprido:

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

int main(int argc, char *argv[]) {
    char destino[8];
    if (argc < 2) {
        return 1;
    }
    strcpy(destino, argv[1]);
    printf("Item: %s\n", destino);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra estouro.c -o estouro     « nenhum aviso
$ ./estouro arroz
Item: arroz
$ ./estouro "biscoito recheado"
*** stack smashing detected ***: terminated
Abortado                   (imagem do núcleo gravada) ./estouro "biscoito recheado"
$ echo $?
134
nenhum aviso
O compilador não tem como saber o tamanho do que a Ana vai digitar. (Quando a origem é um texto fixo no código, o gcc às vezes percebe e avisa com -Wstringop-overflow; não conte com isso.)
*** stack smashing detected ***
"Esmagamento da pilha detectado". O strcpy escreveu 18 bytes (17 letras + '\0') num array de 8 e passou por cima de memória vizinha. O Ubuntu compila os programas com uma proteção (o stack protector, "protetor da pilha") que percebeu o estrago na hora de o main terminar e abortou o programa.
Abortado … 134
O programa foi encerrado à força (código 134 = abortado).

E com um texto só um byte maior que o array? "biscoito" tem 8 letras: com o '\0', são 9 bytes num array de 8. A proteção não pegou nada:

$ ./estouro biscoito
Item: biscoito           « o '\0' foi escrito fora do array, e "funcionou"

Esse é o pior caso: o programa escreveu em memória que não era dele, e tudo parece certo. É o erro clássico do "+ 1" esquecido. Para ver o erro de verdade, compile com o AddressSanitizer (aula Sanitizers):

$ gcc -std=c23 -Wall -Wextra -g -fsanitize=address estouro.c -o estouro
$ ./estouro biscoito
=================================================================
==60068==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7b4e79cf0028 at pc 0x7f4e7c2bd2a9 bp 0x7ffe37812400 sp 0x7ffe37811bb8
WRITE of size 9 at 0x7b4e79cf0028 thread T0
    #0 0x7f4e7c2bd2a8 in strcpy ../../../../src/libsanitizer/asan/asan_interceptors.cpp:575
    #1 0x61832ae8c329 in main /home/ana/c/estouro.c:9
…
  This frame has 1 object(s):
    [32, 40) 'destino' (line 5) <== Memory access at offset 40 overflows this variable
…
SUMMARY: AddressSanitizer: stack-buffer-overflow /home/ana/c/estouro.c:9 in main
…                                     « mais linhas com o mapa da memória
WRITE of size 9
Uma escrita de 9 bytes: as 8 letras de "biscoito" mais o '\0'.
#0 … in strcpy
Quem escreveu foi o strcpy…
#1 … in main /home/ana/c/estouro.c:9
…chamado na linha 9 do seu programa.
[32, 40) 'destino' … overflows this variable
destino ocupa 8 bytes (do 32 ao 39); a escrita chegou ao byte 40, um depois do fim.
Por que isso é grave

Escrever além do fim de um array se chama estouro de buffer (buffer overflow). Um atacante que controla o texto consegue escolher o que vai parar na memória vizinha, e em muitos casos isso basta para tomar o controle do programa. Grande parte das falhas de segurança famosas em programas escritos em C começou com um strcpy, strcat, sprintf ou gets sem limite. Regra do guia: só use strcpy quando você tiver certeza de que a origem cabe (um texto fixo e curto, num destino que você mesmo dimensionou). Para todo o resto, use snprintf.

No editor do guia

No editor, um strcpy que estoura costuma passar calado: o programa mostra o texto inteiro e termina normalmente. A memória do WebAssembly é organizada de outro jeito, e o editor não tem stack protector nem AddressSanitizer. Por isso a missão abaixo cobra o jeito seguro, e não "fazer o programa cair".

strncpy: tem limite, mas é traiçoeiro

strncpy ("string n copy", copiar no máximo n) recebe um limite. Parece a solução, mas tem uma armadilha: se a origem não couber, ele copia n bytes e não coloca o '\0'.

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

int main(void) {
    char a[6];
    char b[6];
    strncpy(a, "pão", sizeof a);
    strncpy(b, "biscoito", sizeof b);
    printf("a = %s\n", a);
    printf("b tem %zu bytes; o último vale %d\n", sizeof b, b[5]);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra strncpy.c -o strncpy
strncpy.c: In function ‘main’:
strncpy.c:8:5: warning: ‘strncpy’ output truncated copying 6 bytes from a string of length 8 [-Wstringop-truncation]
    8 |     strncpy(b, "biscoito", sizeof b);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$ ./strncpy
a = pão
b tem 6 bytes; o último vale 105     « 105 é o 'i': não há '\0' em b

O aviso diz "a saída do strncpy foi cortada: copiou 6 bytes de uma string de tamanho 8". O b ficou com b i s c o i e nenhuma placa de fim: um printf("%s", b) leria além do array, como na seção 7 da aula Strings. Quem usa strncpy precisa sempre completar à mão: b[sizeof b - 1] = '\0';. É fácil esquecer.

snprintf: o jeito seguro

O snprintf ("string n print formatted") é o printf que, em vez de mostrar na tela, escreve num array, com limite. Ele sempre coloca o '\0' e nunca passa do tamanho que você informa:

#include <stdio.h>

int main(void) {
    char item[9];
    int precisa = snprintf(item, sizeof item, "%s", "biscoito recheado");
    printf("Guardou: [%s]\n", item);
    printf("O texto completo tinha %d caracteres.\n", precisa);
    if (precisa >= (int)sizeof item) {
        printf("Aviso: não coube, foi cortado.\n");
    }
    return 0;
}
#include <stdio.h>
O snprintf mora no stdio.h, junto com o printf, e não no string.h.
item
1º argumento: o destino.
sizeof item
2º argumento: o tamanho do destino, com o lugar do '\0'. Use sempre sizeof do próprio array, para não errar a conta.
"%s", "biscoito recheado"
O resto é igual ao printf: um formato e os valores. Para copiar um texto, o formato é "%s".
int precisa = …
O snprintf devolve quantos caracteres o texto completo teria. Se esse número for maior ou igual ao tamanho do array, o texto foi cortado. O (int) converte o sizeof para int e evita um aviso de comparação entre tipos com e sem sinal.
$ gcc -std=c23 -Wall -Wextra snprintf.c -o snprintf
snprintf.c: In function ‘main’:
snprintf.c:5:48: warning: ‘%s’ directive output truncated writing 17 bytes into a region of size 9 [-Wformat-truncation=]
    5 |     int precisa = snprintf(item, sizeof item, "%s", "biscoito recheado");
      |                                                ^~   ~~~~~~~~~~~~~~~~~~~
snprintf.c:5:19: note: ‘snprintf’ output 18 bytes into a destination of size 9
    5 |     int precisa = snprintf(item, sizeof item, "%s", "biscoito recheado");
      |                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$ ./snprintf
Guardou: [biscoito]
O texto completo tinha 17 caracteres.
Aviso: não coube, foi cortado.

O gcc percebeu que o texto fixo não cabe e avisou ("a saída de %s foi cortada: 17 bytes numa região de 9"). Repare na diferença: com snprintf o corte é seguro (8 letras + '\0', nada escrito fora do array); o aviso só pergunta se você quis mesmo cortar. O conserto, se não quis, é aumentar o array.

E como ele é um printf, também monta textos com números, o que nenhuma função do string.h faz:

$ cat snprintf2.c
#include <stdio.h>

int main(void) {
    char nome[] = "Ana";
    int idade = 29;
    char ficha[40];
    snprintf(ficha, sizeof ficha, "%s (%d anos)", nome, idade);
    printf("Ficha: %s\n", ficha);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra snprintf2.c -o snprintf2
$ ./snprintf2
Ficha: Ana (29 anos)

Missão: este programa copia pedido para um array de 9 bytes com strcpy, e estoura. No editor ele parece funcionar (mostra o texto inteiro), mas está escrevendo fora do array. Troque o strcpy por snprintf, com o tamanho do destino, para guardar com segurança só o que cabe: [biscoito].

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

int main(void) {
    char pedido[] = "biscoito recheado";
    char item[9];
    strcpy(item, pedido);
    printf("[%s]\n", item);
    return 0;
}
#include <stdio.h>
#include <string.h>

int main(void) {
    char pedido[] = "biscoito recheado";
    char item[9];
    snprintf(item, sizeof item, "%s", pedido);
    printf("[%s]\n", item);
    return 0;
}

Qual usar para copiar

FunçãoSabe o tamanho do destino?Sempre põe o '\0'?Quando usar
strcpy(dest, orig)NãoSim (se couber)Só quando a origem é fixa e você sabe que cabe.
strncpy(dest, orig, n)SimNão, se a origem for grandeEvite. Se usar, termine com dest[n - 1] = '\0';.
snprintf(dest, sizeof dest, "%s", orig)SimSimPadrão do guia. Também monta textos com números.

3. Juntar: strcat e strncat

strcat = "string concatenate", concatenar (encadear, juntar no fim). Ela acha o '\0' do destino e copia a origem a partir dali:

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

int main(void) {
    char frase[30] = "Lista: ";
    strcat(frase, "arroz");
    strcat(frase, ", feijão");
    printf("%s\n", frase);
    printf("%zu de %zu bytes usados\n", strlen(frase) + 1, sizeof frase);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra juntar.c -o juntar
$ ./juntar
Lista: arroz, feijão
22 de 30 bytes usados
char frase[30] = "Lista: ";
O destino precisa ter espaço sobrando desde o começo: o strcat não aumenta o array.
strcat(frase, "arroz");
Gruda "arroz" depois de "Lista: ", passando por cima do '\0' antigo e pondo um novo no fim.
strlen(frase) + 1
Letras + '\0': os bytes realmente ocupados.

O strcat tem o mesmo defeito do strcpy: não sabe o tamanho do destino. O strncat recebe um limite, mas o limite é o número de letras que ainda cabem, e a conta é sua:

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

int main(void) {
    char frase[16] = "Lista: ";
    size_t sobra = sizeof frase - strlen(frase) - 1;
    strncat(frase, "arroz, feijão, café", sobra);
    printf("[%s]\n", frase);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra strncat.c -o strncat
$ ./strncat
[Lista: arroz, f]       « 15 letras + '\0' = os 16 bytes do array

A conta sizeof frase - strlen(frase) - 1 é "o tamanho do array, menos o que já está ocupado, menos o lugar do '\0'" (o strncat, diferente do strncpy, sempre põe o '\0'). Errar essa conta por 1 é muito comum. Uma alternativa sem conta é montar a frase inteira de uma vez com snprintf(frase, sizeof frase, "Lista: %s, %s", a, b);.


4. Comparar: strcmp

strcmp = "string compare". Ela compara letra por letra, do começo, e devolve um número:

Resultado de strcmp(a, b)Significa
0As duas strings são iguais
menor que 0 (negativo)a vem antes de b (na ordem dos códigos dos caracteres)
maior que 0 (positivo)a vem depois de b
#include <stdio.h>
#include <string.h>

int main(void) {
    char a[] = "ana", b[] = "bia", c[] = "Bia", d[] = "anas", e[] = "carlos";
    printf("ana  x ana    = %d\n", strcmp(a, a));
    printf("ana  x bia    = %d\n", strcmp(a, b));
    printf("bia  x ana    = %d\n", strcmp(b, a));
    printf("Bia  x ana    = %d\n", strcmp(c, a));
    printf("ana  x anas   = %d\n", strcmp(a, d));
    printf("ana  x carlos = %d\n", strcmp(a, e));
    return 0;
}
$ gcc -std=c23 -Wall -Wextra comparar.c -o comparar
$ ./comparar
ana  x ana    = 0         « iguais
ana  x bia    = -1        « 'a' (97) vem antes de 'b' (98)
bia  x ana    = 1
Bia  x ana    = -31       « 'B' (66) vem antes de 'a' (97): maiúsculas vêm antes!
ana  x anas   = -115      « "ana" acabou antes: o '\0' (0) vem antes do 's' (115)
ana  x carlos = -2

Lendo a saída:

  • A comparação para na primeira letra diferente e compara os códigos das duas. Aqui o resultado foi justamente a diferença entre eles (66 − 97 = −31).
  • Não confie no número exato, só no sinal. A regra do C só garante "negativo, zero ou positivo". Quando os dois textos estão escritos direto no código (strcmp("Bia", "ana")), o gcc calcula a resposta na compilação e o programa mostra -1, e não -31. Outras bibliotecas devolvem outros números.
  • A ordem é a dos códigos, não a do dicionário: todas as maiúsculas (65 a 90) vêm antes de todas as minúsculas (97 a 122). Por isso "Bia" vem antes de "ana". Letras acentuadas ficam depois de tudo (veja Acentos e UTF-8).

O uso mais comum: igual ou não?

Na prática, quase sempre a pergunta é "são iguais?", e a resposta é strcmp(a, b) == 0:

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

int main(void) {
    char digitada[20];
    printf("Senha: ");
    if (scanf("%19s", digitada) != 1) {
        return 1;
    }
    if (strcmp(digitada, "lojinha") == 0) {
        printf("Bem-vinda, Ana!\n");
    } else {
        printf("Senha errada.\n");
    }
    return 0;
}
$ gcc -std=c23 -Wall -Wextra senha.c -o senha
$ echo lojinha | ./senha
Senha: Bem-vinda, Ana!
$ echo Lojinha | ./senha
Senha: Senha errada.      « maiúscula conta: 'L' não é 'l'
Cuidado: if (strcmp(a, b)) é "se forem diferentes"

Como iguais dão 0, e 0 é "falso" em C, escrever if (strcmp(nome, "Ana")) entra no if quando os textos são diferentes. É o contrário do que parece. Escreva sempre a comparação inteira: == 0 para iguais, != 0 para diferentes.

Existe também o strncmp(a, b, n), que compara só os n primeiros caracteres: strncmp(email, "ana", 3) == 0 pergunta "começa com ana?".

Missão: a senha digitada (o editor já envia lojinha) está certa, mas o programa responde "Senha errada.". Leia o aviso do compilador e troque o == por uma comparação com strcmp, para mostrar Bem-vinda, Ana!.

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

int main(void) {
    char digitada[20];
    printf("Senha: ");
    if (scanf("%19s", digitada) != 1) {
        return 1;
    }
    if (digitada == "lojinha") {
        printf("Bem-vinda, Ana!\n");
    } else {
        printf("Senha errada.\n");
    }
    return 0;
}
#include <stdio.h>
#include <string.h>

int main(void) {
    char digitada[20];
    printf("Senha: ");
    if (scanf("%19s", digitada) != 1) {
        return 1;
    }
    if (strcmp(digitada, "lojinha") == 0) {
        printf("Bem-vinda, Ana!\n");
    } else {
        printf("Senha errada.\n");
    }
    return 0;
}

5. Procurar: strchr e strstr

strchr = "string character": procura um caractere. strstr = "string string": procura um texto dentro de outro. As duas devolvem o lugar onde acharam (um ponteiro, assunto de Ponteiros e Strings), ou NULL ("nulo", nenhum lugar) se não acharam.

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

int main(void) {
    char email[] = "ana@lojinha.com";

    char *arroba = strchr(email, '@');
    if (arroba != NULL) {
        printf("Achou '@' na posição %d\n", (int)(arroba - email));
        printf("Domínio: %s\n", arroba + 1);
    }

    if (strchr(email, '#') == NULL) {
        printf("Não tem '#'\n");
    }

    char *fim = strstr(email, ".com");
    if (fim != NULL) {
        printf("\".com\" começa na posição %d\n", (int)(fim - email));
    }
    return 0;
}
char *arroba = strchr(email, '@');
Procura o caractere '@' (aspas simples: é um char). O char * é uma variável que guarda um lugar dentro do texto. Por enquanto, leia como "marcador de página".
if (arroba != NULL)
Sempre confira antes de usar: se não achou, o resultado é NULL, e usar um NULL derruba o programa (aula Ponteiro Nulo).
arroba - email
A distância entre o lugar achado e o começo do texto: a posição (índice). O (int) converte para usar com %d.
arroba + 1
O lugar logo depois do @. Um %s a partir dali mostra o resto do texto até o '\0'.
strstr(email, ".com")
Mesma ideia, procurando um pedaço de texto inteiro (aspas duplas).
$ gcc -std=c23 -Wall -Wextra procurar.c -o procurar
$ ./procurar
Achou '@' na posição 3
Domínio: lojinha.com
Não tem '#'
".com" começa na posição 11

Se só interessa saber se tem, basta a comparação: if (strchr(email, '@') != NULL). Para achar a última ocorrência de um caractere, existe strrchr (o r é de reverse, "do fim para o começo"): útil para achar a extensão em "foto.final.png".


6. Quebrar em pedaços: strtok (com cuidado)

strtok = "string token", e token é cada pedaço de um texto separado por algum caractere. Serve para ler uma lista como "arroz,feijão,café":

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

int main(void) {
    char lista[] = "arroz,feijão,,café";
    char *item = strtok(lista, ",");
    while (item != NULL) {
        printf("[%s]\n", item);
        item = strtok(NULL, ",");
    }
    printf("Depois, lista = \"%s\"\n", lista);
    return 0;
}
strtok(lista, ",")
A primeira chamada recebe o texto e os separadores (aqui, só a vírgula; " ," aceitaria espaço ou vírgula). Devolve o primeiro pedaço.
strtok(NULL, ",")
As chamadas seguintes recebem NULL no lugar do texto: quer dizer "continue de onde parou". O strtok guarda escondido onde parou.
while (item != NULL)
Quando não há mais pedaços, ele devolve NULL e o laço termina.
$ gcc -std=c23 -Wall -Wextra tokens.c -o tokens
$ ./tokens
[arroz]
[feijão]
[café]                   « o pedaço vazio entre ",," sumiu
Depois, lista = "arroz"  « a lista original foi alterada

Funciona, mas repare nos quatro cuidados:

  1. Ele altera o texto. Para separar os pedaços, o strtok troca cada vírgula por um '\0'. Por isso, depois, lista mostra só "arroz". Se você ainda precisar do original, faça uma cópia antes.
  2. Separadores seguidos viram um só. O campo vazio entre ,, desapareceu. Numa planilha, isso desalinha as colunas.
  3. Não funciona num literal. Com char *lista = "arroz,feijão";, ele tenta escrever o '\0' na memória só de leitura: no laboratório, Falha de segmentação (imagem do núcleo gravada) ./tokliteral. Use um array (char lista[] = …).
  4. Um texto por vez. Como ele guarda escondido onde parou, não dá para quebrar duas strings ao mesmo tempo (por exemplo, linhas e, dentro de cada linha, colunas). No Linux existe o strtok_r, que guarda essa posição numa variável sua.

Quando dá errado

error: implicit declaration of function ‘strlen’ [-Wimplicit-function-declaration]

O que significa: faltou o #include <string.h>. O compilador não conhece a função. No gcc 15 isso é um erro, e ele mesmo mostra a linha que falta:

$ gcc -std=c23 -Wall -Wextra semh.c -o semh
semh.c: In function ‘main’:
semh.c:5:21: error: implicit declaration of function ‘strlen’ [-Wimplicit-function-declaration]
    5 |     printf("%zu\n", strlen(item));
      |                     ^~~~~~
semh.c:2:1: note: include ‘<string.h>’ or provide a declaration of ‘strlen’
    1 | #include <stdio.h>
  +++ |+#include <string.h>
    2 | 

Como consertar: o +++ |+ é a sugestão do gcc: acrescente #include <string.h> no topo. (O snprintf é do stdio.h.)

warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘size_t’ {aka ‘long unsigned int’}

O que significa: você mostrou o resultado de strlen (ou de sizeof) com %d. O gcc até sugere %ld, mas o formato próprio de size_t é %zu.

Como consertar: printf("%zu letras\n", strlen(item));.

*** stack smashing detected ***: terminated ou texto estranho depois de um strcpy/strcat

O que significa: estouro de buffer: a origem não cabia no destino.

Como consertar: troque por snprintf(dest, sizeof dest, "%s", orig) e confira o retorno se cortar for um problema. Para achar a linha, compile com -g -fsanitize=address.

warning: ‘strncpy’ output truncated … [-Wstringop-truncation]

O que significa: o strncpy cortou a origem e o destino ficou sem '\0'.

Como consertar: use snprintf. Se precisar manter o strncpy, termine com dest[sizeof dest - 1] = '\0';.

O if com strcmp faz o contrário do esperado

O que significa: você escreveu if (strcmp(a, b)), que é verdadeiro quando as strings são diferentes. No laboratório, if (strcmp(nome, "Ana")) com nome valendo "Ana" mostrou "Não é a Ana.".

Como consertar: if (strcmp(a, b) == 0) para iguais.

Falha de segmentação com strchr, strstr ou strtok

O que significa: ou o resultado NULL (não achou) foi usado sem conferir, ou o strtok recebeu um literal (char *s = "…"), que não pode ser alterado.

Como consertar: confira != NULL antes de usar o resultado; para o strtok, use um array char s[] = "…".


Referência rápida

FunçãoNome em inglêsO que fazDevolve
strlen(s)string lengthConta os caracteres até o '\0' (sem ele)size_t (use %zu)
strcpy(d, o)string copyCopia o para d, com '\0'. Sem limited
strncpy(d, o, n)string n copyCopia até n bytes; pode deixar sem '\0'd
snprintf(d, sizeof d, "%s", o)string n print formattedCopia/monta com limite; sempre põe '\0' (stdio.h)Tamanho que o texto completo teria
strcat(d, o)string concatenateJunta o no fim de d. Sem limited
strncat(d, o, n)string n concatenateJunta até n letras e põe '\0'; n = espaço que sobra − 1d
strcmp(a, b)string compareCompara letra por letra< 0, 0 (iguais) ou > 0
strncmp(a, b, n)string n compareCompara só os n primeiros< 0, 0 ou > 0
strchr(s, c)string characterProcura a primeira ocorrência do caractere cO lugar, ou NULL
strrchr(s, c)string reverse characterProcura a última ocorrência de cO lugar, ou NULL
strstr(s, t)string stringProcura o texto t dentro de sO lugar, ou NULL
strtok(s, sep)string tokenQuebra em pedaços; altera s; depois, strtok(NULL, sep)O próximo pedaço, ou NULL

A lista completa, com as funções de memória (memcpy, memset…), está na Referência do string.h.


Exercícios

1. O cabeçalho

Complete a linha que traz as funções desta aula:

#include

2. Contar

Quanto vale strlen("café")? Dica: o é ocupa 2 bytes. Escreva só o número.

strlen("café") ==

3. Copiar com segurança

Complete a cópia segura de origem para destino:

snprintf(destino, , "%s", origem);

4. O que strcmp("casa", "casa") devolve?

5. Qual é o problema de char b[4]; strncpy(b, "banana", sizeof b);?

6. strchr(email, '@') devolve NULL. O que isso quer dizer?

7. Depois de quebrar char s[] = "a;b;c"; com strtok(s, ";") até o fim, o que printf("%s", s) mostra?

Missão final: a etiqueta do cliente

Missão: a lojinha imprime etiquetas no formato sobrenome, nome. Monte etiqueta com snprintf (formato "%s, %s"), conte as letras com strlen e use strchr para conferir se o e-mail tem @. Sem avisos.

Etiqueta: Souza, Ana (10 letras)
E-mail válido? sim
#include <stdio.h>
#include <string.h>

int main(void) {
    char nome[] = "Ana";
    char sobrenome[] = "Souza";
    char email[] = "ana@lojinha.com";
    char etiqueta[30] = "";

    // 1. monte "Souza, Ana" em etiqueta

    // 2. mostre a etiqueta e o número de letras
    printf("Etiqueta: %s (? letras)\n", etiqueta);

    // 3. o e-mail tem '@'?
    printf("E-mail válido? ?\n");
    return 0;
}
#include <stdio.h>
#include <string.h>

int main(void) {
    char nome[] = "Ana";
    char sobrenome[] = "Souza";
    char email[] = "ana@lojinha.com";
    char etiqueta[30] = "";

    snprintf(etiqueta, sizeof etiqueta, "%s, %s", sobrenome, nome);

    printf("Etiqueta: %s (%zu letras)\n", etiqueta, strlen(etiqueta));

    if (strchr(email, '@') != NULL) {
        printf("E-mail válido? sim\n");
    } else {
        printf("E-mail válido? não\n");
    }
    return 0;
}

A vírgula e o espaço contam como caracteres: "Souza, Ana" tem 10. Ter um @ está longe de garantir que um e-mail é válido, mas é a primeira conferência que todo formulário faz (mais em Validar a Entrada).


Resumo

  • #include <string.h> traz as funções de texto. Todas trabalham até o '\0' e nenhuma sabe o tamanho dos seus arrays.
  • strlen conta as letras sem o '\0' (resultado size_t, formato %zu); para caber, é preciso strlen + 1 bytes.
  • strcpy e strcat não têm limite: com origem grande, causam estouro de buffer (stack smashing, ou pior, nada aparente). -fsanitize=address mostra a linha.
  • strncpy pode deixar o destino sem '\0'; strncat exige a conta do espaço que sobra. O jeito seguro de copiar e montar textos é snprintf(dest, sizeof dest, "…", …).
  • strcmp devolve 0 para iguais, negativo se o primeiro vem antes, positivo se vem depois; confie só no sinal. Escreva sempre == 0.
  • strchr/strstr devolvem o lugar achado ou NULL (confira antes de usar). strtok altera o texto, junta separadores seguidos e não funciona em literais.