Funções de string: strlen, strcpy, strcat, strcmp

1. Introdução às funções de string na biblioteca <string.h>

Em linguagem C, strings são representadas como vetores de caracteres terminados pelo caractere nulo (\0). O cabeçalho <string.h> fornece um conjunto de funções essenciais para manipulação dessas sequências. Entre as mais utilizadas estão strlen, strcpy, strcat e strcmp, que permitem medir, copiar, concatenar e comparar strings, respectivamente.

O correto funcionamento dessas funções depende inteiramente da presença do terminador nulo. Sem ele, as operações podem ler ou escrever além dos limites da memória alocada, causando comportamentos indefinidos. Compreender o papel do \0 é fundamental para usar essas funções com segurança.

2. strlen: medindo o comprimento de uma string

A função strlen tem a seguinte sintaxe:

size_t strlen(const char *str);

Ela retorna o número de caracteres em str até (mas não incluindo) o terminador nulo. O tipo size_t é um tipo inteiro sem sinal definido em <stddef.h>, projetado para armazenar tamanhos de objetos.

Exemplo prático:

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

int main() {
    char mensagem[] = "Olá, mundo!";
    size_t tam = strlen(mensagem);

    printf("String: \"%s\"\n", mensagem);
    printf("strlen: %zu caracteres\n", tam);
    printf("sizeof: %zu bytes (inclui \\0)\n", sizeof(mensagem));

    return 0;
}

Saída esperada:

String: "Olá, mundo!"
strlen: 11 caracteres
sizeof: 12 bytes (inclui \0)

A diferença crucial: strlen conta apenas os caracteres visíveis, enquanto sizeof retorna o total de bytes ocupados pelo array, incluindo o \0. Essa distinção é vital ao alocar buffers dinamicamente.

3. strcpy: copiando strings

Sintaxe:

char *strcpy(char *dest, const char *src);

strcpy copia o conteúdo de src para dest, incluindo o terminador nulo. A função retorna o ponteiro para dest.

Perigo comum - estouro de buffer:

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

int main() {
    char destino[5];  // Pequeno buffer
    char origem[] = "Mensagem muito longa";

    strcpy(destino, origem);  // Estouro de buffer! Comportamento indefinido

    printf("Destino: %s\n", destino);
    return 0;
}

Para evitar esse problema, use strncpy, que limita a cópia a n caracteres:

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

int main() {
    char destino[10];
    char origem[] = "Seguro";

    strncpy(destino, origem, sizeof(destino) - 1);
    destino[sizeof(destino) - 1] = '\0';  // Garante terminação

    printf("Destino: %s\n", destino);
    return 0;
}

4. strcat: concatenando strings

Sintaxe:

char *strcat(char *dest, const char *src);

strcat localiza o final de dest (onde está o \0), remove esse terminador e anexa o conteúdo de src a partir daquele ponto. O resultado é uma única string terminada por \0.

Exemplo com verificação de tamanho:

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

int main() {
    char nome[50] = "João";
    char sobrenome[] = " Silva";

    if (strlen(nome) + strlen(sobrenome) + 1 <= sizeof(nome)) {
        strcat(nome, sobrenome);
        printf("Nome completo: %s\n", nome);
    } else {
        printf("Buffer insuficiente para concatenação.\n");
    }

    return 0;
}

A concatenação segura requer que dest tenha espaço suficiente para armazenar ambas as strings mais o \0. Sem essa verificação, o programa pode corromper dados adjacentes na memória.

5. strcmp: comparando strings

Sintaxe:

int strcmp(const char *str1, const char *str2);

A função compara lexicograficamente duas strings com base nos valores ASCII dos caracteres. Retorna:

  • Valor negativo se str1 for menor que str2
  • Zero se forem iguais
  • Valor positivo se str1 for maior que str2

Exemplo de comparação:

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

int main() {
    char senha_correta[] = "C0d1g0#2024";
    char entrada[50];

    printf("Digite a senha: ");
    fgets(entrada, sizeof(entrada), stdin);
    entrada[strcspn(entrada, "\n")] = '\0';  // Remove newline

    if (strcmp(entrada, senha_correta) == 0) {
        printf("Acesso concedido.\n");
    } else {
        printf("Senha incorreta.\n");
    }

    return 0;
}

Para comparações parciais, use strncmp, que compara apenas os primeiros n caracteres:

if (strncmp(entrada, "C0d", 3) == 0) {
    printf("Começa com C0d\n");
}

6. Boas práticas e armadilhas comuns

Verificação de ponteiros nulos:

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

size_t strlen_segura(const char *str) {
    if (str == NULL) {
        return 0;
    }
    return strlen(str);
}

Alocação adequada de buffers:

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

int main() {
    char *origem = "String dinâmica";
    size_t tam = strlen(origem) + 1;

    char *destino = (char *)malloc(tam);
    if (destino != NULL) {
        strcpy(destino, origem);
        printf("Copiado: %s\n", destino);
        free(destino);
    }

    return 0;
}

Código vulnerável vs. robusto:

Vulnerável:

char nome[10];
strcpy(nome, "Nome muito extenso");

Robusto:

char nome[10];
strncpy(nome, "Nome muito extenso", sizeof(nome) - 1);
nome[sizeof(nome) - 1] = '\0';

7. Exemplo integrado: um programa prático

O programa a seguir lê nome e sobrenome, realiza operações com as quatro funções e trata possíveis erros:

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

int main() {
    char primeiro[30], ultimo[30];
    char nome_completo[100];

    printf("Digite o primeiro nome: ");
    fgets(primeiro, sizeof(primeiro), stdin);
    primeiro[strcspn(primeiro, "\n")] = '\0';

    printf("Digite o sobrenome: ");
    fgets(ultimo, sizeof(ultimo), stdin);
    ultimo[strcspn(ultimo, "\n")] = '\0';

    // Verifica tamanhos
    size_t tam_necessario = strlen(primeiro) + strlen(ultimo) + 2;  // + espaço e \0
    if (tam_necessario > sizeof(nome_completo)) {
        printf("Erro: nome muito longo.\n");
        return 1;
    }

    // Copia primeiro nome
    strcpy(nome_completo, primeiro);

    // Concatena espaço e sobrenome
    strcat(nome_completo, " ");
    strcat(nome_completo, ultimo);

    printf("Nome completo: %s\n", nome_completo);
    printf("Tamanho: %zu caracteres\n", strlen(nome_completo));

    // Comparação com nome armazenado
    char armazenado[] = "Maria Silva";
    if (strcmp(nome_completo, armazenado) == 0) {
        printf("Nome idêntico ao armazenado.\n");
    } else {
        printf("Nome diferente do armazenado.\n");
    }

    return 0;
}

Este exemplo demonstra o uso seguro de todas as quatro funções, com validações que previnem estouros de buffer e garantem terminação correta das strings.

Referências