C99, C11 e C17: o que mudou em cada versão
1. Contexto histórico e motivações das revisões
1.1. Do C89/K&R ao C99: necessidade de modernização e padronização internacional
O padrão original da linguagem C, conhecido como C89 ou ANSI C, foi publicado em 1989 e consolidado como ISO/IEC 9899:1990. Durante a década de 1990, a linguagem precisava evoluir para acompanhar hardware mais rápido, compiladores mais inteligentes e a crescente demanda por código numérico e científico. O C99 (ISO/IEC 9899:1999) surgiu como uma reforma significativa, introduzindo recursos modernos que facilitavam a escrita de código mais limpo e eficiente.
1.2. C11: correções, concorrência e segurança em um cenário multicore
Com a popularização de processadores multicore no início dos anos 2000, a ausência de um modelo de memória formal e suporte nativo a threads tornou-se um gargalo. O C11 (ISO/IEC 9899:2011) focou em corrigir ambiguidades do C99, adicionar suporte a concorrência e melhorar a segurança de funções de biblioteca.
1.3. C17: revisão de bugfixes sem novas funcionalidades
O C17 (ISO/IEC 9899:2018) foi uma versão puramente corretiva. Nenhuma nova funcionalidade foi introduzida; apenas defeitos (Defect Reports) do C11 foram resolvidos, tornando a especificação mais clara e consistente sem quebrar código existente.
2. Principais mudanças do C99
2.1. Declarações misturadas com código e escopo de bloco
No C89, todas as declarações de variáveis deviam aparecer no início de um bloco. O C99 permitiu declará-las em qualquer ponto, próximo ao uso:
#include <stdio.h>
int main(void) {
printf("Digite um numero: ");
int x; // declaração no meio do código (válido em C99+)
scanf("%d", &x);
printf("Voce digitou: %d\n", x);
return 0;
}
2.2. Tipos de dados: long long, _Bool e complex
Foram adicionados tipos inteiros de 64 bits (long long), o tipo booleano (_Bool) e suporte a números complexos:
#include <stdio.h>
#include <complex.h>
int main(void) {
long long big = 123456789012345LL;
_Bool flag = 1;
double complex z = 1.0 + 2.0*I;
printf("long long: %lld\n", big);
printf("flag: %d\n", flag);
printf("complex: %.1f + %.1fi\n", creal(z), cimag(z));
return 0;
}
2.3. Novas funcionalidades: inline functions, designated initializers e variadic macros
Designated initializers permitem inicializar membros específicos de estruturas:
#include <stdio.h>
typedef struct {
int x;
int y;
char nome[20];
} Ponto;
int main(void) {
Ponto p = { .y = 10, .x = 5, .nome = "origem" };
printf("(%d, %d) - %s\n", p.x, p.y, p.nome);
return 0;
}
Variadic macros aceitam número variável de argumentos:
#include <stdio.h>
#define DEBUG(msg, ...) printf("[DEBUG] " msg "\n", ##__VA_ARGS__)
int main(void) {
DEBUG("Iniciando...");
DEBUG("Valor: %d", 42);
return 0;
}
3. Biblioteca padrão expandida no C99
3.1. Cabeçalhos adicionados: stdint.h, inttypes.h e stdbool.h
Esses cabeçalhos padronizaram tipos inteiros de largura fixa e o tipo booleano:
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
int main(void) {
uint32_t valor = 0xFFFFFFFF;
bool ativo = true;
printf("Valor: %" PRIu32 "\n", valor);
printf("Ativo: %s\n", ativo ? "sim" : "nao");
return 0;
}
3.2. Suporte a ponto flutuante: fenv.h e funções matemáticas extras
O cabeçalho fenv.h permite controlar exceções de ponto flutuante:
#include <stdio.h>
#include <fenv.h>
#include <math.h>
#pragma STDC FENV_ACCESS ON
int main(void) {
feclearexcept(FE_ALL_EXCEPT);
double resultado = 1.0 / 0.0; // infinito
if (fetestexcept(FE_DIVBYZERO)) {
printf("Divisao por zero detectada!\n");
}
printf("Resultado: %f\n", resultado);
return 0;
}
3.3. Restrict qualifier: otimização de aliasing para compiladores
O qualificador restrict informa ao compilador que um ponteiro é a única maneira de acessar o objeto apontado:
void copia(int *restrict dest, const int *restrict src, size_t n) {
for (size_t i = 0; i < n; i++) {
dest[i] = src[i];
}
}
4. Principais mudanças do C11
4.1. Modelo de memória e suporte a threads (_Thread_local, threads.h)
O C11 introduziu um modelo de memória formal e suporte nativo a threads:
#include <stdio.h>
#include <threads.h>
_Thread_local int contador = 0;
int funcao_thread(void *arg) {
contador++;
printf("Thread %s: contador = %d\n", (char*)arg, contador);
return 0;
}
int main(void) {
thrd_t t1, t2;
thrd_create(&t1, funcao_thread, "A");
thrd_create(&t2, funcao_thread, "B");
thrd_join(t1, NULL);
thrd_join(t2, NULL);
return 0;
}
4.2. Anonymous structs/unions e static assertions (_Static_assert)
Structs e unions anônimos simplificam agrupamentos:
#include <stdio.h>
#include <stddef.h>
typedef struct {
union {
struct { int x, y; };
struct { int w, h; };
};
} Ponto;
_Static_assert(sizeof(Ponto) == 2 * sizeof(int), "Tamanho inesperado");
int main(void) {
Ponto p = { .x = 10, .y = 20 };
printf("w=%d, h=%d\n", p.w, p.h); // acessa os mesmos bytes
return 0;
}
4.3. Generic selections (_Generic) e noreturn functions (_Noreturn)
Expressões genéricas permitem sobrecarga baseada no tipo:
#include <stdio.h>
#define tipo(x) _Generic((x), \
int: "int", \
double: "double", \
char*: "string", \
default: "outro" \
)
_Noreturn void erro_fatal(void) {
printf("Erro fatal!\n");
while(1); // ou exit()
}
int main(void) {
printf("10 e %s\n", tipo(10));
printf("3.14 e %s\n", tipo(3.14));
printf("\"oi\" e %s\n", tipo("oi"));
// erro_fatal(); // descomente para testar
return 0;
}
5. Segurança e previsibilidade no C11
5.1. Bounds-checking interfaces (anexo K) e funções seguras
O anexo K introduziu funções como fopen_s e strcpy_s que verificam limites:
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
#include <string.h>
int main(void) {
char buffer[10];
errno_t err = strcpy_s(buffer, sizeof(buffer), "exemplo");
if (err == 0) {
printf("Copiado: %s\n", buffer);
} else {
printf("Erro na copia\n");
}
return 0;
}
5.2. Analyzeability e fast-math pragmas
O C11 definiu pragmas para controle de otimizações:
#pragma STDC FP_CONTRACT OFF
#pragma STDC FENV_ACCESS ON
5.3. Remoção do suporte obrigatório a gets() e outras funções perigosas
A função gets() foi removida do padrão, pois não permitia controle de tamanho do buffer, sendo substituída por fgets().
6. C17: correções técnicas sem novas features
6.1. Defeitos corrigidos no padrão C11 (DRs resolvidos)
O C17 corrigiu mais de 30 Defect Reports, incluindo ambiguidades em macros variádicas e comportamento de _Atomic.
6.2. Esclarecimentos sobre comportamento indefinido e alignment
Esclareceu-se que acessar membros de union para reinterpretação de bytes é definido em C (diferente de C++), desde que o membro lido seja o último escrito.
6.3. Impacto na portabilidade: por que o C17 não quebrou código existente
Por ser apenas corretivo, todo código C11 válido continua válido em C17. A versão é identificada pela macro __STDC_VERSION__ como 201710L.
7. Compatibilidade e migração entre versões
7.1. Diferenças de compiladores (GCC, Clang, MSVC) na implementação de cada padrão
- GCC: suporta C99 desde a versão 3.0, C11 desde 4.6, C17 desde 8.0
- Clang: suporta todos os padrões desde versões iniciais
- MSVC: implementou C99 parcialmente apenas no Visual Studio 2013+; C11 e C17 têm suporte limitado
7.2. Boas práticas: como escrever código compatível com C99/C11/C17
#include <stdio.h>
#if __STDC_VERSION__ >= 201112L
#define NO_RETURN _Noreturn
#else
#define NO_RETURN
#endif
NO_RETURN void fim(void) {
printf("Encerrando...\n");
while(1);
}
7.3. Ferramentas para detecção de conformidade (macros __STDC_VERSION__)
#include <stdio.h>
int main(void) {
#if __STDC_VERSION__ >= 201710L
printf("Compilador C17\n");
#elif __STDC_VERSION__ >= 201112L
printf("Compilador C11\n");
#elif __STDC_VERSION__ >= 199901L
printf("Compilador C99\n");
#else
printf("Compilador C89/C90\n");
#endif
return 0;
}
8. Legado e futuro: o que veio depois (C23)
8.1. Resumo das funcionalidades incorporadas no C23 (sem detalhamento profundo)
O C23 (ISO/IEC 9899:2024) trouxe nullptr, typeof, melhorias em static_assert sem necessidade de cabeçalho, atributos padronizados ([[nodiscard]], [[maybe_unused]]) e suporte a arrays de tamanho variável em mais contextos.
8.2. Lições das transições C99 → C11 → C17 para adoção de novos padrões
Cada transição mostrou que a adoção leva anos. C99 demorou quase uma década para ser amplamente suportado. C11 ainda não é completamente implementado pelo MSVC. C17 foi adotado rapidamente por ser apenas corretivo.
8.3. Recomendações para projetos que desejam migrar para versões recentes da linguagem
- Use
__STDC_VERSION__para detectar o padrão disponível - Prefira C11 ou C17 para novos projetos, pois oferecem melhor segurança e suporte a threads
- Evite recursos do anexo K (bounds-checking) se precisar de portabilidade máxima, pois nem todos os compiladores o implementam
- Teste com múltiplos compiladores (GCC, Clang, MSVC) para garantir compatibilidade
Referências
- ISO/IEC 9899:2018 - Padrão C17 (rascunho final) — Documento oficial do padrão C17 com todas as correções em relação ao C11
- C99 Standard (ISO/IEC 9899:1999) - Rascunho público — Rascunho do padrão C99 com todas as funcionalidades introduzidas
- C11 Standard (ISO/IEC 9899:2011) - Rascunho final — Documento de referência para o padrão C11, incluindo modelo de memória e threads
- Diferenças entre C99, C11 e C17 - cppreference.com — Tabela comparativa detalhada das mudanças entre as versões do padrão C
- GCC C Language Standards — Documentação oficial do GCC sobre suporte aos padrões C, incluindo flags de compilação
- Clang Language Compatibility — Status de implementação dos padrões C no compilador Clang
- MSVC C Standards Conformance — Página oficial da Microsoft sobre conformidade do MSVC com os padrões C