C Strings Dinâmicas

Até aqui, todo texto morava num array de tamanho fixo: char nome[100];. Se o nome tem 3 letras, sobram 96 bytes vazios; se tem 150, não cabe. Nesta aula você vai guardar textos em memória do tamanho exato, reservada com malloc, vai juntar textos, montar frases e ler linhas de qualquer comprimento. Tudo gira em torno de uma conta simples, que todo programador C já errou um dia: strlen(s) + 1.

Você vai aprender
  • Por que um texto de 3 letras precisa de 4 bytes (o + 1 do '\0').
  • Como copiar um texto para memória nova: à mão com malloc + strcpy e com strdup (padrão no C23).
  • Como juntar duas strings numa terceira, do tamanho certo.
  • Como montar uma frase com números usando snprintf para medir antes.
  • Como ler uma linha de qualquer tamanho: com fgets em pedaços e com getline (POSIX).
  • Os erros clássicos: esquecer o + 1, devolver um array local, dar free num texto entre aspas.

Todos os programas desta aula 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. Antes desta aula, vale ter visto Strings, Funções de String e malloc e free.


A ideia: uma etiqueta sob medida

Pense numa etiqueta de nome para crachá. Com char nome[100], a Ana sempre compra a etiqueta gigante, de 100 espaços, mesmo para escrever "Bia". Com uma string dinâmica, ela mede o nome primeiro e corta a etiqueta do tamanho exato.

Só que toda string em C termina com um caractere invisível, o '\0' (o "fim de texto", veja Strings). É ele que diz às funções onde o texto acaba. O strlen conta as letras, mas não conta o '\0'. Por isso a etiqueta precisa de um espaço a mais:

TextoNa memóriastrlenBytes necessários
"Bia"'B' 'i' 'a' '\0'34
"Ana Souza"'A' 'n' 'a' ' ' 'S' 'o' 'u' 'z' 'a' '\0'910
"" (vazio)'\0'01

A regra, para decorar: para guardar uma cópia de s, reserve strlen(s) + 1 bytes. Como cada char ocupa exatamente 1 byte (sizeof(char) é sempre 1), não precisa multiplicar por nada.


1. Copiar um texto para memória nova

O programa lê o nome da Ana num buffer grande (temporário) e depois guarda uma cópia do tamanho exato. A cópia é feita por uma função própria, copiar:

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

char *copiar(const char *s) {
    size_t n = strlen(s);
    char *nova = malloc(n + 1);
    if (nova == NULL) {
        return NULL;
    }
    strcpy(nova, s);
    return nova;
}

int main(void) {
    char buffer[100];
    printf("Seu nome: ");
    if (fgets(buffer, sizeof buffer, stdin) == NULL) {
        return 1;
    }
    buffer[strcspn(buffer, "\n")] = '\0';

    char *nome = copiar(buffer);
    if (nome == NULL) {
        printf("Sem memória!\n");
        return 1;
    }
    printf("\nGuardei \"%s\": %zu letras em %zu bytes\n", nome, strlen(nome), strlen(nome) + 1);
    free(nome);
    return 0;
}
char *copiar(const char *s)
Recebe o texto original (o const promete não mexer nele, veja const e Ponteiros) e devolve um ponteiro para a cópia nova.
size_t n = strlen(s);
Mede o texto: quantas letras, sem o '\0'.
char *nova = malloc(n + 1);
Reserva as letras mais um byte para o '\0'. Este + 1 é o coração da aula.
if (nova == NULL) return NULL;
Sem memória, a função avisa quem chamou devolvendo NULL.
strcpy(nova, s);
Copia as letras e o '\0'. O strcpy é perigoso quando o destino pode ser pequeno demais; aqui é seguro, porque o destino acabou de ser reservado com o tamanho exato.
return nova;
Devolve o endereço da memória reservada. Ela continua existindo depois que a função termina (memória do heap só some com free).
buffer[strcspn(buffer, "\n")] = '\0';
Tira o Enter que o fgets guarda no fim (veja fgets).
free(nome);
Quem recebeu a cópia é dono dela e tem de liberá-la. Combine isso sempre: uma função que devolve memória nova deixa o free para quem chamou.
$ gcc -std=c23 -Wall -Wextra copia.c -o copia
$ ./copia
Seu nome: Ana Souza

Guardei "Ana Souza": 9 letras em 10 bytes

O buffer de 100 bytes é temporário: some quando o main termina e pode ser reaproveitado para ler outro nome. A cópia em nome ocupa só os 10 bytes necessários.

Missão: escreva o corpo da função copiar: reserve n + 1 bytes com malloc, devolva NULL se ele falhar, copie o texto com strcpy e devolva o ponteiro. O main troca a primeira letra da cópia, e a tela deve mostrar Original: Ana Souza e Cópia: Ena Souza (o original não muda).

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

char *copiar(const char *s) {
    size_t n = strlen(s);
    /* 1. reserve n + 1 bytes com malloc
       2. se o malloc devolver NULL, devolva NULL
       3. copie s para a memória nova com strcpy e devolva o ponteiro */
    return NULL;
}

int main(void) {
    const char *original = "Ana Souza";
    char *copia = copiar(original);
    if (copia == NULL) {
        printf("copiar devolveu NULL\n");
        return 1;
    }
    copia[0] = 'E';
    printf("Original: %s\n", original);
    printf("Cópia: %s\n", copia);
    free(copia);
    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *copiar(const char *s) {
    size_t n = strlen(s);
    char *nova = malloc(n + 1);
    if (nova == NULL) {
        return NULL;
    }
    strcpy(nova, s);
    return nova;
}

int main(void) {
    const char *original = "Ana Souza";
    char *copia = copiar(original);
    if (copia == NULL) {
        printf("copiar devolveu NULL\n");
        return 1;
    }
    copia[0] = 'E';
    printf("Original: %s\n", original);
    printf("Cópia: %s\n", copia);
    free(copia);
    return 0;
}

Repare que o código inicial dá o aviso unused variable 'n' ("variável n não usada"): é o compilador lembrando que você mediu o texto e ainda não usou a medida.


2. strdup: a cópia pronta C23

Copiar um texto para memória nova é tão comum que existe uma função pronta: strdup (de string duplicate, "duplicar string"). Ela faz exatamente o que a sua copiar faz: mede, reserva strlen + 1, copia e devolve o ponteiro (ou NULL se faltar memória).

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

int main(void) {
    const char *original = "Ana Souza";
    char *copia = strdup(original);
    if (copia == NULL) {
        printf("Sem memória!\n");
        return 1;
    }
    copia[0] = 'E';
    printf("Original: %s\n", original);
    printf("Cópia:    %s\n", copia);
    free(copia);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra dup.c -o dup
$ ./dup
Original: Ana Souza
Cópia:    Ena Souza

Por décadas, strdup existiu em todo Linux e macOS (ela faz parte do POSIX, o padrão dos sistemas tipo Unix), mas não fazia parte do C padrão. Só entrou no padrão no C23, junto com a irmã strndup. Com um padrão antigo e estrito, o gcc nem a reconhece:

$ gcc -std=c17 -Wall -Wextra dup.c -o dup
dup.c: In function ‘main’:
dup.c:7:19: error: implicit declaration of function ‘strdup’; did you mean ‘strcmp’? [-Wimplicit-function-declaration]
    7 |     char *copia = strdup(original);
      |                   ^~~~~~
      |                   strcmp
dup.c:7:19: error: initialization of ‘char *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
$ gcc -std=gnu17 -Wall -Wextra dup.c -o dup     « com as extensões GNU/POSIX, compila
$

implicit declaration of function ‘strdup’ = "a função strdup não foi declarada": no modo -std=c17 estrito, o string.h esconde o que não é C17. O segundo erro é consequência do primeiro. Com -std=c23 (o padrão do guia) ou -std=gnu17, funciona.

strndup: copiar só o começo

strndup(s, n) copia no máximo n letras de s e sempre põe o '\0' no fim. strndup("Arroz, feijão, café", 5) devolve um "Arroz" novinho (6 bytes). No gcc 15 com -std=c23, funciona direto. No editor do guia, a biblioteca C (wasi-libc) ainda só mostra o strndup no modo POSIX: escreva #define _POSIX_C_SOURCE 200809L na primeira linha do arquivo, antes dos #include.


3. Juntar duas strings em memória nova

Para juntar "Bom dia, " e "Ana!", o strcat sozinho não serve: ele escreve no fim da primeira string, e um texto entre aspas não pode ser mudado (nem tem espaço sobrando). O certo é reservar uma terceira área, com espaço para os dois textos e um '\0':

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

char *juntar(const char *a, const char *b) {
    char *nova = malloc(strlen(a) + strlen(b) + 1);
    if (nova == NULL) {
        return NULL;
    }
    strcpy(nova, a);
    strcat(nova, b);
    return nova;
}

int main(void) {
    char *saudacao = juntar("Bom dia, ", "Ana!");
    if (saudacao == NULL) {
        return 1;
    }
    printf("%s\n", saudacao);
    printf("%zu letras\n", strlen(saudacao));
    free(saudacao);
    return 0;
}
LinhaO que faz
malloc(strlen(a) + strlen(b) + 1)9 letras de "Bom dia, " + 4 de "Ana!" + 1 do '\0' = 14 bytes. Um '\0' só: o do primeiro texto é substituído pelo segundo.
strcpy(nova, a);Copia o primeiro texto (com o seu '\0') para o começo da área nova.
strcat(nova, b);String concatenate, "emendar strings": acha o '\0' de nova e escreve b a partir dali, com um '\0' novo no fim.
$ gcc -std=c23 -Wall -Wextra juntar.c -o juntar
$ ./juntar
Bom dia, Ana!
13 letras

13 letras em 14 bytes. A função não mexe em a nem em b (const), e quem chama recebe uma string nova, que pode mudar e precisa liberar.

Textos com números: meça com snprintf

E se a frase tem números, como "Ana tem 29 anos"? Quantos bytes ocupa o 29? E se fosse 1234567? Em vez de adivinhar, peça para o snprintf medir: com destino NULL e tamanho 0, ele não escreve nada e só devolve quantas letras a frase teria (veja Texto ↔ Número).

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

int main(void) {
    const char *nome = "Ana";
    int idade = 29;

    int n = snprintf(NULL, 0, "%s tem %d anos", nome, idade);
    if (n < 0) {
        return 1;
    }
    char *frase = malloc((size_t)n + 1);
    if (frase == NULL) {
        return 1;
    }
    snprintf(frase, (size_t)n + 1, "%s tem %d anos", nome, idade);

    printf("%s (%d letras)\n", frase, n);
    free(frase);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra formatar.c -o formatar
$ ./formatar
Ana tem 29 anos (15 letras)

São duas chamadas com o mesmo formato: a primeira mede (15), a segunda escreve de verdade nos 16 bytes reservados. O n < 0 confere o caso raro de erro no formato. O (size_t) converte o int devolvido pelo snprintf para o tipo de tamanhos (veja Conversão de Tipos).


4. Ler uma linha de qualquer tamanho

O fgets lê uma linha, mas para num tamanho máximo: com char buffer[100], uma linha de 300 letras chega em pedaços. Juntando o fgets com o array que cresce, dá para ler linhas de qualquer tamanho: começa com 16 bytes e, enquanto a linha não terminar (o '\n' ainda não chegou), dobra o espaço e lê mais um pedaço.

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

char *ler_linha(FILE *f) {
    size_t capacidade = 16;
    size_t tamanho = 0;
    char *linha = malloc(capacidade);
    if (linha == NULL) {
        return NULL;
    }

    while (fgets(linha + tamanho, (int)(capacidade - tamanho), f) != NULL) {
        tamanho += strlen(linha + tamanho);
        if (linha[tamanho - 1] == '\n') {
            linha[tamanho - 1] = '\0';
            return linha;
        }
        char *maior = realloc(linha, capacidade * 2);
        if (maior == NULL) {
            free(linha);
            return NULL;
        }
        linha = maior;
        capacidade = capacidade * 2;
        printf("  (o buffer cresceu para %zu bytes)\n", capacidade);
    }

    if (tamanho == 0) {
        free(linha);
        return NULL;
    }
    return linha;
}

int main(void) {
    char *linha;
    while ((linha = ler_linha(stdin)) != NULL) {
        printf("[%s] (%zu bytes)\n", linha, strlen(linha));
        free(linha);
    }
    return 0;
}
fgets(linha + tamanho, (int)(capacidade - tamanho), f)
Lê o próximo pedaço depois do que já foi lido (linha + tamanho é o endereço da primeira posição livre, veja Ponteiros e Arrays), usando só o espaço que sobra. O fgets pede um int como tamanho, daí o (int).
tamanho += strlen(linha + tamanho);
Soma quantas letras vieram neste pedaço.
if (linha[tamanho - 1] == '\n')
O último caractere lido é o Enter: a linha acabou. Troca o '\n' por '\0' e devolve.
realloc(linha, capacidade * 2)
Não acabou: o buffer lotou no meio da linha. Dobra o espaço (com a variável auxiliar, como sempre) e o laço lê o próximo pedaço.
printf(" (o buffer cresceu …")
Só para você ver o buffer crescendo. Num programa de verdade, tire esta linha.
if (tamanho == 0) { free(linha); return NULL; }
O fgets falhou sem ler nada: a entrada acabou. Devolver NULL avisa o main para parar. Se leu alguma coisa (uma última linha sem Enter no fim), devolve o que leu.
free(linha);
No main: cada linha devolvida é memória nova, liberada depois de usada.
$ cat lista.txt
arroz
feijão preto, de preferência o da marca que a vó Rita gosta, pacote de 1 kg
café
$ gcc -std=c23 -Wall -Wextra -g lerlinha.c -o lerlinha
$ ./lerlinha < lista.txt
[arroz] (5 bytes)
  (o buffer cresceu para 32 bytes)
  (o buffer cresceu para 64 bytes)
  (o buffer cresceu para 128 bytes)
[feijão preto, de preferência o da marca que a vó Rita gosta, pacote de 1 kg] (78 bytes)
[café] (5 bytes)
$ valgrind --leak-check=full ./lerlinha < lista.txt 2>&1 | tail -6
==52904==   total heap usage: 9 allocs, 9 frees, 8,480 bytes allocated
==52904==
==52904== All heap blocks were freed -- no leaks are possible
==52904==
==52904== For lists of detected and suppressed errors, rerun with: -s
==52904== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

A linha do feijão coube depois de três dobras (16 → 32 → 64 → 128). café tem 4 letras e 5 bytes porque o é ocupa 2 bytes em UTF-8 (veja Acentos e UTF-8); por isso o programa diz "bytes", e não "letras". O valgrind confirma: 9 blocos reservados, 9 liberados, nenhum erro.

Experimente: rode e veja o buffer crescer na linha comprida. Depois troque a capacidade inicial de 16 para 4 ou para 256 e compare quantas vezes ele cresce.

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

char *ler_linha(FILE *f) {
    size_t capacidade = 16;
    size_t tamanho = 0;
    char *linha = malloc(capacidade);
    if (linha == NULL) {
        return NULL;
    }

    while (fgets(linha + tamanho, (int)(capacidade - tamanho), f) != NULL) {
        tamanho += strlen(linha + tamanho);
        if (linha[tamanho - 1] == '\n') {
            linha[tamanho - 1] = '\0';
            return linha;
        }
        char *maior = realloc(linha, capacidade * 2);
        if (maior == NULL) {
            free(linha);
            return NULL;
        }
        linha = maior;
        capacidade = capacidade * 2;
        printf("  (o buffer cresceu para %zu bytes)\n", capacidade);
    }

    if (tamanho == 0) {
        free(linha);
        return NULL;
    }
    return linha;
}

int main(void) {
    char *linha;
    while ((linha = ler_linha(stdin)) != NULL) {
        printf("[%s] (%zu bytes)\n", linha, strlen(linha));
        free(linha);
    }
    return 0;
}

O jeito POSIX: getline

No Linux e no macOS existe uma função pronta que faz tudo isso: getline. Ela é do POSIX, não do C padrão (nem no C23). Por isso, com -std=c23 é preciso pedir as funções POSIX com uma linha antes de todos os #include:

#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    char *linha = NULL;
    size_t capacidade = 0;
    ssize_t lidos;

    while ((lidos = getline(&linha, &capacidade, stdin)) != -1) {
        printf("Li %zd caracteres (o buffer tem %zu): %s", lidos, capacidade, linha);
    }
    free(linha);
    return 0;
}
#define _POSIX_C_SOURCE 200809L
"Quero as funções do POSIX de 2008." Tem de vir antes dos #include. (Sobre #define, veja Pré-processador.)
char *linha = NULL; size_t capacidade = 0;
Começa sem buffer. O getline reserva e aumenta o buffer sozinho, e atualiza as duas variáveis (por isso recebe os endereços delas).
ssize_t lidos
O getline devolve quantos caracteres leu, contando o '\n', ou -1 no fim da entrada. ssize_t é o "size_t com sinal" do POSIX, que aceita o -1; no printf, é %zd.
free(linha);
Um free só, no fim: o mesmo buffer é reaproveitado a cada linha.
$ gcc -std=c23 -Wall -Wextra getline.c -o getline
$ ./getline < nomes.txt
Li 4 caracteres (o buffer tem 120): Ana
Li 58 caracteres (o buffer tem 120): linha bem mais comprida que a primeira, com muitas letras

Repare que o '\n' continua na linha (por isso o printf não tem \n no fim) e que a glibc começou com um buffer de 120 bytes. No editor do guia, a biblioteca é outra e os números do buffer saem diferentes (6 e 60), mas o resultado é o mesmo. Sem o #define, o gcc não conhece nem o getline nem o ssize_t:

$ gcc -std=c23 -Wall -Wextra getline.c -o getline      « sem a linha do #define
getline.c: In function ‘main’:
getline.c:7:5: error: unknown type name ‘ssize_t’; did you mean ‘size_t’?
    7 |     ssize_t lidos;
      |     ^~~~~~~
      |     size_t
getline.c:9:21: error: implicit declaration of function ‘getline’ [-Wimplicit-function-declaration]
    9 |     while ((lidos = getline(&linha, &capacidade, stdin)) != -1) {
      |                     ^~~~~~~
«…»

Se o gcc for chamado sem -std (ele usa gnu23, com as extensões GNU e POSIX ligadas), compila mesmo sem o #define. O getline não existe no Windows com o compilador da Microsoft; lá, use a versão com fgets.


Formas de criar uma string dinâmica: qual usar

FormaExemploQuando usar
malloc + strcpychar *c = malloc(strlen(s) + 1); strcpy(c, s);Para entender o que acontece; em código que precisa compilar com padrões antigos sem POSIX.
strdup C23char *c = strdup(s);Copiar um texto inteiro. A escolha do dia a dia.
strndup C23char *c = strndup(s, 5);Copiar só o começo de um texto.
malloc + strcpy + strcatmalloc(strlen(a) + strlen(b) + 1)Juntar textos prontos.
snprintf(NULL, 0, …) + mallocint n = snprintf(NULL, 0, "%s tem %d anos", …);Montar uma frase com números ou vários pedaços.
fgets em pedaços + reallocler_linha(stdin) desta aulaLer linhas de qualquer tamanho em C padrão (funciona em qualquer sistema).
getline (POSIX)getline(&linha, &cap, stdin)Ler linhas de qualquer tamanho no Linux e no macOS.

Em todas, a regra é a mesma: quem recebe a string nova chama free quando terminar de usá-la.


Quando dá errado

Esqueceu o + 1: "Invalid write of size 1"
    char *nova = malloc(n);        /* faltou o + 1 */
$ gcc -std=c23 -Wall -Wextra -g semmais1.c -o semmais1
$ echo "Ana Souza" | valgrind ./semmais1
«… cabeçalho …»
==52232== Invalid write of size 1
==52232==    at 0x485A2F6: strcpy (vg_replace_strmem.c:565)
==52232==    by 0x4001299: copiar (semmais1.c:11)
==52232==    by 0x4001320: main (semmais1.c:23)
==52232==  Address 0x4aa80c9 is 0 bytes after a block of size 9 alloc'd
==52232==    at 0x4850858: malloc (vg_replace_malloc.c:447)
==52232==    by 0x4001274: copiar (semmais1.c:7)
==52232==    by 0x4001320: main (semmais1.c:23)
==52232==
==52232== Invalid read of size 1
==52232==    at 0x485A174: strlen (vg_replace_strmem.c:506)
==52232==    by 0x4001356: main (semmais1.c:28)
«… mais duas leituras inválidas, no strlen e no printf …»
Seu nome:
Guardei "Ana Souza": 9 letras em 10 bytes
«…»
==52232== ERROR SUMMARY: 4 errors from 4 contexts (suppressed: 0 from 0)

O que significa: o strcpy da linha 11 escreveu 1 byte (Invalid write of size 1) logo depois (0 bytes after) de um bloco de 9 bytes: é o '\0', que não tinha lugar. Depois, cada função que procura o fim do texto (strlen, printf) lê esse byte que não é seu. O programa até mostrou a frase certa; no editor do guia também roda sem reclamar. É o tipo de erro que fica escondido por meses e um dia corrompe outro dado.

Como resolver: malloc(strlen(s) + 1), sempre. Ou use strdup, que já faz a conta certa.

Devolveu um array local: "function returns address of local variable"
#include <stdio.h>
#include <string.h>

char *copiar(const char *s) {
    char nova[100];
    strcpy(nova, s);
    return nova;
}

int main(void) {
    char *nome = copiar("Ana Souza");
    printf("Nome: %s\n", nome);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra local.c -o local
local.c: In function ‘copiar’:
local.c:7:12: warning: function returns address of local variable [-Wreturn-local-addr]
    7 |     return nova;
      |            ^~~~
$ ./local
Nome: (null)

O que significa: "a função devolve o endereço de uma variável local". O array nova mora na pilha e deixa de existir quando copiar termina; o endereço devolvido aponta para o nada. O gcc, sabendo disso, devolveu NULL no lugar, e o printf mostrou (null). No editor do guia o Clang avisa com outras palavras (address of stack memory associated with local variable 'nova' returned) e o programa, por sorte, mostra o nome. Os dois estão errados: é comportamento indefinido.

Como resolver: reserve a cópia com malloc (ou strdup), como na seção 1. Memória do heap continua existindo depois que a função termina.

free num texto entre aspas: "free(): invalid pointer"
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    char *nome = "Ana";
    printf("Nome: %s\n", nome);
    free(nome);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra literal.c -o literal
literal.c: In function ‘main’:
literal.c:7:5: warning: ‘free’ called on a pointer to an unallocated object ‘"Ana"’ [-Wfree-nonheap-object]
    7 |     free(nome);
      |     ^~~~~~~~~~
literal.c:5:11: note: assigned here
    5 |     char *nome = "Ana";
      |           ^~~~
$ ./literal
Nome: Ana
free(): invalid pointer
Abortado                   (imagem do núcleo gravada) ./literal

O que significa: o aviso diz "free chamado num ponteiro para um objeto que não foi reservado". O texto "Ana" entre aspas não veio do malloc: faz parte do próprio programa. Ao recebê-lo, a glibc percebeu e abortou com free(): invalid pointer ("ponteiro inválido"). No editor do guia, o programa morre com Falha de segmentação.

Como resolver: só passe para o free o que veio de malloc, calloc, realloc, strdup, strndup ou getline. Se uma variável às vezes aponta para um texto fixo e às vezes para memória reservada, use strdup("Ana") também no caso fixo, para o free valer sempre.

"implicit declaration of function ‘strdup’" ou ‘getline’

O que aconteceu: o padrão escolhido esconde a função. strdup só é C padrão a partir do C23; getline não é C padrão em nenhuma versão (é POSIX).

Como resolver: para strdup, compile com -std=c23 (ou -std=gnu17). Para getline, ponha #define _POSIX_C_SOURCE 200809L antes de todos os #include.

"definitely lost" no valgrind

O que aconteceu: alguma string nova (de strdup, juntar, ler_linha…) nunca recebeu o seu free. É fácil esquecer quando a função "parece" só devolver um texto.

Como resolver: toda função que devolve char * com memória nova deve dizer isso num comentário (/* devolve memória nova: libere com free */), e quem a chama libera. Veja Vazamentos de Memória.


Referência rápida

EscritaSignificado
malloc(strlen(s) + 1)Espaço para uma cópia de s: as letras mais o '\0'.
strcpy(destino, s)Copia s com o '\0' (o destino tem de caber).
strcat(destino, s)Emenda s no fim de destino (que tem de ter espaço sobrando).
strdup(s) C23Cópia nova de s; NULL se faltar memória.
strndup(s, n) C23Cópia nova de no máximo n letras de s.
malloc(strlen(a) + strlen(b) + 1)Espaço para juntar a e b.
snprintf(NULL, 0, formato, …)Não escreve nada; devolve quantas letras a frase teria.
getline(&linha, &cap, stdin)POSIX: lê uma linha inteira, de qualquer tamanho, com o '\n'; -1 no fim. Precisa de #define _POSIX_C_SOURCE 200809L com -std=c23.
free(s)Libera uma string que veio de malloc, strdup, getline… Nunca um texto entre aspas.

Exercícios

1. Quantos bytes?

Quantos bytes são necessários para guardar uma cópia de "Carla"?

2. Reservar para copiar

Complete para reservar espaço para uma cópia de nome:

char *copia = malloc();

3. A função pronta

Complete com a função do C23 que faz a cópia inteira de uma vez:

char *copia = ;

4. Para juntar a e b numa string nova, quantos bytes reservar?

5. Qual destes ponteiros não pode ir para o free?

6. Sobre o getline, o que é verdade?

Missão final: o nome completo

Missão: a função nome_completo deve devolver uma string nova com o nome, um espaço e o sobrenome. Hoje ela reserva espaço de menos e esquece o espaço e o sobrenome. Corrija o tamanho do malloc (cabem o nome, o espaço, o sobrenome e o '\0') e complete com strcat. A tela deve mostrar [Ana Souza] (9 letras) e [Bia Lima] (8 letras).

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

/* devolve memória nova: libere com free */
char *nome_completo(const char *nome, const char *sobrenome) {
    size_t tamanho = strlen(nome) + strlen(sobrenome);   /* falta espaço! */
    char *nova = malloc(tamanho);
    if (nova == NULL) {
        return NULL;
    }
    strcpy(nova, nome);
    /* acrescente o espaço e o sobrenome com strcat */
    return nova;
}

int main(void) {
    char *a = nome_completo("Ana", "Souza");
    char *b = nome_completo("Bia", "Lima");
    if (a == NULL || b == NULL) {
        free(a);
        free(b);
        return 1;
    }
    printf("[%s] (%zu letras)\n", a, strlen(a));
    printf("[%s] (%zu letras)\n", b, strlen(b));
    free(a);
    free(b);
    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* devolve memória nova: libere com free */
char *nome_completo(const char *nome, const char *sobrenome) {
    size_t tamanho = strlen(nome) + 1 + strlen(sobrenome) + 1;
    char *nova = malloc(tamanho);
    if (nova == NULL) {
        return NULL;
    }
    strcpy(nova, nome);
    strcat(nova, " ");
    strcat(nova, sobrenome);
    return nova;
}

int main(void) {
    char *a = nome_completo("Ana", "Souza");
    char *b = nome_completo("Bia", "Lima");
    if (a == NULL || b == NULL) {
        free(a);
        free(b);
        return 1;
    }
    printf("[%s] (%zu letras)\n", a, strlen(a));
    printf("[%s] (%zu letras)\n", b, strlen(b));
    free(a);
    free(b);
    return 0;
}

Resumo

  • Uma string de n letras ocupa n + 1 bytes: o último é o '\0', que o strlen não conta.
  • Para copiar: malloc(strlen(s) + 1) + strcpy, ou simplesmente strdup(s) (C padrão desde o C23; antes, POSIX).
  • Para juntar: malloc(strlen(a) + strlen(b) + 1), strcpy e strcat.
  • Para frases com números: meça com snprintf(NULL, 0, …), reserve n + 1 e escreva com snprintf.
  • Para ler linhas de qualquer tamanho: fgets em pedaços com um buffer que dobra, ou getline (POSIX, com _POSIX_C_SOURCE).
  • Nunca devolva um array local; nunca dê free num texto entre aspas; toda string nova tem o seu free.