C Atributos
Às vezes você sabe uma coisa sobre o seu código que o compilador não tem como adivinhar: "o retorno desta função é importante, não jogue fora", "esta função é antiga, não use mais", "aqui no switch eu quero mesmo continuar no próximo case". Os atributos C23 são etiquetas entre colchetes duplos, como [[nodiscard]], que contam isso ao compilador. Em troca, ele avisa quando alguém usa o código do jeito errado.
- O que é um atributo e onde se escreve
[[…]]. [[nodiscard]]: avisar quando o retorno de uma função é ignorado.[[maybe_unused]]: calar o aviso de "variável não usada" quando isso é de propósito.[[deprecated]]: marcar algo como antigo, com uma mensagem para quem ainda usa.[[fallthrough]]: dizer que a "queda" para o próximocasedoswitché intencional.[[noreturn]]e o antigo_Noreturn: funções que nunca voltam.- O
__attribute__((…))do gcc, que veio antes de tudo isso.
Os programas desta aula foram compilados com o gcc 15.2 na máquina 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. Atributos produzem avisos, por isso o -Wall -Wextra é importante aqui (aula Avisos).
A ideia: etiquetas na caixa da mudança
Numa mudança, você escreve nas caixas: "FRÁGIL", "ESTE LADO PARA CIMA", "NÃO JOGAR FORA". A etiqueta não muda o que está dentro da caixa. Ela muda o jeito como os outros a tratam: o carregador lê "FRÁGIL" e toma cuidado.
Atributos são essas etiquetas. [[nodiscard]] é o "NÃO JOGAR FORA" de uma função: o programa funciona igual, mas o compilador passa a reclamar de quem joga o retorno fora. Se o compilador não conhece uma etiqueta, ele a ignora (com um aviso), em vez de dar erro.
| Atributo | Etiqueta da caixa | Onde vai |
|---|---|---|
[[nodiscard]] | "Não jogue o resultado fora" | Função |
[[maybe_unused]] | "Pode ficar sem uso, está tudo bem" | Variável, parâmetro, função |
[[deprecated]] | "Antigo: use outro" | Função, variável, tipo |
[[fallthrough]] | "Continua no próximo, de propósito" | Dentro do switch |
[[noreturn]] | "Daqui não se volta" | Função |
1. [[nodiscard]]: não jogue o retorno fora
"Discard" é "descartar"; no discard, "não descarte". Veja um erro comum: a função dobro devolve o resultado, mas quem chamou achou que ela mudaria a variável:
#include <stdio.h>
[[nodiscard]] int dobro(int x) {
return x * 2;
}
int main(void) {
int preco = 5;
dobro(preco);
printf("Preço: %d\n", preco);
return 0;
}
$ gcc -std=c23 -Wall -Wextra nodiscard.c -o nodiscard
nodiscard.c: In function ‘main’:
nodiscard.c:9:5: warning: ignoring return value of ‘dobro’, declared with attribute ‘nodiscard’ [-Wunused-result]
9 | dobro(preco);
| ^~~~~~~~~~~~
nodiscard.c:3:19: note: declared here
3 | [[nodiscard]] int dobro(int x) {
| ^~~~~
$ ./nodiscard
Preço: 5 « o dobro foi calculado e jogado fora
- [[nodiscard]]
- O atributo, entre dois pares de colchetes, antes da declaração da função.
- int dobro(int x)
- A função continua igual. O atributo só muda o que o compilador confere em quem a chama.
- warning: ignoring return value of ‘dobro’
- "Ignorando o valor de retorno de
dobro": a linha 9 chamou a função e não guardou nem usou o resultado. - declared with attribute ‘nodiscard’
- "Declarada com o atributo
nodiscard": o aviso existe por causa da etiqueta. - [-Wunused-result]
- O nome do aviso ("resultado não usado"). Ele vem ligado mesmo sem
-Wall.
Sem o [[nodiscard]], o gcc compilaria em silêncio e a Ana só descobriria o erro olhando a saída. O conserto é guardar o resultado: preco = dobro(preco);.
Com um motivo
Você pode explicar o porquê, entre parênteses. A mensagem aparece no aviso:
#include <stdio.h>
#include <stdlib.h>
[[nodiscard("o novo saldo vem no retorno")]]
int sacar(int saldo, int valor) {
return saldo - valor;
}
int main(void) {
int saldo = 100;
sacar(saldo, 30);
printf("Saldo: %d\n", saldo);
malloc(10);
return 0;
}
$ gcc -std=c23 -Wall -Wextra nodiscard2.c -o nodiscard2
nodiscard2.c: In function ‘main’:
nodiscard2.c:11:5: warning: ignoring return value of ‘sacar’, declared with attribute ‘nodiscard’: "o novo saldo vem no retorno" [-Wunused-result]
11 | sacar(saldo, 30);
| ^~~~~~~~~~~~~~~~
nodiscard2.c:5:5: note: declared here
5 | int sacar(int saldo, int valor) {
| ^~~~~
nodiscard2.c:13:5: warning: ignoring return value of ‘malloc’ declared with attribute ‘warn_unused_result’ [-Wunused-result]
13 | malloc(10);
| ^~~~~~~~~~
Repare no segundo aviso: o malloc da biblioteca já vem com uma etiqueta parecida, warn_unused_result (a versão do gcc, mais antiga). Chamar malloc e jogar fora o endereço é sempre um erro: a memória fica reservada e ninguém consegue liberá-la (aula malloc e free).
O atributo pode ficar na mesma linha da função ou na linha de cima, como aqui: as duas formas são iguais.
Quando ignorar é de propósito
Se você quer mesmo descartar o resultado, escreva (void) antes da chamada. É um jeito de dizer "eu sei, e não quero":
(void) dobro(5); // compila sem aviso
No Clang, o compilador do editor do guia, o aviso é ignoring return value of function declared with 'nodiscard' attribute [-Wunused-result]: a mesma ideia, sem o nome da função.
Missão: rode e leia o aviso. O desconto de 10% foi calculado e jogado fora, e o preço continuou 50. Conserte a chamada para guardar o retorno em preco, até sair Preço: 45 sem nenhum aviso.
#include <stdio.h>
[[nodiscard]] int aplicar_desconto(int preco, int porcento) {
return preco - preco * porcento / 100;
}
int main(void) {
int preco = 50;
aplicar_desconto(preco, 10);
printf("Preço: %d\n", preco);
return 0;
}
#include <stdio.h>
[[nodiscard]] int aplicar_desconto(int preco, int porcento) {
return preco - preco * porcento / 100;
}
int main(void) {
int preco = 50;
preco = aplicar_desconto(preco, 10);
printf("Preço: %d\n", preco);
return 0;
}
2. [[maybe_unused]]: sem uso, de propósito
O -Wall -Wextra avisa sobre variáveis e parâmetros que nunca são usados, porque quase sempre isso é um esquecimento:
#include <stdio.h>
void saudar(int vezes, const char *nome) {
int teste = 0;
for (int i = 0; i < vezes; i++) {
printf("Olá!\n");
}
}
int main(void) {
saudar(2, "Ana");
return 0;
}
$ gcc -std=c23 -Wall -Wextra unused0.c -o unused0
unused0.c: In function ‘saudar’:
unused0.c:4:9: warning: unused variable ‘teste’ [-Wunused-variable]
4 | int teste = 0;
| ^~~~~
unused0.c:3:36: warning: unused parameter ‘nome’ [-Wunused-parameter]
3 | void saudar(int vezes, const char *nome) {
| ~~~~~~~~~~~~^~~~
Unused variable = "variável não usada"; unused parameter = "parâmetro não usado". Às vezes, porém, é de propósito: a função ainda está pela metade, ou ela precisa ter aquele parâmetro porque vai ser guardada num ponteiro para função com um formato fixo (aula Ponteiros para Funções). Nesses casos, a etiqueta [[maybe_unused]] ("talvez não usado") cala o aviso só ali:
void saudar(int vezes, [[maybe_unused]] const char *nome) {
[[maybe_unused]] int teste = 0;
for (int i = 0; i < vezes; i++) {
printf("Olá!\n");
}
}
$ gcc -std=c23 -Wall -Wextra unused.c -o unused « nenhum aviso
Antes do C23 o costume era escrever (void) nome; dentro da função, que "usa" o parâmetro sem fazer nada. Você ainda vai ver muito isso. Se a variável é só sobra (como teste), o melhor conserto é apagá-la; o [[maybe_unused]] é para quando ela precisa ficar.
Missão: a função tratar tem o formato que o sistema de botões da lojinha exige, por isso o parâmetro codigo precisa existir, mesmo sem uso. Rode e veja o aviso. Cale-o com [[maybe_unused]] no parâmetro (sem apagá-lo).
#include <stdio.h>
void tratar(int codigo) {
printf("Olá! Botão apertado.\n");
}
int main(void) {
tratar(7);
return 0;
}
#include <stdio.h>
void tratar([[maybe_unused]] int codigo) {
printf("Olá! Botão apertado.\n");
}
int main(void) {
tratar(7);
return 0;
}
3. [[deprecated]]: "isto é antigo, use outro"
"Deprecated" quer dizer obsoleto, "desaconselhado". Quando a Ana escreve uma versão melhor de uma função mas não pode apagar a velha (outras partes do programa ainda a usam), ela marca a velha:
#include <stdio.h>
[[deprecated("use calcular_total_com_desconto")]]
double calcular_total(double preco, int qtd) {
return preco * qtd;
}
[[deprecated]] int versao_antiga = 1;
int main(void) {
printf("%.2f\n", calcular_total(2.5, 4));
printf("%d\n", versao_antiga);
return 0;
}
$ gcc -std=c23 -Wall -Wextra deprecated.c -o deprecated
deprecated.c: In function ‘main’:
deprecated.c:11:5: warning: ‘calcular_total’ is deprecated: use calcular_total_com_desconto [-Wdeprecated-declarations]
11 | printf("%.2f\n", calcular_total(2.5, 4));
| ^~~~~~
deprecated.c:4:8: note: declared here
4 | double calcular_total(double preco, int qtd) {
| ^~~~~~~~~~~~~~
deprecated.c:12:5: warning: ‘versao_antiga’ is deprecated [-Wdeprecated-declarations]
12 | printf("%d\n", versao_antiga);
| ^~~~~~
deprecated.c:8:20: note: declared here
8 | [[deprecated]] int versao_antiga = 1;
| ^~~~~~~~~~~~~
$ ./deprecated
10.00
1
- ‘calcular_total’ is deprecated
- "
calcular_totalestá obsoleta". É só um aviso: o programa foi gerado e funciona. - : use calcular_total_com_desconto
- A sua mensagem, dizendo o que usar no lugar. Sem mensagem (como no
versao_antiga), o aviso só diz que é obsoleto. - note: declared here
- Onde está a declaração marcada.
Repare que o aviso aparece em quem usa, não na declaração. Assim cada parte do programa que ainda depende da função velha fica marcada, e a Ana pode trocá-las aos poucos. Bibliotecas usam muito isso para avisar que algo vai sumir numa versão futura.
4. [[fallthrough]]: continuar no próximo case
Num switch, sem break, o programa "cai" no case de baixo (aula switch). Quase sempre isso é um break esquecido, e o -Wextra do gcc avisa. Mas às vezes cair é exatamente o que se quer. Na gincana da escola, quem chega ao nível 3 ganha os prêmios dos níveis 3, 2 e 1:
#include <stdio.h>
int main(void) {
int nivel = 2;
switch (nivel) {
case 3:
printf("Prêmio: viagem\n");
case 2:
printf("Prêmio: bicicleta\n");
case 1:
printf("Prêmio: chaveiro\n");
break;
default:
printf("Sem prêmio\n");
}
return 0;
}
$ gcc -std=c23 -Wall -Wextra fall.c -o fall
fall.c: In function ‘main’:
fall.c:7:9: warning: this statement may fall through [-Wimplicit-fallthrough=]
7 | printf("Prêmio: viagem\n");
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
fall.c:8:5: note: here
8 | case 2:
| ^~~~
fall.c:9:9: warning: this statement may fall through [-Wimplicit-fallthrough=]
9 | printf("Prêmio: bicicleta\n");
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
fall.c:10:5: note: here
10 | case 1:
| ^~~~
$ ./fall
Prêmio: bicicleta
Prêmio: chaveiro
This statement may fall through = "este comando pode cair (para o próximo)", e a nota here mostra para onde. O programa está certo, mas o gcc não tem como saber que a queda é proposital. Com [[fallthrough]]; no fim do case, você diz isso a ele (e a quem ler o código):
switch (nivel) {
case 3:
printf("Prêmio: viagem\n");
[[fallthrough]];
case 2:
printf("Prêmio: bicicleta\n");
[[fallthrough]];
case 1:
printf("Prêmio: chaveiro\n");
break;
default:
printf("Sem prêmio\n");
}
$ gcc -std=c23 -Wall -Wextra fall2.c -o fall2 « nenhum aviso
- [[fallthrough]]
- "Cair através": a queda é intencional.
- ;
- Este atributo fica sozinho, como um comando vazio, e por isso termina com ponto e vírgula.
- logo antes do próximo case
- Ele só pode aparecer como o último comando antes de um
caseoudefault. Em outro lugar, é erro.
O Clang não liga esse aviso com -Wall -Wextra: o fall.c compila calado no editor. Para vê-lo, é preciso pedir -Wimplicit-fallthrough, como faz a missão final desta aula. Aí o Clang avisa unannotated fall-through between switch labels ("queda sem anotação entre rótulos do switch") e até sugere: insert '[[fallthrough]];' to silence this warning.
5. [[noreturn]]: funções que nunca voltam
Algumas funções nunca devolvem o controle para quem as chamou: elas encerram o programa (com exit, aula Encerrar o Programa). O compilador não sabe disso, e essa ignorância gera um aviso falso:
#include <stdio.h>
#include <stdlib.h>
void erro_fatal(const char *msg) {
fprintf(stderr, "Erro: %s\n", msg);
exit(1);
}
int ler_idade(int idade) {
if (idade >= 0) {
return idade;
}
erro_fatal("idade negativa");
}
int main(void) {
printf("%d\n", ler_idade(29));
return 0;
}
$ gcc -std=c23 -Wall -Wextra noreturn0.c -o noreturn0
noreturn0.c: In function ‘ler_idade’:
noreturn0.c:14:1: warning: control reaches end of non-void function [-Wreturn-type]
14 | }
| ^
"O controle chega ao fim de uma função não-void": o gcc acha que, depois de erro_fatal, a ler_idade termina sem return (aula Valor de Retorno). Marcando erro_fatal com [[noreturn]] ("não retorna"), o aviso some, porque agora o compilador sabe que nada depois dela vai rodar:
#include <stdio.h>
#include <stdlib.h>
[[noreturn]] void erro_fatal(const char *msg) {
fprintf(stderr, "Erro: %s\n", msg);
exit(1);
}
int ler_idade(int idade) {
if (idade >= 0) {
return idade;
}
erro_fatal("idade negativa");
}
int main(void) {
printf("%d\n", ler_idade(29));
printf("%d\n", ler_idade(-1));
return 0;
}
$ gcc -std=c23 -Wall -Wextra noreturn.c -o noreturn $ ./noreturn 29 Erro: idade negativa $ echo $? 1
E se ela voltar?
Prometer que não volta e voltar é grave. Sem o exit:
$ cat noreturn2.c
#include <stdio.h>
[[noreturn]] void erro_fatal(const char *msg) {
fprintf(stderr, "Erro: %s\n", msg);
}
int main(void) {
erro_fatal("teste");
return 0;
}
$ gcc -std=c23 -Wall -Wextra noreturn2.c -o noreturn2
noreturn2.c: In function ‘erro_fatal’:
noreturn2.c:5:1: warning: ‘noreturn’ function does return
5 | }
| ^
$ ./noreturn2
Erro: teste
Instrução ilegal (imagem do núcleo gravada) ./noreturn2
$ echo $?
132
"Função noreturn retorna": o gcc avisou, e o programa morreu com "Instrução ilegal" (código 132 = 128 + 4, o sinal SIGILL; aula Sinais). Como o compilador confiou na promessa, ele não preparou nada para o caminho de volta. É comportamento indefinido: no editor do guia, o mesmo programa termina com "Falha de segmentação" (139). Nunca ignore esse aviso.
O antigo _Noreturn
Antes do C23, a mesma etiqueta se escrevia _Noreturn void erro_fatal(…) C11, ou noreturn com #include <stdnoreturn.h>. O gcc 15 ainda aceita as duas, sem aviso, até em C23. Mas o C23 marcou essas formas como obsoletas: em código novo, use [[noreturn]]. As funções exit e abort da biblioteca já vêm marcadas assim.
6. Formas diferentes: __attribute__ e outros atributos
Muito antes do C23, o gcc já tinha as suas etiquetas, com outra escrita: __attribute__((…)), com dois parênteses. Você vai encontrar isso em todo código C antigo e nos próprios cabeçalhos do sistema:
| C23 | gcc (antigo) |
|---|---|
[[nodiscard]] | __attribute__((warn_unused_result)) |
[[maybe_unused]] | __attribute__((unused)) |
[[deprecated("msg")]] | __attribute__((deprecated("msg"))) |
[[fallthrough]] | __attribute__((fallthrough)) ou o comentário /* fall through */ |
[[noreturn]] | __attribute__((noreturn)) |
Alguns atributos do gcc não têm equivalente no padrão. Um muito útil é o format, que faz o compilador conferir os %d e %s de uma função sua parecida com o printf (aula Funções Variádicas). No C23, ele pode ser escrito com colchetes e o "sobrenome" do compilador, [[gnu::format(printf, 1, 2)]]:
$ cat gnu.c
#include <stdio.h>
[[gnu::format(printf, 1, 2)]]
void registrar(const char *formato, ...);
int main(void) {
registrar("%d itens\n", "três");
return 0;
}
$ gcc -std=c23 -Wall -Wextra -c gnu.c « -c: só compila, sem ligar
gnu.c: In function ‘main’:
gnu.c:7:17: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘char *’ [-Wformat=]
7 | registrar("%d itens\n", "três");
| ~^ ~~~~~~
| | |
| int char *
| %s
O 1, 2 quer dizer "o formato é o 1º parâmetro, e os valores começam no 2º". Agora a função da Ana ganhou a mesma vigilância do printf.
O C23 também criou [[unsequenced]] e [[reproducible]], que prometem que uma função não tem efeitos colaterais, para o compilador otimizar. O gcc 15 os conhece (e exige que fiquem depois dos parênteses dos parâmetros: int quadrado(int x) [[unsequenced]];); o Clang do editor ainda os ignora com o aviso unknown attribute 'unsequenced' ignored. São recursos avançados, raros em código do dia a dia.
Qual usar
- Em código novo (C23), use os atributos com colchetes. São padrão e funcionam no gcc e no Clang.
[[nodiscard]]vale ouro em funções que devolvem um resultado que precisa ser usado: um código de erro, memória reservada, um valor novo calculado.[[maybe_unused]]e[[fallthrough]]servem para manter o-Wall -Wextrasem avisos falsos. Aviso falso que fica na tela esconde os verdadeiros.__attribute__só quando precisar de um recurso do gcc que não tem versão padrão (prefira então a forma[[gnu::…]]).
Quando dá errado
warning: ‘importante’ attribute ignored [-Wattributes]
Aparece com um atributo que o compilador não conhece, como [[importante]] int x = 1; (ou com um nome escrito errado, como [[nodiscart]]). O Clang diz unknown attribute 'importante' ignored.
O que significa: a etiqueta foi ignorada. O programa compila, mas sem a proteção que você queria.
Como consertar: confira a grafia. Os atributos padrão são nodiscard, maybe_unused, deprecated, fallthrough, noreturn, unsequenced e reproducible.
warning: ignoring return value of ‘f’, declared with attribute ‘nodiscard’
O que significa: você chamou a função e não usou o resultado. Quase sempre é um erro de lógica: o resultado era a resposta.
Como consertar: guarde (x = f(x);) ou use o resultado (num if, num printf). Se descartar for mesmo intencional, escreva (void) f(x);.
warning: this statement may fall through [-Wimplicit-fallthrough=]
O que significa: um case termina sem break e o programa vai continuar no próximo.
Como consertar: se foi esquecimento, ponha break;. Se é de propósito, ponha [[fallthrough]]; como último comando do case.
warning: ‘noreturn’ function does return
O que significa: uma função marcada [[noreturn]] tem um caminho que chega ao fim. Se esse caminho acontecer, o programa pode morrer de forma estranha (vimos "Instrução ilegal").
Como consertar: garanta que todo caminho termine com exit, abort ou outra função [[noreturn]]. Se a função às vezes volta, tire o atributo.
Referência rápida
| Atributo | Onde | O que faz |
|---|---|---|
[[nodiscard]] / [[nodiscard("motivo")]] | Função | Avisa (-Wunused-result) quando o retorno é ignorado |
[[maybe_unused]] | Variável, parâmetro, função | Cala os avisos de "não usado" |
[[deprecated]] / [[deprecated("use x")]] | Função, variável, tipo | Avisa (-Wdeprecated-declarations) em cada uso |
[[fallthrough]]; | Último comando de um case | Diz que a queda para o próximo case é intencional |
[[noreturn]] | Função | Promete que a função nunca volta (substitui _Noreturn) |
(void) f(x); | Chamada | Descarta o retorno de propósito, sem aviso |
[[gnu::format(printf, 1, 2)]] | Função | Atributo do gcc: confere o formato como no printf |
__attribute__((…)) | Vários | A escrita antiga do gcc |
Exercícios
1. Não jogue fora
Complete para o compilador avisar quando alguém ignorar o retorno de calcular:
2. Queda intencional
Complete a linha que vai no fim do case para dizer que cair no próximo é de propósito:
3. O que acontece quando você usa uma função marcada com [[deprecated]]?
4. A função f é [[nodiscard]]. Qual destas linhas compila sem aviso?
5. Uma função marcada [[noreturn]] chega ao fim e volta. O que acontece?
6. Qual era a forma do C11 para "esta função não retorna"?
Missão final: os prêmios da gincana
Missão: esta caixa compila com -Wimplicit-fallthrough, então o Clang avisa sobre as quedas entre os case. Quem chega ao nível 3 deve ganhar os três prêmios, então as quedas são de propósito. Marque-as com [[fallthrough]]; para o programa mostrar os três prêmios sem nenhum aviso (não use break nesses dois lugares).
Prêmio: viagem Prêmio: bicicleta Prêmio: chaveiro
#include <stdio.h>
int main(void) {
int nivel = 3;
switch (nivel) {
case 3:
printf("Prêmio: viagem\n");
case 2:
printf("Prêmio: bicicleta\n");
case 1:
printf("Prêmio: chaveiro\n");
break;
default:
printf("Sem prêmio\n");
}
return 0;
}
#include <stdio.h>
int main(void) {
int nivel = 3;
switch (nivel) {
case 3:
printf("Prêmio: viagem\n");
[[fallthrough]];
case 2:
printf("Prêmio: bicicleta\n");
[[fallthrough]];
case 1:
printf("Prêmio: chaveiro\n");
break;
default:
printf("Sem prêmio\n");
}
return 0;
}
Resumo
- Atributos C23 são etiquetas
[[nome]]que contam ao compilador algo que ele não adivinha. Eles não mudam o que o programa faz; mudam os avisos. [[nodiscard]]: aviso quando o retorno é ignorado.(void) f();descarta de propósito.[[maybe_unused]]: cala "unused variable/parameter" quando a falta de uso é intencional.[[deprecated("use x")]]: aviso em cada uso de algo antigo.[[fallthrough]];: a queda para o próximocaseé proposital (o gcc avisa com-Wextra; o Clang só com-Wimplicit-fallthrough).[[noreturn]]: a função nunca volta. Substitui o_Noreturndo C11, que o C23 marcou como obsoleto. Voltar mesmo assim é comportamento indefinido.- O gcc tem a escrita antiga
__attribute__((…))e atributos próprios, como[[gnu::format(printf, 1, 2)]].