C static_assert
Todo programa parte de algumas certezas: "um int tem 4 bytes", "cada registro do arquivo tem 24 bytes", "a lista de nomes tem um nome para cada dia da semana". Se uma dessas certezas deixar de valer (outro computador, alguém mexeu numa struct), o programa erra em silêncio. O static_assert transforma essas certezas em regras que o compilador confere: se a regra falhar, o programa nem é gerado.
- O que é uma "afirmação" (assertion) e por que conferir na compilação.
- Como escrever
static_assert(condição, "mensagem");e ler o erro quando ela falha. - As três escritas:
_Static_assertC11,static_assertcomassert.hestatic_assertsem include e sem mensagem C23. - Conferir o tamanho de tipos e de structs, e se um array tem um item para cada valor de um
enum. - Por que a condição precisa ser uma constante (e como o
constexprajuda). - A diferença para o
assert, que confere com o programa rodando.
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.
A ideia: o medidor de bagagem do aeroporto
No aeroporto, antes de embarcar, a mala de mão passa por um medidor: uma caixa de metal do tamanho máximo permitido. Se a mala não cabe, você descobre no chão, antes de o avião sair. Ninguém espera o avião decolar para descobrir que a mala não cabe no bagageiro.
O static_assert é o medidor: ele confere a regra antes de o programa existir, durante a compilação. Já o assert comum (aula Testes) é como um alarme que toca durante o voo: ele confere enquanto o programa roda, e quando toca, o programa é interrompido.
static_assert | assert | |
|---|---|---|
| Quando confere | Na compilação | Com o programa rodando |
| O que pode conferir | Só o que o compilador já sabe: sizeof, constantes, enum | Qualquer coisa: variáveis, o que o usuário digitou |
| Quando falha | Erro de compilação: o programa não é gerado | O programa para com "Abortado" (código 134) |
| Custa tempo ao rodar? | Nada: no programa pronto ele nem existe | Um pouquinho, a cada vez que passa por ali |
Assert é "afirmar" em inglês: você afirma que algo é verdade, e o computador confere. Static ("estático", "parado") quer dizer aqui "na compilação", quando nada ainda está rodando.
1. O primeiro static_assert
A Ana guarda pontuações de até 2 bilhões num int. Isso só cabe se o int tiver 4 bytes (aula Inteiros). Ela escreve essa certeza no código:
#include <stdio.h>
static_assert(sizeof(int) == 4, "este programa precisa de int com 4 bytes");
int main(void) {
int pontos = 2000000000;
printf("Pontos: %d\n", pontos);
return 0;
}
$ gcc -std=c23 -Wall -Wextra tamanho.c -o tamanho $ ./tamanho Pontos: 2000000000
- static_assert
- Em C23, uma palavra-chave da linguagem: não precisa de nenhum
#include. - ( sizeof(int) == 4
- A condição: a regra que tem de ser verdadeira. O
sizeof(int)é conhecido pelo compilador, então ele consegue conferir na hora (aula Tamanho dos Tipos). - , "este programa…" )
- A mensagem, mostrada só se a condição for falsa. Escreva o porquê da regra: quem ler o erro daqui a um ano vai agradecer.
- fora do main
- O
static_assertpode ficar fora de qualquer função (como aqui), dentro de uma função ou até dentro de umastruct. Ele não vira código nenhum: é só uma conferência.
Quando a regra é verdadeira, nada acontece: o gcc compilou calado e o programa rodou normalmente. O static_assert só aparece quando falha.
Quando a regra é falsa
Agora, de propósito, uma regra que não vale no computador da Ana:
#include <stdio.h>
static_assert(sizeof(int) == 8, "este programa precisa de int com 8 bytes");
int main(void) {
printf("Compilou!\n");
return 0;
}
$ gcc -std=c23 -Wall -Wextra falha.c -o falha
falha.c:3:1: error: static assertion failed: "este programa precisa de int com 8 bytes"
3 | static_assert(sizeof(int) == 8, "este programa precisa de int com 8 bytes");
| ^~~~~~~~~~~~~
$ ./falha
bash: ./falha: Arquivo ou diretório inexistente
- falha.c:3:1:
- Arquivo, linha 3, coluna 1: onde está o
static_assertque falhou. - error: static assertion failed:
- "Erro: afirmação estática falhou". É um error, não um aviso: o programa não foi gerado.
- "este programa precisa de int com 8 bytes"
- A sua mensagem, copiada do código. É por isso que ela deve explicar a regra.
- Arquivo ou diretório inexistente
- Como a compilação falhou, o arquivo
falhanão existe. Nenhum programa com a regra quebrada chega a rodar.
O Clang, compilador do editor do guia, mostra ainda a conta que deu errado, o que ajuda bastante:
falha.c:3:15: error: static assertion failed due to requirement 'sizeof(int) == 8': este programa precisa de int com 8 bytes
3 | static_assert(sizeof(int) == 8, "este programa precisa de int com 8 bytes");
| ^~~~~~~~~~~~~~~~
falha.c:3:27: note: expression evaluates to '4 == 8'
Due to requirement = "por causa da exigência"; expression evaluates to '4 == 8' = "a expressão dá 4 == 8": o sizeof(int) valeu 4, e 4 não é 8.
Missão (provoque o erro): a sala da escola tem 40 cadeiras, e o static_assert garante que MAX_ALUNOS nunca passe disso. Rode: compila. Agora mude MAX_ALUNOS para 45 e rode de novo: o compilador tem de recusar, com a sua mensagem.
#include <stdio.h>
#define MAX_ALUNOS 35
static_assert(MAX_ALUNOS <= 40, "a sala só tem 40 cadeiras");
int main(void) {
printf("A turma pode ter até %d alunos.\n", MAX_ALUNOS);
return 0;
}
#include <stdio.h>
#define MAX_ALUNOS 45
static_assert(MAX_ALUNOS <= 40, "a sala só tem 40 cadeiras");
int main(void) {
printf("A turma pode ter até %d alunos.\n", MAX_ALUNOS);
return 0;
}
2. C23: a mensagem é opcional
Em C23, você pode escrever só a condição. Para regras óbvias, a mensagem seria repetir o que o código já diz:
#include <stdio.h>
static_assert(sizeof(int) == 8);
int main(void) {
printf("Compilou!\n");
return 0;
}
$ gcc -std=c23 -Wall -Wextra semmsg.c -o semmsg
semmsg.c:3:1: error: static assertion failed
3 | static_assert(sizeof(int) == 8);
| ^~~~~~~~~~~~~
O gcc diz só "falhou" e mostra a linha; a regra está ali, escrita no código. O Clang mostra static assertion failed due to requirement 'sizeof(int) == 8' e a nota expression evaluates to '4 == 8'.
3. As escritas antigas: _Static_assert e assert.h
O recurso nasceu no C11, com um nome feio de propósito: _Static_assert. O nome "bonito" era só uma macro definida em assert.h. Isso tudo ainda funciona, e você vai encontrar muito código assim:
#include <stdio.h>
_Static_assert(sizeof(int) == 4, "int precisa ter 4 bytes");
int main(void) {
printf("Compilou!\n");
return 0;
}
$ gcc -std=c11 -Wall -Wextra c11.c -o c11 && ./c11 Compilou!
O -std=c11 pede ao gcc o C de 2011 (aula Compilar com gcc). Nesse padrão, escrever static_assert sem incluir assert.h dá um erro estranho:
$ gcc -std=c11 -Wall -Wextra c11b.c -o c11b
c11b.c:3:15: error: expected declaration specifiers or ‘...’ before ‘sizeof’
3 | static_assert(sizeof(int) == 4, "int precisa ter 4 bytes");
| ^~~~~~
c11b.c:3:33: error: expected declaration specifiers or ‘...’ before string constant
3 | static_assert(sizeof(int) == 4, "int precisa ter 4 bytes");
| ^~~~~~~~~~~~~~~~~~~~~~~~~
No C11, sem a macro, static_assert é um nome desconhecido, e o compilador acha que você está declarando uma função chamada static_assert com parâmetros esquisitos. A mensagem não fala nada de "assert", por isso confunde. Com #include <assert.h> no começo, o mesmo arquivo compila com -std=c11.
| Escrita | Desde | Precisa de | Mensagem |
|---|---|---|---|
_Static_assert(cond, "msg"); | C11 | Nada | Obrigatória até o C17 |
static_assert(cond, "msg"); | C11 | #include <assert.h> (no C11 e C17) | Obrigatória até o C17 |
static_assert(cond, "msg"); | C23 | Nada (é palavra-chave) | Opcional |
static_assert(cond); | C23 | Nada | Sem mensagem |
Qual usar: com o gcc 15 ou o Clang em C23, escreva static_assert, com mensagem quando a regra precisar de explicação. Se o seu código precisa compilar também em C11/C17, inclua assert.h e escreva sempre a mensagem. (O gcc aceita _Static_assert(cond) sem mensagem até com -std=c11, mas com -pedantic ele avisa: ISO C11 does not support omitting the string in ‘_Static_assert’.)
4. Conferir o tamanho de uma struct
O uso mais comum: proteger um formato de arquivo. A lojinha da Ana grava o estoque num arquivo binário, um produto depois do outro, cada um com 24 bytes (aula Arquivos Binários). Se a struct mudar de tamanho, os arquivos antigos passam a ser lidos errado. O static_assert vigia isso:
#include <stdio.h>
struct produto {
char codigo[8];
int quantidade;
double preco;
};
static_assert(sizeof(struct produto) == 24, "o arquivo estoque.dat usa registros de 24 bytes");
int main(void) {
printf("Cada produto ocupa %zu bytes.\n", sizeof(struct produto));
return 0;
}
$ gcc -std=c23 -Wall -Wextra produto.c -o produto $ ./produto Cada produto ocupa 24 bytes.
Meses depois, alguém acrescenta um campo bool ativo; no fim da struct, sem lembrar do arquivo:
$ gcc -std=c23 -Wall -Wextra produto2.c -o produto2
produto2.c:10:1: error: static assertion failed: "o arquivo estoque.dat usa registros de 24 bytes"
10 | static_assert(sizeof(struct produto) == 24, "o arquivo estoque.dat usa registros de 24 bytes");
| ^~~~~~~~~~~~~
A regra pegou o problema antes de algum estoque ser estragado. E de quanto ficou a struct? Não 25, como a conta "24 + 1 byte do bool" sugere: o Clang mostra expression evaluates to '32 == 24'. O compilador acrescenta bytes vazios (padding, "enchimento") para alinhar a struct na memória; a aula Structs explica. Mais um motivo para conferir com static_assert em vez de fazer a conta de cabeça.
5. Um nome para cada valor do enum
Outro erro clássico: um enum com os dias da semana e um array com o nome de cada dia (aula Enums). Se alguém esquece um nome, o array fica menor que o enum. Sem conferência, olha o que acontece:
#include <stdio.h>
enum dia { SEG, TER, QUA, QUI, SEX, SAB, DOM, TOTAL_DIAS };
const char *nomes[] = { "segunda", "terça", "quarta", "quinta", "sexta", "sábado" };
int main(void) {
for (int d = SEG; d < TOTAL_DIAS; d++) {
printf("%d: %s\n", d, nomes[d]);
}
return 0;
}
$ gcc -std=c23 -Wall -Wextra dias0.c -o dias0 $ ./dias0 0: segunda 1: terça 2: quarta 3: quinta 4: sexta 5: sábado 6: (null) « o domingo foi esquecido: leu fora do array
O nomes[6] não existe. Ler fora do array é comportamento indefinido (aula Comportamento Indefinido): aqui saiu (null), em outro computador poderia ser lixo ou o programa poderia morrer. E o gcc não avisou nada. Uma linha resolve:
O dias.c é o mesmo programa com esta linha a mais, logo depois do array (linha 7):
static_assert(sizeof nomes / sizeof nomes[0] == TOTAL_DIAS, "falta nome para algum dia");
$ gcc -std=c23 -Wall -Wextra dias.c -o dias
dias.c:7:1: error: static assertion failed: "falta nome para algum dia"
7 | static_assert(sizeof nomes / sizeof nomes[0] == TOTAL_DIAS, "falta nome para algum dia");
| ^~~~~~~~~~~~~
- sizeof nomes
- O tamanho do array inteiro, em bytes.
- / sizeof nomes[0]
- Dividido pelo tamanho de um item: dá quantos itens o array tem (aula Arrays). Aqui, 6.
- == TOTAL_DIAS
- O último nome do
enumvale 7, porque os valores começam em 0. Esse "item extra no fim" é um truque comum para contar quantos valores oenumtem.
A missão final desta aula é consertar esse programa.
6. A condição precisa ser constante
O compilador só confere o que ele já sabe antes de o programa rodar. O valor de uma variável comum, ele não sabe:
#include <stdio.h>
int main(void) {
int dias = 7;
static_assert(dias == 7, "a semana tem 7 dias");
printf("ok\n");
return 0;
}
$ gcc -std=c23 -Wall -Wextra naoconst.c -o naoconst
naoconst.c: In function ‘main’:
naoconst.c:5:24: error: expression in static assertion is not constant
5 | static_assert(dias == 7, "a semana tem 7 dias");
| ~~~~~^~~~
"A expressão na afirmação estática não é constante." Para nós é óbvio que dias vale 7, mas para a linguagem uma variável pode mudar a qualquer momento. (O Clang diz static assertion expression is not an integral constant expression, "não é uma expressão constante inteira".)
O que vale numa condição: números escritos no código, sizeof, valores de enum, macros #define e, no C23, variáveis constexpr (aula Constantes):
#include <stdio.h>
int main(void) {
constexpr int dias = 7;
static_assert(dias == 7, "a semana tem 7 dias");
printf("A semana tem %d dias.\n", dias);
return 0;
}
$ gcc -std=c23 -Wall -Wextra constexpr.c -o constexpr $ ./constexpr A semana tem 7 dias.
Atenção: const int dias = 7; não serve, pela regra da linguagem. Em C, const quer dizer "não posso mudar", mas não "o compilador conhece o valor". O gcc recusa com o mesmo expression in static assertion is not constant. O Clang do editor aceita, como uma extensão por conta própria, e isso engana: o código passa no editor e falha no gcc. Só o constexpr funciona em todo compilador C23.
7. static_assert × assert
Para conferir o que só se sabe com o programa rodando (o estoque depois de uma venda, um número digitado), existe o assert, de assert.h:
#include <stdio.h>
#include <assert.h>
int main(void) {
int estoque = 0;
printf("Vendendo...\n");
estoque = estoque - 1;
assert(estoque >= 0);
printf("Estoque: %d\n", estoque);
return 0;
}
$ gcc -std=c23 -Wall -Wextra runtime.c -o runtime $ ./runtime Vendendo... runtime: runtime.c:8: main: Assertion `estoque >= 0' failed. Abortado (imagem do núcleo gravada) ./runtime $ echo $? 134
- Este compilou sem reclamar: o
assertsó olha a variável quando o programa passa pela linha 8. Assertion `estoque >= 0' failed.= "a afirmaçãoestoque >= 0falhou", com o nome do programa, do arquivo, a linha e a função.Abortadoé o Bash avisando que o programa foi interrompido pelo sinalSIGABRT(aula Sinais); o código de saída é 134 (128 + 6, o número do sinal).- O
printf("Estoque…")nunca rodou: o estoque negativo não chegou a ser usado. - No editor do guia, a mensagem tem outro formato (
Assertion failed: estoque >= 0 (runtime.c: main: 8)), mas o efeito e o código 134 são os mesmos.
Um detalhe importante: compilando com -DNDEBUG ("no debug", sem depuração), todo assert some do programa. O static_assert nunca some:
$ gcc -std=c23 -Wall -Wextra -DNDEBUG runtime.c -o runtime $ ./runtime Vendendo... Estoque: -1 « o assert foi removido; o erro passou
Qual usar
| Situação | Use |
|---|---|
Tamanho de tipo ou de struct, valores de enum, macros, tamanho de array | static_assert: o erro aparece antes de existir programa |
| Valor de variável, resultado de conta, parâmetro recebido por uma função | assert: confere ao rodar |
| Algo que o usuário pode errar (digitou letra, arquivo não existe) | Nenhum dos dois: um if com mensagem de erro (aula Validar a Entrada). O assert é para erros do programador. |
Quando dá errado
error: static assertion failed: "…"
O que significa: a regra que você (ou alguém) escreveu é falsa neste computador ou neste código. Não é defeito do compilador: é o static_assert fazendo o trabalho dele.
Como consertar: leia a mensagem e descubra o que mudou (um campo novo na struct, um nome a menos no array, outro computador). Conserte o código. Mudar o número da regra só para "passar" esconde o problema que ela devia mostrar.
error: expression in static assertion is not constant
O que significa: a condição usa uma variável comum (ou const), cujo valor o compilador não conhece.
Como consertar: troque a variável por constexpr, um enum ou um #define. Se o valor só existe com o programa rodando, use assert.
error: expected declaration specifiers or ‘...’ before ‘sizeof’
O que significa: você compilou com -std=c11 ou -std=c17 e usou static_assert sem #include <assert.h>. Nesses padrões, o nome só existe como macro desse cabeçalho. No Clang: expected parameter declarator.
Como consertar: inclua assert.h, escreva _Static_assert ou compile em C23.
O que significa: tamanhos de tipos mudam de máquina para máquina. static_assert(sizeof(long) == 8) passa no gcc do laboratório (Linux de 64 bits) e falha no editor do guia, onde long tem 4 bytes (veja O Editor do Guia).
Como consertar: é exatamente esse o aviso que você queria. Se o código precisa de um tamanho exato, use os tipos de stdint.h (int32_t, int64_t, aula Inteiros de Tamanho Fixo).
Referência rápida
| Escrita | O que faz |
|---|---|
static_assert(cond, "msg"); | Erro de compilação com msg se cond for falsa C23 (no C11/C17, com assert.h) |
static_assert(cond); | O mesmo, sem mensagem C23 |
_Static_assert(cond, "msg"); | A forma original, sem include C11 |
static_assert(sizeof(int) == 4); | Confere o tamanho de um tipo |
static_assert(sizeof(struct s) == 24, "…"); | Protege um formato de arquivo ou de rede |
static_assert(sizeof v / sizeof v[0] == N, "…"); | Confere quantos itens um array tem |
assert(cond); | Confere com o programa rodando; falha = "Abortado", código 134; some com -DNDEBUG |
| Onde pode ficar | Fora de funções, dentro de funções e dentro de struct |
Exercícios
1. Complete a regra
Complete para conferir, na compilação, que double tem 8 bytes:
2. Quantos itens
Complete para calcular quantos itens tem o array notas:
3. Quando um static_assert falha, o que acontece?
4. Qual destas condições não pode ir num static_assert?
5. Em C23, qual destas linhas compila sem nenhum #include?
6. Você compila com -DNDEBUG. O que acontece com assert e static_assert?
Missão final: o domingo esquecido
Missão: rode e leia o erro: o static_assert encontrou um nome faltando no array. Conserte o array (não mexa no static_assert) para o programa compilar e mostrar os sete dias, terminando em 6: domingo.
0: segunda 1: terça 2: quarta 3: quinta 4: sexta 5: sábado 6: domingo
#include <stdio.h>
enum dia { SEG, TER, QUA, QUI, SEX, SAB, DOM, TOTAL_DIAS };
const char *nomes[] = { "segunda", "terça", "quarta", "quinta", "sexta", "sábado" };
static_assert(sizeof nomes / sizeof nomes[0] == TOTAL_DIAS, "falta nome para algum dia");
int main(void) {
for (int d = SEG; d < TOTAL_DIAS; d++) {
printf("%d: %s\n", d, nomes[d]);
}
return 0;
}
#include <stdio.h>
enum dia { SEG, TER, QUA, QUI, SEX, SAB, DOM, TOTAL_DIAS };
const char *nomes[] = { "segunda", "terça", "quarta", "quinta", "sexta", "sábado", "domingo" };
static_assert(sizeof nomes / sizeof nomes[0] == TOTAL_DIAS, "falta nome para algum dia");
int main(void) {
for (int d = SEG; d < TOTAL_DIAS; d++) {
printf("%d: %s\n", d, nomes[d]);
}
return 0;
}
Resumo
static_assert(condição, "mensagem");confere uma regra na compilação. Se for falsa:error: static assertion failede nenhum programa é gerado.- C11:
_Static_assert, oustatic_assertcom#include <assert.h>, mensagem obrigatória. C23:static_asserté palavra-chave e a mensagem é opcional. - Usos clássicos: tamanho de tipos (
sizeof(int) == 4), tamanho de structs gravadas em arquivo, um item do array para cada valor doenum. - A condição tem de ser constante: números,
sizeof,enum,#define,constexpr. Variável comum ouconstdánot constant. - O
assertconfere com o programa rodando, para com "Abortado" (código 134) e some com-DNDEBUG. Prefira ostatic_assertsempre que a regra puder ser conferida na compilação.