C Novidades do C99 ao C23

O C de hoje não é o C de 1989. A cada dez anos, mais ou menos, a linguagem ganha uma versão nova, com recursos que deixam o código mais curto e mais seguro. Este guia ensina o C23, a versão mais recente, mas você vai ler muito código escrito em C89, C99 e C11. Esta aula é o mapa: o que cada versão trouxe, como saber qual versão o compilador está usando, e o C23 em detalhe, com exemplos que compilam no gcc 15.

Você vai aprender
  • A linha do tempo: K&R, C89, C99, C11, C17, C23 e o próximo, o C2y.
  • Como pedir uma versão ao compilador (-std=) e como o programa descobre a sua (__STDC_VERSION__).
  • Os destaques do C99 e do C11, que você vai encontrar em todo código "moderno".
  • O C23 por partes: bool, nullptr, constexpr, typeof, auto, 0b1010, 1'000, #embed, #elifdef, atributos, static_assert sem mensagem, unreachable(), memset_explicit, strdup e %b.
  • O que mudou de um jeito que quebra código antigo (int f();).
  • O que já foi aprovado para o C2y e o que ainda é só proposta.

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, e conferidos também no editor do guia (Clang 22). Quando os dois discordam, a aula diz. A história completa das versões está em Versões do C.


A ideia: as edições de um dicionário

Um dicionário ganha edições novas de tempos em tempos. Cada edição acrescenta palavras ("selfie", "aplicativo"), às vezes corrige definições e, raramente, tira alguma coisa. Um texto escrito com a edição de 1990 continua legível com a de 2024; o contrário nem sempre: quem só tem a edição velha não acha "aplicativo".

Com o C é igual. A "edição" oficial se chama padrão (standard), escrita por um comitê internacional (ISO). Programas antigos quase sempre compilam com o compilador novo. Mas um programa que usa nullptr (C23) não compila num compilador que só conhece o C17. E o compilador precisa saber qual edição você está seguindo: é para isso que serve a opção -std=.


1. A linha do tempo

VersãoAnoPrincipais novidades
K&R C1978O C do livro de Kernighan e Ritchie. Não era um padrão oficial.
C89 / C90 (ANSI C)1989/1990O primeiro padrão. Protótipos de função, void, const, a biblioteca padrão (stdio.h, stdlib.h, string.h…). Comentários só com /* */ e variáveis só no começo do bloco.
C991999Comentários //, for (int i = 0; …), bool via stdbool.h, stdint.h (int32_t), long long, snprintf, inicializadores com nome (.x = 1), VLAs, inline, tgmath.h.
C112011_Generic, _Static_assert, _Noreturn, _Alignas, threads (threads.h) e atômicos opcionais, structs e unions anônimas, strings u8"…". O gets foi removido.
C17 (ou C18)2018Só correções do C11. Nenhum recurso novo.
C232024bool/true/false como palavras-chave, nullptr, constexpr, typeof, auto, atributos [[…]], 0b1010, 1'000, #embed, #elifdef, static_assert sem mensagem, unreachable(), strdup, %b…
C2yem preparoO próximo. O "y" é provisório: ninguém sabe o ano. Veja a seção 6.

O nome vem do ano em que o texto ficou pronto: o C23 foi aprovado em 2023 e publicado pela ISO em 2024 (por isso o valor do __STDC_VERSION__, logo abaixo, é 202311: novembro de 2023).


2. Qual versão o compilador está usando?

O programa pode perguntar. A macro __STDC_VERSION__, que o próprio compilador define, diz o ano e o mês da versão:

#include <stdio.h>

int main(void) {
    printf("__STDC_VERSION__ = %ld\n", __STDC_VERSION__);
    return 0;
}
$ gcc versao.c -o versao && ./versao       « sem -std: o padrão do gcc 15
__STDC_VERSION__ = 202311
$ gcc -std=c99 versao.c -o versao && ./versao
__STDC_VERSION__ = 199901
$ gcc -std=c11 versao.c -o versao && ./versao
__STDC_VERSION__ = 201112
$ gcc -std=c17 versao.c -o versao && ./versao
__STDC_VERSION__ = 201710
$ gcc -std=c23 versao.c -o versao && ./versao
__STDC_VERSION__ = 202311
-std=c99, -std=c11…
"Standard": pede ao gcc que siga aquela edição. Recursos mais novos passam a dar erro.
sem -std
O gcc 15 usa gnu23: o C23 mais algumas extensões do gcc. Compiladores mais velhos usavam gnu17 ou gnu11, e é por isso que tanta gente ainda vê erros em nullptr ou bool.
199901, 201112…
Ano e mês: 199901 = janeiro de 1999. Com #if __STDC_VERSION__ >= 202311L, um programa pode usar um recurso novo só quando ele existe (aula Compilação Condicional).

Com -std=c89 nem esse programa compila: o __STDC_VERSION__ só passou a existir depois do C89. O editor do guia compila em C23, como o laboratório; outras versões só com data-flags na própria caixa (veja O Editor do Guia).


3. C99 e C11: o "C moderno" que você já usa

Muito do que este guia usa desde o primeiro dia veio do C99. Para sentir a diferença, veja este programa curtinho compilado como C89:

#include <stdio.h>

int main(void) {
    for (int i = 0; i < 3; i++) {   // C99: variável dentro do for, comentário //
        printf("%d ", i);
    }
    printf("\n");
    return 0;
}
$ gcc -std=c89 -Wall -Wextra linhas.c -o linhas
linhas.c: In function ‘main’:
linhas.c:4:5: error: ‘for’ loop initial declarations are only allowed in C99 or C11 mode
    4 |     for (int i = 0; i < 3; i++) {   // C99: variável dentro do for, comentário //
      |     ^~~
linhas.c:4:5: note: use option ‘-std=c99’, ‘-std=gnu99’, ‘-std=c11’ or ‘-std=gnu11’ to compile your code
linhas.c:4:37: error: C++ style comments are not allowed in ISO C90
    4 |     for (int i = 0; i < 3; i++) {   // C99: variável dentro do for, comentário //
      |                                     ^
linhas.c:4:37: note: (this will be reported only once per input file)

Dois erros em uma linha: "declarações no começo do for só são permitidas no modo C99 ou C11" e "comentários no estilo C++ não são permitidos no ISO C90". Em C89, o i tinha de ser declarado no começo do main e o comentário escrito com /* */. É assim que muito código antigo é escrito, e agora você sabe por quê.

RecursoVersãoAula
// comentário, for (int i…), declarar variável no meio do blocoC99Comentários, for
bool com #include <stdbool.h>C99Booleanos
int32_t, uint8_t (stdint.h)C99Inteiros de Tamanho Fixo
snprintfC99Texto ↔ Número
struct ponto p = { .x = 1, .y = 2 };C99Structs
inlineC99Funções inline
_GenericC11_Generic
_Static_assertC11static_assert
_NoreturnC11Atributos
gets removido (use fgets)C11Ler uma Linha
threads.h (opcional)C11Threads

4. O C23 em detalhe

O C23 foi a maior atualização desde o C99. Boa parte dela traz para o C ideias que já existiam no C++. Este programa usa várias de uma vez:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
    bool aberto = true;                 // bool, true e false sem include
    int *p = nullptr;                   // nullptr
    constexpr int MAX = 1'000'000;      // constexpr e separador '
    int mascara = 0b1010;               // binário
    auto total = 2.5 * 4;               // auto deduz double
    typeof(total) metade = total / 2;   // typeof
    printf("%d %d %d %d\n", aberto, p == nullptr, MAX, mascara);
    printf("%.1f %.1f\n", total, metade);
    printf("%b\n", 10u);                // %b no printf
    char *copia = strdup("Ana");        // strdup agora é padrão
    printf("%s\n", copia);
    free(copia);
    printf("%ld\n", __STDC_VERSION__);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra c23.c -o c23
$ ./c23
1 1 1000000 10
10.0 5.0
1010
Ana
202311

Agora, uma por uma.

bool, true e false sem include

Até o C17, era preciso #include <stdbool.h>; sem ele, bool dava unknown type name ‘bool’. No C23 são palavras-chave. O stdbool.h continua existindo (e não faz mal), só não é mais necessário. Aula: Booleanos.

nullptr

O "ponteiro nulo" com tipo próprio, no lugar do NULL, que é só uma macro para 0 ou ((void*)0). Com nullptr, o compilador sempre sabe que é um ponteiro, inclusive em funções variádicas e no _Generic. Aula: Ponteiro Nulo.

constexpr

Uma constante que o compilador conhece na compilação, e que pode ser usada onde o C exige uma constante (tamanho de array global, case, static_assert). Diferente de const, que só proíbe mudar. No C23, constexpr vale só para variáveis; funções constexpr (que existem no C++) não chegaram ao C. Aula: Constantes.

0b1010 e 1'000'000

Números em binário com o prefixo 0b (antes, só uma extensão do gcc) e o apóstrofo como separador de dígitos, para ler números grandes: 1'000'000 é um milhão. O apóstrofo pode ir em qualquer posição e não muda o valor. Aula: Inteiros.

typeof, typeof_unqual e auto

  • typeof(x) é "o tipo de x". Era uma extensão do gcc havia décadas; agora é padrão. Muito usado em macros.
  • typeof_unqual(x) é o mesmo tipo, mas sem const e volatile. Com const int a = 5;, typeof(a) b cria um const int, que não pode mudar; typeof_unqual(a) c cria um int comum.
  • auto total = 2.5 * 4; deixa o compilador deduzir o tipo pelo valor (double). No C antigo, auto existia mas não servia para nada ("variável local", que já é o normal). No C23 ele funciona só para variáveis com valor inicial; não vale para parâmetros nem para o retorno de funções.
$ cat typeofu.c
#include <stdio.h>

int main(void) {
    const int a = 5;
    typeof(a) b = 6;
    typeof_unqual(a) c = 7;
    c = c + 1;
    printf("%d %d %d\n", a, b, c);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra typeofu.c -o typeofu && ./typeofu
5 6 8

Use auto com moderação: int idade = 29; diz mais a quem lê do que auto idade = 29;.

%b no printf

Mostra um número em binário: printf("%b", 10u) escreve 1010. Funciona com a biblioteca do laboratório (glibc).

No editor do guia, o %b não funciona

A biblioteca do editor (wasi-libc, baseada na musl) ainda não conhece o %b. Quando o printf encontra um formato que não conhece, ele desiste da linha inteira: printf("[%b]\n", 10u) não mostra nem o [, e o Clang não avisa nada. No programa c23.c acima, a linha 1010 simplesmente some no editor. Para mostrar binário em qualquer biblioteca, faça um laço com >> e & (aula Operadores de Bits).

strdup e strndup

strdup("Ana") ("string duplicate") reserva memória com malloc e copia o texto para lá; depois, free. Existiam havia muito tempo no POSIX (Linux, macOS); o C23 os trouxe para o padrão. No editor, strdup funciona, mas strndup ainda não existe (use of undeclared identifier 'strndup'). Aula: Strings Dinâmicas.

Experimente: rode o programa do C23 no editor. Repare que a linha do %b não aparece (explicado acima). Depois troque auto total = 2.5 * 4; por auto total = 2 * 4; e veja o que muda na segunda linha: o tipo deduzido agora é int, e o %.1f passa a receber o tipo errado (leia o aviso).

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
    bool aberto = true;
    int *p = nullptr;
    constexpr int MAX = 1'000'000;
    int mascara = 0b1010;
    auto total = 2.5 * 4;
    typeof(total) metade = total / 2;
    printf("%d %d %d %d\n", aberto, p == nullptr, MAX, mascara);
    printf("%.1f %.1f\n", total, metade);
    printf("%b\n", 10u);
    char *copia = strdup("Ana");
    printf("%s\n", copia);
    free(copia);
    printf("%ld\n", __STDC_VERSION__);
    return 0;
}

#embed: um arquivo dentro do programa

Uma diretiva nova do pré-processador (aula Pré-processador): ela coloca os bytes de um arquivo dentro de um array, na compilação. Antes, isso exigia ferramentas externas (como o xxd -i). Serve para embutir imagens, sons, textos de ajuda:

#include <stdio.h>

static const char mensagem[] = {
#embed "aviso.txt"
    , '\0'
};

int main(void) {
    printf("%s", mensagem);
    printf("(%zu bytes)\n", sizeof mensagem - 1);
    return 0;
}
$ cat aviso.txt
Loja fechada no domingo.
$ gcc -std=c23 -Wall -Wextra emb.c -o emb
$ ./emb
Loja fechada no domingo.
(25 bytes)
$ rm aviso.txt; ./emb      « o arquivo já está dentro do programa
Loja fechada no domingo.
(25 bytes)
#embed "aviso.txt"
Vira a lista dos bytes do arquivo, separados por vírgula: 76, 111, 106, 97, … ("L", "o", "j", "a"…).
, '\0'
O arquivo não termina com o '\0' que toda string precisa (aula Strings). A vírgula e o '\0' o acrescentam.
(25 bytes)
24 letras e espaços mais o \n do fim da linha.

Repare na diferença para ler o arquivo com fopen (aula Ler Arquivos): com #embed, o arquivo é lido pelo compilador, uma vez; o programa pronto já carrega o conteúdo e não precisa do arquivo para rodar.

#elifdef, #elifndef e #warning

Atalhos do pré-processador: #elifdef X é o mesmo que #elif defined(X), e #elifndef X, o mesmo que #elif !defined(X). O #warning "texto", que já existia no gcc, virou padrão: ele mostra um aviso na compilação.

#include <stdio.h>

#define LINUX

int main(void) {
#ifdef WINDOWS
    printf("Windows\n");
#elifdef LINUX
    printf("Linux\n");
#else
    printf("outro\n");
#endif
    return 0;
}
$ gcc -std=c23 -Wall -Wextra elifdef.c -o elifdef && ./elifdef
Linux

Atributos [[…]] e static_assert sem mensagem

[[nodiscard]], [[maybe_unused]], [[deprecated]], [[fallthrough]] e [[noreturn]] contam ao compilador o que ele não adivinha (aula Atributos). E o static_assert virou palavra-chave, com a mensagem opcional: static_assert(sizeof(int) == 4); (aula static_assert).

unreachable()

De stddef.h. Diz ao compilador "o programa nunca chega aqui". Ele usa isso para otimizar e para não avisar sobre um caminho que você sabe que não existe:

#include <stdio.h>
#include <stddef.h>

const char *nome_dia(int d) {
    switch (d) {
    case 0: return "domingo";
    case 6: return "sábado";
    default:
        if (d > 0 && d < 6) return "dia útil";
        unreachable();
    }
}

int main(void) {
    printf("%s\n", nome_dia(6));
    return 0;
}
$ gcc -std=c23 -Wall -Wextra unreach.c -o unreach && ./unreach
sábado

Sem o unreachable(), o gcc avisaria que a função pode chegar ao fim sem return. Cuidado: se o programa chegar ali (alguém chamou nome_dia(9)), é comportamento indefinido, não um erro com mensagem (aula Comportamento Indefinido). Use só quando tiver certeza; para conferir, prefira assert.

memset_explicit

Apagar uma senha da memória com memset(senha, 0, …) parece fácil, mas o otimizador pode remover esse memset se a variável não for mais lida depois ("para que zerar algo que ninguém vai ler?"). A senha fica na memória. O memset_explicit, de string.h, nunca é removido:

#include <stdio.h>
#include <string.h>

int main(void) {
    char senha[16] = "segredo123";
    printf("Usando a senha...\n");
    memset_explicit(senha, 0, sizeof senha);
    printf("Apagada: [%s]\n", senha);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra memset.c -o memset && ./memset
Usando a senha...
Apagada: []

No editor do guia ele ainda não existe: o Clang diz use of undeclared identifier 'memset_explicit'. A aula Segurança volta ao assunto.

Outras novidades do C23

RecursoExemplogcc 15Editor
Zerar com chaves vaziasint v[5] = {};SimSim
enum com tipo escolhidoenum cor : unsigned char { … }; (1 byte)SimSim
Conta que avisa se estourarckd_add(&r, a, b) de stdckdint.h devolve true se não coubeSimSim
Funções de bitsstdc_count_ones(255u) de stdbit.h (conta os bits 1: 8)SimNão (falta o stdbit.h)
Inteiros de qualquer largura_BitInt(12) x = 100;SimSim
Parâmetro sem nome na definiçãovoid botao(int) { … }SimSim
alignas, thread_local sem includethread_local static int n;SimSim

Missão: modernize este programa escrito no estilo C99. Troque o #define MAX 1000000 por constexpr int MAX = 1'000'000; dentro do main (com o separador), troque os dois NULL por nullptr e apague o #include <stdbool.h>, que não é mais necessário. A saída tem de continuar 1000000 0 1.

#include <stdio.h>
#include <stdbool.h>

#define MAX 1000000

int main(void) {
    bool achou = false;
    int *p = NULL;
    printf("%d %d %d\n", MAX, achou, p == NULL);
    return 0;
}
#include <stdio.h>

int main(void) {
    constexpr int MAX = 1'000'000;
    bool achou = false;
    int *p = nullptr;
    printf("%d %d %d\n", MAX, achou, p == nullptr);
    return 0;
}

5. O que o C23 quebrou: int f();

Quase todo código antigo compila igual no C23. A exceção mais importante: uma declaração com parênteses vazios. Até o C17, int soma(); queria dizer "uma função que recebe não sei quantos parâmetros" (um resto do K&R C). No C23, quer dizer "recebe nenhum", igual a int soma(void);:

#include <stdio.h>

int soma();

int main(void) {
    printf("%d\n", soma(2, 3));
    return 0;
}

int soma(int a, int b) { return a + b; }
$ gcc -std=c23 -Wall -Wextra velho.c -o velho
velho.c: In function ‘main’:
velho.c:6:20: error: too many arguments to function ‘soma’; expected 0, have 2
    6 |     printf("%d\n", soma(2, 3));
      |                    ^~~~ ~
velho.c:3:5: note: declared here
    3 | int soma();
      |     ^~~~
velho.c: At top level:
velho.c:10:5: error: conflicting types for ‘soma’; have ‘int(int,  int)’
   10 | int soma(int a, int b) { return a + b; }
      |     ^~~~
velho.c:3:5: note: previous declaration of ‘soma’ with type ‘int(void)’
    3 | int soma();
      |     ^~~~

"Argumentos demais para a função soma; esperava 0, recebeu 2" e "tipos em conflito para soma": a declaração da linha 3 agora diz int(void). O conserto é escrever o protótipo completo, int soma(int a, int b); (aula Protótipos). Também saíram do C23 as definições de função no estilo K&R (com os tipos dos parâmetros depois dos parênteses), que ninguém deveria usar havia 30 anos.


6. O que vem no C2y

O comitê já trabalha na próxima versão, apelidada de C2y. Nada está publicado, e tudo pode mudar até o fim. Alguns recursos já foram aprovados para o rascunho, e o gcc 15 e o Clang 22 testam parte deles com -std=c2y:

Recurso (rascunho do C2y)Exemplogcc 15Clang 22 (editor)
Prefixo 0o para octal0o17 (= 15), mais claro que o velho 017SimSim
_Generic com um tipo_Generic(int, int: "int", …)SimSim
Laços com nomefora: for (…) { … break fora; }SimSim
Declaração dentro do ifif (int n = ler(); n > 0)SimNão
Operador para contar itens de array_Countof(notas)NãoSim

E o defer? É uma proposta muito comentada: um comando que roda "na saída do bloco", útil para nunca esquecer um free ou fclose. Por enquanto ele está sendo discutido numa especificação técnica separada (um documento experimental), não no rascunho do C2y, e nem o gcc 15 nem o Clang 22 o aceitam (error: unknown type name ‘defer’). Se e quando ele vai entrar no C, ninguém sabe ainda.

Recursos de rascunho não são para código de verdade

O -std=c2y existe para testar. A escrita pode mudar até a publicação (o próprio _Countof já trocou de nome durante as discussões). Para programas que você vai manter, fique no -std=c23.


7. Qual versão usar

SituaçãoUse
Aprender, projetos novos, gcc 14+ ou Clang 18+-std=c23 (é o que este guia faz)
Projeto que precisa compilar em compiladores mais velhos ou em microcontroladores-std=c11 ou -std=c17, com stdbool.h, NULL e _Static_assert
Código muito antigo, ou o projeto exige-std=c99 (raramente -std=c89)
Testar o futuro-std=c2y, só para experimentar

Escreva sempre o -std= no comando (ou no Makefile, aula Makefile). Assim o programa compila igual em qualquer computador, mesmo que o padrão do compilador mude na próxima versão.


Quando dá errado

error: ‘nullptr’ undeclared (first use in this function)
$ gcc -std=c17 -Wall -Wextra antigo.c -o antigo
antigo.c: In function ‘main’:
antigo.c:4:14: error: ‘nullptr’ undeclared (first use in this function)
    4 |     int *p = nullptr;
      |              ^~~~~~~
antigo.c:4:14: note: each undeclared identifier is reported only once for each function it appears in

O que significa: o código usa um recurso do C23, mas o compilador está em C17 (pelo -std= ou porque é uma versão antiga do gcc). Para ele, nullptr é só um nome desconhecido. O mesmo acontece com bool (unknown type name ‘bool’), constexpr e typeof.

Como consertar: compile com -std=c23. Se não puder, use as formas antigas: NULL, #include <stdbool.h>, #define ou enum.

error: too many arguments to function ‘soma’; expected 0, have 2

O que significa: código antigo com int soma(); compilado em C23, onde () quer dizer "nenhum parâmetro".

Como consertar: escreva o protótipo completo, int soma(int a, int b);.

Funciona no gcc, mas no editor use of undeclared identifier 'memset_explicit' (ou a linha do %b some)

O que significa: o compilador entende o C23, mas a biblioteca do editor (wasi-libc) ainda não tem todas as funções novas. Linguagem e biblioteca andam separadas: o mesmo acontece em outros sistemas.

Como consertar: confira no laboratório (gcc + glibc). No editor, use as alternativas: memset comum, um laço para o binário, malloc + memcpy no lugar de strndup.


Referência rápida

Versão__STDC_VERSION__-std=Lembre por
C89/C90(não existe)c89 ou c90Primeiro padrão, /* */
C99199901Lc99//, for (int i…), stdint.h, stdbool.h
C11201112Lc11_Generic, _Static_assert, sem gets
C17201710Lc17Correções
C23202311Lc23 (padrão do gcc 15: gnu23)bool, nullptr, constexpr, [[…]], #embed
C2y(provisório)c2yRascunho: 0o17, laços com nome…

Exercícios

1. O ponteiro nulo do C23

Complete com a palavra-chave do C23 para o ponteiro nulo:

int *p = ;

2. Pedir a versão

Complete a opção do gcc para compilar como C11:

gcc programa.c -o programa

3. Em que versão os comentários // e o for (int i = 0; …) entraram no C?

4. No C23, o que quer dizer int f();?

5. Sobre o defer, o que é verdade hoje?

6. O que o #embed "logo.png" faz?

Missão final: a promoção embutida

Missão: a caixa tem duas abas: o programa e o arquivo frase.txt. Hoje o array frase está vazio e o programa mostra (0 bytes). Use #embed para colocar o conteúdo de frase.txt dentro do array (lembre da vírgula antes do '\0'). O resultado deve ser a frase e (37 bytes): são 34 caracteres, mas o "ç" e o "ã" ocupam 2 bytes cada em UTF-8 (aula Acentos e UTF-8), mais 1 byte da quebra de linha no fim do arquivo.

Promoção: 3 cadernos por 10 reais!
(37 bytes)
#include <stdio.h>

static const char frase[] = {
    // coloque o #embed aqui
    '\0'
};

int main(void) {
    printf("%s", frase);
    printf("(%zu bytes)\n", sizeof frase - 1);
    return 0;
}
Promoção: 3 cadernos por 10 reais!

#include <stdio.h>

static const char frase[] = {
#embed "frase.txt"
    , '\0'
};

int main(void) {
    printf("%s", frase);
    printf("(%zu bytes)\n", sizeof frase - 1);
    return 0;
}

Resumo

  • Versões: K&R (1978), C89/C90, C99, C11, C17, C23 e o C2y em preparo. Peça uma com -std=c23; descubra com __STDC_VERSION__ (202311L no C23). O gcc 15 já usa gnu23 por padrão.
  • C99 trouxe //, for (int i…), stdbool.h, stdint.h, snprintf. C11 trouxe _Generic, _Static_assert, _Noreturn e removeu o gets.
  • C23: bool/true/false sem include, nullptr, constexpr, typeof/typeof_unqual, auto, 0b1010, 1'000, #embed, #elifdef, [[atributos]], static_assert sem mensagem, unreachable(), memset_explicit, strdup, %b.
  • No C23, int f(); quer dizer "sem parâmetros": código antigo que dependia disso não compila.
  • No editor do guia, o compilador entende o C23 inteiro, mas a biblioteca ainda não tem %b, memset_explicit, strndup nem stdbit.h.
  • C2y: 0o17, laços com nome, _Generic com tipo e outros já estão no rascunho; defer ainda é proposta à parte.