C Funções Variádicas
Você já chamou o printf com um argumento (printf("Olá\n")), com dois (printf("%d\n", idade)) e com cinco. Mas uma função em C tem uma lista fixa de parâmetros (veja Parâmetros). Como o printf aceita qualquer quantidade? Ele é uma função variádica: uma função que recebe um número variável de argumentos. Nesta aula você vai escrever as suas, com as ferramentas do cabeçalho stdarg.h, e vai ver por que elas são poderosas e perigosas ao mesmo tempo.
- O que significam as reticências
...na lista de parâmetros. - As quatro peças do
stdarg.h:va_list,va_start,va_argeva_end. - Os três jeitos de a função saber quantos argumentos chegaram: um contador, uma sentinela e uma string de formato.
- Por que
va_arg(args, float)eva_arg(args, char)estão errados (as promoções). - O
va_startde um argumento só, novidade do C23. - Como escrever uma função de log que funciona como o
printf, comvprintf.
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/variadicas, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa (veja Compilar com gcc). Você pode repetir cada um no editor do guia, que compila C de verdade no navegador.
A ideia: uma sacola sem etiquetas
Imagine que a Ana entrega uma sacola de compras para o filho guardar. Dentro há uma quantidade qualquer de coisas, uma atrás da outra. O filho só consegue tirar um item por vez, na ordem em que foram colocados, e os itens não têm etiqueta. Para guardar tudo direito, ele precisa de um bilhete da mãe dizendo duas coisas:
- O que é cada item ("primeiro um pacote de arroz, depois uma garrafa de leite…"), para saber como pegar e onde guardar.
- Quando parar ("são 3 itens", ou "pare quando achar o papel escrito FIM").
Uma função variádica é o filho. Os argumentos extras chegam na sacola, sem etiqueta de tipo. O bilhete é o que a função recebe antes das reticências: no printf, é o formato. Em printf("%s tem %d anos\n", nome, idade), o "%s tem %d anos\n" diz: "tire um texto, depois um int, e acabou". Se o bilhete mentir, o filho guarda o leite no armário de panelas, e ninguém avisa.
1. As reticências: ...
Uma função variádica se declara com ... (três pontos) no fim da lista de parâmetros. É assim que o printf está declarado dentro do stdio.h (simplificado):
int printf(const char *formato, ...);
- const char *formato
- Um parâmetro nomeado, normal: o texto com os
%d,%s…. É o "bilhete". - ...
- "E mais quantos argumentos você quiser, de qualquer tipo". O nome técnico em português é reticências; em inglês, ellipsis. A palavra "variádica" vem do inglês variadic, "de quantidade variável".
As reticências sempre vêm por último. int f(..., int x) não compila. Até o C17, a função precisava ter pelo menos um parâmetro nomeado antes do ...; o C23 liberou isso (seção 5).
2. Sua primeira função variádica: somar N números
Para ler os argumentos que vieram nas reticências, use o cabeçalho stdarg.h (standard arguments, "argumentos padrão"). Esta função soma quantos números você passar. O primeiro argumento diz quantos vêm depois:
#include <stdarg.h>
#include <stdio.h>
int soma(int quantos, ...) {
va_list args;
va_start(args, quantos);
int total = 0;
for (int i = 0; i < quantos; i++) {
total += va_arg(args, int);
}
va_end(args);
return total;
}
int main(void) {
printf("%d\n", soma(3, 10, 20, 30));
printf("%d\n", soma(5, 1, 2, 3, 4, 5));
printf("%d\n", soma(0));
return 0;
}
$ gcc -std=c23 -Wall -Wextra soma.c -o soma $ ./soma 60 15 0
A mesma função recebeu 3 números, depois 5, depois nenhum. Linha por linha:
| Linha | O que faz |
|---|---|
#include <stdarg.h> | Traz as quatro peças abaixo. Sem ele, nada disso existe. |
int soma(int quantos, ...) | Um parâmetro nomeado (quantos, o "bilhete") e as reticências. |
va_list args; | Cria a "mão que tira itens da sacola". va_list = variable argument list, "lista de argumentos variáveis". É um tipo especial; você nunca olha dentro dele. |
va_start(args, quantos); | Põe a mão na sacola, logo depois do último parâmetro nomeado (quantos). Tem de vir antes de qualquer va_arg. |
va_arg(args, int) | Tira o próximo argumento, dizendo que ele é um int, e avança. Cada chamada pega um; não dá para voltar. |
va_end(args); | Tira a mão da sacola. Todo va_start tem o seu va_end, na mesma função, como todo malloc tem o seu free. |
va_start, va_arg e va_end não são funções
São macros (veja Macros com Parâmetros): o compilador as troca por um código especial, que sabe onde os argumentos ficaram guardados. É por isso que va_arg(args, int) pode receber um tipo (int) como argumento, coisa que uma função normal não aceita.
Experimente: rode. Depois acrescente uma chamada soma(4, 2, 4, 6, 8) e mostre o resultado. Tente também soma(1, 99).
#include <stdarg.h>
#include <stdio.h>
int soma(int quantos, ...) {
va_list args;
va_start(args, quantos);
int total = 0;
for (int i = 0; i < quantos; i++) {
total += va_arg(args, int);
}
va_end(args);
return total;
}
int main(void) {
printf("%d\n", soma(3, 10, 20, 30));
printf("%d\n", soma(2, 7, 8));
return 0;
}
3. Como a função sabe quando parar
Dentro da função, não existe jeito de perguntar "quantos argumentos vieram?" nem "de que tipo é o próximo?". Quem chama tem de contar. Há três jeitos de combinar isso:
| Jeito | Como funciona | Exemplo |
|---|---|---|
| Contador | Um parâmetro nomeado diz quantos argumentos vêm depois. | soma(3, 10, 20, 30) |
| Sentinela | Um valor especial marca o fim, como o '\0' de uma string. | lista("arroz", "café", NULL) |
| Formato | Um texto diz quantos argumentos vêm e de que tipo. | printf("%s: %d\n", nome, n) |
A sentinela
A palavra vem do soldado que fica de guarda no fim do acampamento. Para uma lista de textos, a sentinela natural é NULL (veja Ponteiro Nulo), porque nenhum texto de verdade mora no endereço nulo:
#include <stdarg.h>
#include <stdio.h>
void lista_compras(const char *primeiro, ...) {
va_list args;
va_start(args, primeiro);
int n = 1;
for (const char *item = primeiro; item != NULL; item = va_arg(args, const char *)) {
printf("%d. %s\n", n, item);
n++;
}
va_end(args);
}
int main(void) {
lista_compras("arroz", "feijão", "café", NULL);
return 0;
}
$ gcc -std=c23 -Wall -Wextra sentinela.c -o sentinela $ ./sentinela 1. arroz 2. feijão 3. café
- const char *item = primeiro
- O laço começa pelo parâmetro nomeado, que já é o primeiro item.
- item != NULL
- Continua enquanto não achar a sentinela.
- item = va_arg(args, const char *)
- Tira o próximo argumento da sacola, como um texto (um
const char *; veja Ponteiros e Strings). - NULL
- Na chamada, o último argumento. Esquecê-lo é um erro grave (veja Quando dá errado).
Experimente: acrescente mais dois itens à lista (antes do NULL) e rode.
#include <stdarg.h>
#include <stdio.h>
void lista_compras(const char *primeiro, ...) {
va_list args;
va_start(args, primeiro);
int n = 1;
for (const char *item = primeiro; item != NULL; item = va_arg(args, const char *)) {
printf("%d. %s\n", n, item);
n++;
}
va_end(args);
}
int main(void) {
lista_compras("arroz", "feijão", "café", NULL);
return 0;
}
O formato
É o jeito do printf e do scanf: a função lê o texto, e cada % diz "tire mais um argumento, deste tipo". O %d vira um va_arg(args, int); o %s, um va_arg(args, char *); o %f, um va_arg(args, double). É o jeito mais flexível, e o printf de verdade é feito assim. Você vai usar esse poder na seção 6, sem precisar reescrever o printf.
4. Os tipos que chegam: as promoções
Quando um argumento entra pelas reticências, o C aplica as promoções padrão de argumentos (default argument promotions), porque a função não diz que tipo espera:
| Você passa | Chega na sacola como | Então leia com |
|---|---|---|
char, signed char, unsigned char | int | va_arg(args, int) |
short, unsigned short | int | va_arg(args, int) |
bool | int | va_arg(args, int) |
float | double | va_arg(args, double) |
int, long, double, ponteiros… | o mesmo tipo | o mesmo tipo |
É por isso que o printf usa o mesmo %f para float e para double: os dois chegam como double (veja Formatos do printf). E é por isso que va_arg(args, float) está errado: não existe float na sacola.
#include <stdarg.h>
#include <stdio.h>
double media(int quantos, ...) {
va_list args;
va_start(args, quantos);
double total = 0;
for (int i = 0; i < quantos; i++) {
total += va_arg(args, float);
}
va_end(args);
return total / quantos;
}
int main(void) {
float a = 7.5f, b = 8.5f;
printf("Média: %.2f\n", media(2, a, b));
return 0;
}
$ gcc -std=c23 -Wall -Wextra media.c -o media
In file included from media.c:1:
media.c: In function ‘media’:
media.c:9:31: warning: ‘float’ is promoted to ‘double’ when passed through ‘...’
9 | total += va_arg(args, float);
| ^
media.c:9:31: note: (so you should pass ‘double’ not ‘float’ to ‘va_arg’)
media.c:9:31: note: if this code is reached, the program will abort
$ ./media
Instrução ilegal (imagem do núcleo gravada) ./media
$ echo $?
132
O gcc foi bem claro, em três frases:
- ‘float’ is promoted to ‘double’ when passed through ‘...’
- "
floaté promovido adoublequando passa pelas reticências". - (so you should pass ‘double’ not ‘float’ to ‘va_arg’)
- "(então você deveria passar
double, e nãofloat, para ova_arg)". - if this code is reached, the program will abort
- "se este código for executado, o programa vai abortar". E abortou mesmo: o gcc pôs ali uma instrução proibida de propósito, e o sistema matou o programa com Instrução ilegal (sinal
SIGILL; código 132 = 128 + 4).
O Clang (o compilador do editor) avisa com outras palavras e não aborta: deixa o programa ler lixo.
$ clang -std=c23 -Wall -Wextra media.c -o media-clang
media.c:9:31: warning: second argument to 'va_arg' is of promotable type 'float'; this va_arg has undefined behavior because arguments will be promoted to 'double' [-Wvarargs]
9 | total += va_arg(args, float);
| ^~~~~
1 warning generated.
$ ./media-clang
Média: 0.00
Second argument to 'va_arg' is of promotable type 'float'; this va_arg has undefined behavior because arguments will be promoted to 'double' = "o segundo argumento do va_arg é de um tipo promovível, float; este va_arg tem comportamento indefinido, porque os argumentos serão promovidos a double". No laboratório saiu Média: 0.00; no editor do guia sai outro número qualquer. Comportamento indefinido de verdade (veja Comportamento Indefinido).
Missão: rode e veja o aviso e a média errada. Conserte o va_arg para a média sair Média: 8.00, sem nenhum aviso. (Não precisa mudar o main: os float continuam chegando como double.)
#include <stdarg.h>
#include <stdio.h>
double media(int quantos, ...) {
va_list args;
va_start(args, quantos);
double total = 0;
for (int i = 0; i < quantos; i++) {
total += va_arg(args, float);
}
va_end(args);
return total / quantos;
}
int main(void) {
float a = 7.5f, b = 8.5f;
printf("Média: %.2f\n", media(2, a, b));
return 0;
}
#include <stdarg.h>
#include <stdio.h>
double media(int quantos, ...) {
va_list args;
va_start(args, quantos);
double total = 0;
for (int i = 0; i < quantos; i++) {
total += va_arg(args, double);
}
va_end(args);
return total / quantos;
}
int main(void) {
float a = 7.5f, b = 8.5f;
printf("Média: %.2f\n", media(2, a, b));
return 0;
}
Com char acontece o mesmo: va_arg(args, char) dá o aviso ‘char’ is promoted to ‘int’ when passed through ‘...’ no gcc, e o programa também morre com Instrução ilegal. O certo é ler um int e guardar num char: char c = (char) va_arg(args, int);.
A promoção acontece só com os tipos pequenos. Se a função lê va_arg(args, int) e você passa 1.5 (um double), ou se ela lê double e você passa 8 (um int), ninguém converte nada: a função lê lixo. Na chamada de uma função que lê double, escreva 8.0, e não 8.
5. va_start com um argumento só C23
Até o C17, o va_start precisava de dois argumentos: a va_list e o nome do último parâmetro nomeado. O C23 simplificou: o segundo virou opcional.
#include <stdarg.h>
#include <stdio.h>
int soma(int quantos, ...) {
va_list args;
va_start(args);
int total = 0;
for (int i = 0; i < quantos; i++) {
total += va_arg(args, int);
}
va_end(args);
return total;
}
int main(void) {
printf("%d\n", soma(3, 10, 20, 30));
return 0;
}
$ gcc -std=c23 -Wall -Wextra c23.c -o c23
$ ./c23
60
$ gcc -std=c17 -Wall -Wextra c23.c -o c23
c23.c: In function ‘soma’:
c23.c:6:18: error: macro ‘va_start’ requires 2 arguments, but only 1 given
6 | va_start(args);
| ^
In file included from c23.c:1:
/usr/lib/gcc/x86_64-linux-gnu/15/include/stdarg.h:50:9: note: macro ‘va_start’ defined here
50 | #define va_start(v,l) __builtin_va_start(v,l)
| ^~~~~~~~
c23.c:6:5: error: ‘va_start’ undeclared (first use in this function)
6 | va_start(args);
| ^~~~~~~~
c23.c:6:5: note: each undeclared identifier is reported only once for each function it appears in
No C23 funciona; com -std=c17, o erro diz macro ‘va_start’ requires 2 arguments, but only 1 given ("a macro va_start exige 2 argumentos, mas só 1 foi dado"). O editor do guia (Clang, -std=c23) também aceita a forma nova. Se o seu código precisa compilar em compiladores antigos, use a forma de dois argumentos, que continua valendo no C23.
O C23 trouxe junto outra novidade: a função pode ter só as reticências, sem nenhum parâmetro nomeado, como void mostra(...). Com o va_start(args) de um argumento, ela consegue ler a sacola mesmo assim. O gcc 15 e o editor aceitam. Mas lembre: sem parâmetro nomeado, não há "bilhete", e a função continua precisando saber quantos argumentos ler (com uma sentinela, por exemplo).
6. vprintf: uma função de log como o printf
Um uso muito comum de função variádica é o log (o "diário de bordo" do programa): uma função que escreve mensagens com um prefixo, como [LOG], e aceita formatos como o printf. Você não precisa reescrever o printf: existe uma versão dele que recebe a sacola inteira, uma va_list, em vez dos argumentos soltos. É o vprintf (o v é de va_list):
#include <stdarg.h>
#include <stdio.h>
void registrar(const char *formato, ...) {
va_list args;
va_start(args, formato);
fprintf(stderr, "[LOG] ");
vfprintf(stderr, formato, args);
fprintf(stderr, "\n");
va_end(args);
}
int main(void) {
int estoque = 12;
registrar("programa começou");
registrar("estoque de %s: %d unidades", "café", estoque);
registrar("preço médio: R$ %.2f", 7.9);
return 0;
}
$ gcc -std=c23 -Wall -Wextra log.c -o log $ ./log [LOG] programa começou [LOG] estoque de café: 12 unidades [LOG] preço médio: R$ 7.90
- registrar(const char *formato, ...)
- A mesma "cara" do
printf: um formato e as reticências. - fprintf(stderr, "[LOG] ")
- Escreve o prefixo na saída de erros,
stderr, o lugar certo para mensagens de diagnóstico (veja stdin, stdout e stderr). - vfprintf(stderr, formato, args)
- Faz o trabalho do
printf: lê o formato e tira da sacolaargsum argumento por%. É a versãovdofprintf. - va_end(args)
- Quem chamou
va_startchamava_end. Ovfprintfusa a sacola, mas não a fecha.
Cada função da família printf tem a sua versão v:
| Com argumentos soltos | Com uma va_list | Escreve em |
|---|---|---|
printf(fmt, ...) | vprintf(fmt, args) | a tela (stdout) |
fprintf(arq, fmt, ...) | vfprintf(arq, fmt, args) | um arquivo ou stderr |
snprintf(buf, n, fmt, ...) | vsnprintf(buf, n, fmt, args) | uma string, sem passar de n bytes |
scanf(fmt, ...) | vscanf(fmt, args) | (lê da entrada) |
O compilador não confere o seu formato… a menos que você peça
O gcc confere os %d e %s de um printf (o aviso -Wformat). Mas na sua função ele não sabe que o primeiro parâmetro é um formato. Veja um erro passar calado:
#include <stdarg.h>
#include <stdio.h>
void registrar(const char *formato, ...) {
va_list args;
va_start(args, formato);
printf("[LOG] ");
vprintf(formato, args);
printf("\n");
va_end(args);
}
int main(void) {
registrar("estoque: %d unidades", "doze");
return 0;
}
$ gcc -std=c23 -Wall -Wextra logsem.c -o logsem « nenhum aviso $ ./logsem [LOG] estoque: -1248849909 unidades
O %d pediu um int, mas veio um texto; o vprintf leu lixo. O gcc e o Clang aceitam um atributo que diz "esta função funciona como o printf": [[gnu::format(printf, 1, 2)]]. O 1 é a posição do formato; o 2, a posição onde começam as reticências. Ele não faz parte do padrão C (o gnu:: avisa que é uma extensão do GNU; veja Atributos), mas os dois compiladores mais usados entendem:
#include <stdarg.h>
#include <stdio.h>
[[gnu::format(printf, 1, 2)]]
void registrar(const char *formato, ...) {
va_list args;
va_start(args, formato);
printf("[LOG] ");
vprintf(formato, args);
printf("\n");
va_end(args);
}
int main(void) {
registrar("estoque: %d unidades", "doze");
return 0;
}
$ gcc -std=c23 -Wall -Wextra logerro.c -o logerro
logerro.c: In function ‘main’:
logerro.c:15:26: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘char *’ [-Wformat=]
15 | registrar("estoque: %d unidades", "doze");
| ~^ ~~~~~~
| | |
| int char *
| %s
Agora o erro aparece na hora de compilar, com a sugestão de usar %s. No editor, o Clang avisa format specifies type 'int' but the argument has type 'char *'.
Formas diferentes: qual usar
| Forma | Bom para | Cuidado |
|---|---|---|
Contador (soma(3, a, b, c)) | Argumentos todos do mesmo tipo, em pouca quantidade | Se o número não bater com os argumentos, a função lê lixo |
Sentinela (..., NULL) | Listas de ponteiros (textos) | Esquecer o NULL faz a função ler além do fim |
Formato + vprintf | Mensagens, logs, relatórios | Use [[gnu::format(printf, …)]] para o compilador conferir |
Array + tamanho (soma(v, n)) | Muitos valores do mesmo tipo | Nenhum dos problemas acima: o compilador confere tudo |
Qual usar: quando todos os valores são do mesmo tipo, prefira um array e o tamanho, como int soma(const int v[], int n) (veja Ponteiros e Arrays). O compilador confere o tipo de cada coisa, e não há sacola sem etiqueta. Guarde as funções variádicas para quando os tipos mudam de chamada para chamada, como num log com formato. É o caso em que elas brilham.
Quando dá errado
As funções variádicas são uma das poucas partes do C em que o compilador não consegue conferir os tipos. Quase todo erro aqui é comportamento indefinido, sem mensagem.
int main(void) {
printf("%d\n", soma(4, 10, 20, 30)); // diz 4, passa 3
printf("%d\n", soma(2, 1.5, 2.5)); // passa double, a função lê int
return 0;
}
$ gcc -std=c23 -Wall -Wextra faltou.c -o faltou « nenhum aviso $ ./faltou 1931552444 707198992 $ ./faltou 1537287868 1258110992 « outro lixo a cada execução
O que significa: a primeira chamada mandou tirar 4 itens de uma sacola com 3; a segunda mandou tirar int onde havia double. Nenhum compilador avisa. No editor do guia, a primeira linha pode até sair 60, por sorte.
Como consertar: confira cada chamada. Se a função lê int, passe inteiros. Se você se pega errando a contagem, troque por um array e o tamanho.
lista_compras("arroz", "feijão", "café"); // faltou o NULL
$ gcc -std=c23 -Wall -Wextra semsent.c -o semsent « nenhum aviso $ ./semsent 1. arroz 2. feijão 3. café 4. feijão 5. �?��u 6. 7. 8. ���� 9. �@c��a 10. ~��� Falha de segmentação (imagem do núcleo gravada) ./semsent
O que significa: sem o NULL, a função continuou tirando "textos" da memória depois dos argumentos: lixo, até cair num endereço proibido. No editor do guia, o mesmo código pode parar certinho no item 3, por sorte.
Como consertar: ponha o NULL no fim de toda chamada. É fácil esquecer; por isso a sentinela combina melhor com poucas chamadas, ou com uma macro que acrescente o NULL sozinha.
warning: ‘float’ is promoted to ‘double’ when passed through ‘...’ e depois Instrução ilegal
O que significa: um va_arg(args, float) (ou char, short, bool). Esses tipos nunca chegam pela sacola: viram double ou int. O gcc troca a linha por uma instrução que derruba o programa (código 132). No Clang: second argument to 'va_arg' is of promotable type.
Como consertar: leia o tipo promovido: va_arg(args, double) para float, va_arg(args, int) para char, short e bool.
error: unknown type name ‘va_list’
$ gcc -std=c23 -Wall -Wextra seminclude.c -o seminclude
seminclude.c: In function ‘soma’:
seminclude.c:4:5: error: unknown type name ‘va_list’
4 | va_list args;
| ^~~~~~~
seminclude.c:2:1: note: ‘va_list’ is defined in header ‘<stdarg.h>’; this is probably fixable by adding ‘#include <stdarg.h>’
1 | #include <stdio.h>
+++ |+#include <stdarg.h>
2 |
seminclude.c:5:5: error: implicit declaration of function ‘va_start’ [-Wimplicit-function-declaration]
5 | va_start(args, quantos);
| ^~~~~~~~
…
O que significa: "nome de tipo desconhecido: va_list". Faltou o #include <stdarg.h>, e o próprio gcc sugere a linha (this is probably fixable by adding = "isto provavelmente se conserta acrescentando"). No Clang: use of undeclared identifier 'va_list'.
Como consertar: acrescente #include <stdarg.h> no topo.
error: ‘va_start’ used in function with fixed arguments
O que significa: "va_start usado numa função com argumentos fixos": a função não tem ... na lista de parâmetros, então não há sacola para abrir. No Clang: 'va_start' used in function with fixed args.
Como consertar: acrescente , ... no fim dos parâmetros, ou tire o va_start se a função não precisa de argumentos extras.
error: macro ‘va_start’ requires 2 arguments, but only 1 given
O que significa: você usou o va_start(args) do C23 compilando com um padrão antigo (-std=c17, -std=c11…).
Como consertar: compile com -std=c23 ou escreva a forma antiga, va_start(args, ultimo_parametro), que funciona em todas as versões.
Referência rápida
| Escrita | O que faz |
|---|---|
#include <stdarg.h> | Traz va_list e as macros abaixo |
tipo f(tipo ultimo, ...) | Declara uma função variádica (as reticências sempre no fim) |
va_list args; | Cria a variável que percorre os argumentos extras |
va_start(args, ultimo); | Começa a leitura depois do último parâmetro nomeado |
va_start(args); C23 | O mesmo, sem repetir o nome |
va_arg(args, tipo) | Devolve o próximo argumento como tipo e avança (use int e double, nunca char, short, bool ou float) |
va_end(args); | Termina a leitura; um para cada va_start |
va_copy(copia, args); C99 | Copia a posição atual, para percorrer a sacola duas vezes (a cópia também precisa de va_end) |
vprintf(fmt, args) | printf que recebe uma va_list (também vfprintf, vsnprintf, vscanf) |
[[gnu::format(printf, 1, 2)]] | Extensão do gcc e do Clang: confere os formatos da sua função como confere os do printf |
Exercícios
1. Começar a leitura
Complete a linha que começa a leitura dos argumentos extras de int maior(int quantos, ...), na forma que funciona em todas as versões do C:
2. O tipo certo
A função recebe notas que foram guardadas em variáveis float. Complete o tipo no va_arg:
3. A versão com va_list
Qual função faz o trabalho do fprintf, mas recebe uma va_list?
4. Dentro de uma função variádica, como ela descobre quantos argumentos recebeu?
5. Você chama letras(3, 'A', 'n', 'a'). Como a função deve ler cada letra?
6. A função soma(int quantos, ...) lê int. O que acontece com soma(2, 1.5, 2.5)?
Missão final: o log da lojinha
Missão: a função registrar da lojinha mostra o formato cru, com os %s e %d sem trocar. Reescreva-a com va_list, va_start, vprintf e va_end, para a saída ser [LOG] Ana vendeu 3 cafés e [LOG] caixa fechou com R$ 23.70. Não mude o main.
#include <stdarg.h>
#include <stdio.h>
void registrar(const char *formato, ...) {
printf("[LOG] %s\n", formato);
}
int main(void) {
registrar("%s vendeu %d cafés", "Ana", 3);
registrar("caixa fechou com R$ %.2f", 23.7);
return 0;
}
#include <stdarg.h>
#include <stdio.h>
void registrar(const char *formato, ...) {
va_list args;
va_start(args, formato);
printf("[LOG] ");
vprintf(formato, args);
printf("\n");
va_end(args);
}
int main(void) {
registrar("%s vendeu %d cafés", "Ana", 3);
registrar("caixa fechou com R$ %.2f", 23.7);
return 0;
}
Resumo
- Uma função variádica tem
...no fim dos parâmetros e aceita qualquer quantidade de argumentos extras. Oprintfé a mais famosa. - Para ler os extras:
va_list args;,va_start(args, ultimo);, umva_arg(args, tipo)por argumento eva_end(args);no fim. - A função não sabe quantos argumentos vieram nem de que tipo: quem chama diz, com um contador, uma sentinela (
NULL) ou um formato. Se a informação mentir, é comportamento indefinido, sem aviso. - Pelas reticências,
char,shorteboolchegam comoint, efloatchega comodouble. Nunca useva_arg(args, float): no gcc, o programa morre comInstrução ilegal. - No C23,
va_start(args)aceita um argumento só e a função pode ter só.... - Para uma função "tipo
printf", passe ava_listparavprintf/vfprintf/vsnprintfe marque-a com[[gnu::format(printf, 1, 2)]]para o compilador conferir os formatos. - Quando os valores são todos do mesmo tipo, um array e o tamanho são mais seguros.