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.
- Por que um texto de 3 letras precisa de 4 bytes (o
+ 1do'\0'). - Como copiar um texto para memória nova: à mão com
malloc+strcpye comstrdup(padrão no C23). - Como juntar duas strings numa terceira, do tamanho certo.
- Como montar uma frase com números usando
snprintfpara medir antes. - Como ler uma linha de qualquer tamanho: com
fgetsem pedaços e comgetline(POSIX). - Os erros clássicos: esquecer o
+ 1, devolver um array local, darfreenum 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:
| Texto | Na memória | strlen | Bytes necessários |
|---|---|---|---|
"Bia" | 'B' 'i' 'a' '\0' | 3 | 4 |
"Ana Souza" | 'A' 'n' 'a' ' ' 'S' 'o' 'u' 'z' 'a' '\0' | 9 | 10 |
"" (vazio) | '\0' | 0 | 1 |
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
constpromete 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'. Ostrcpyé 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
fgetsguarda 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
freepara 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;
}
| Linha | O 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. Ofgetspede umintcomo 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
fgetsfalhou sem ler nada: a entrada acabou. DevolverNULLavisa omainpara 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
getlinereserva e aumenta o buffer sozinho, e atualiza as duas variáveis (por isso recebe os endereços delas). - ssize_t lidos
- O
getlinedevolve quantos caracteres leu, contando o'\n', ou-1no fim da entrada.ssize_té o "size_tcom sinal" do POSIX, que aceita o-1; noprintf, é%zd. - free(linha);
- Um
freesó, 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
| Forma | Exemplo | Quando usar |
|---|---|---|
malloc + strcpy | char *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 C23 | char *c = strdup(s); | Copiar um texto inteiro. A escolha do dia a dia. |
strndup C23 | char *c = strndup(s, 5); | Copiar só o começo de um texto. |
malloc + strcpy + strcat | malloc(strlen(a) + strlen(b) + 1) | Juntar textos prontos. |
snprintf(NULL, 0, …) + malloc | int n = snprintf(NULL, 0, "%s tem %d anos", …); | Montar uma frase com números ou vários pedaços. |
fgets em pedaços + realloc | ler_linha(stdin) desta aula | Ler 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
+ 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.
#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.
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.
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
| Escrita | Significado |
|---|---|
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) C23 | Cópia nova de s; NULL se faltar memória. |
strndup(s, n) C23 | Có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:
3. A função pronta
Complete com a função do C23 que faz a cópia inteira de uma vez:
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
nletras ocupan + 1bytes: o último é o'\0', que ostrlennão conta. - Para copiar:
malloc(strlen(s) + 1)+strcpy, ou simplesmentestrdup(s)(C padrão desde o C23; antes, POSIX). - Para juntar:
malloc(strlen(a) + strlen(b) + 1),strcpyestrcat. - Para frases com números: meça com
snprintf(NULL, 0, …), reserven + 1e escreva comsnprintf. - Para ler linhas de qualquer tamanho:
fgetsem pedaços com um buffer que dobra, ougetline(POSIX, com_POSIX_C_SOURCE). - Nunca devolva um array local; nunca dê
freenum texto entre aspas; toda string nova tem o seufree.