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.

Você vai aprender
  • 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_assert C11, static_assert com assert.h e static_assert sem 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 constexpr ajuda).
  • 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_assertassert
Quando confereNa compilaçãoCom o programa rodando
O que pode conferirSó o que o compilador já sabe: sizeof, constantes, enumQualquer coisa: variáveis, o que o usuário digitou
Quando falhaErro de compilação: o programa não é geradoO programa para com "Abortado" (código 134)
Custa tempo ao rodar?Nada: no programa pronto ele nem existeUm 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_assert pode ficar fora de qualquer função (como aqui), dentro de uma função ou até dentro de uma struct. 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_assert que 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 falha nã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.

EscritaDesdePrecisa deMensagem
_Static_assert(cond, "msg");C11NadaObrigató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");C23Nada (é palavra-chave)Opcional
static_assert(cond);C23NadaSem 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 enum vale 7, porque os valores começam em 0. Esse "item extra no fim" é um truque comum para contar quantos valores o enum tem.

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 assert só olha a variável quando o programa passa pela linha 8.
  • Assertion `estoque >= 0' failed. = "a afirmação estoque >= 0 falhou", com o nome do programa, do arquivo, a linha e a função.
  • Abortado é o Bash avisando que o programa foi interrompido pelo sinal SIGABRT (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çãoUse
Tamanho de tipo ou de struct, valores de enum, macros, tamanho de arraystatic_assert: o erro aparece antes de existir programa
Valor de variável, resultado de conta, parâmetro recebido por uma funçãoassert: 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.

A regra passa no gcc e falha em outro computador (ou ao contrário)

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

EscritaO 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 ficarFora 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:

(sizeof(double) == 8);

2. Quantos itens

Complete para calcular quantos itens tem o array notas:

sizeof 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 failed e nenhum programa é gerado.
  • C11: _Static_assert, ou static_assert com #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 do enum.
  • A condição tem de ser constante: números, sizeof, enum, #define, constexpr. Variável comum ou const dá not constant.
  • O assert confere com o programa rodando, para com "Abortado" (código 134) e some com -DNDEBUG. Prefira o static_assert sempre que a regra puder ser conferida na compilação.