Tipos de variáveis em C: como funcionam na prática
Variáveis em C são só caixas com nomes e tamanhos definidos. A parte mais ignorada é que o tamanho exato depende do compilador e da arquitetura. int pode ser 16 bits num microcontrolador e 32 bits num desktop. Isso causa erros estranhos que levam horas pra rastrear. O primeiro problema real que eu encontrei foi com type promotion em operações mistas. Eu tava multiplicando um short por um unsigned char e o resultado truncava pra 8 bits num sistema embarcado. A solução foi fazer um cast explícito pros operandos: (int)a * (int)b. Sem isso, o compilador faz as promoções automaticamente mas nem sempre pro tamanho que você espera.
Tipos de variaveis em c mais comuns
Os básicos são char, short, int, long, long long, float, double e long double. Cada um tem um range mínimo garantido pelo padrão C99, mas valores maiores podem existir dependendo da plataforma. char — ocupa 1 byte. Pode ser signed ou unsigned. O padrão não especifica qual é o padrão, então se você precisa de certeza absoluta, use charsigned ou charunsigned. Em operações aritméticas, char vira int automaticamente. Isso é chamado de integral promotion e é a fonte de vários bugs silenciosos.
short e int — short tem pelo menos 16 bits. int tem pelo menos 16 bits também, mas na maioria dos sistemas modernos é 32 bits. A confusão comum é assumir que short é sempre menor que int. Em alguns sistemas embutidos, ambos têm 16 bits. Sempre verifique com sizeof(). long e long long — long tem pelo menos 32 bits. long long tem pelo menos 64 bits, definido no C99. Em sistemas Windows de 64 bits, long ainda é 32 bits. Em Linux 64 bits, long é 64 bits. Essa diferença quebra portabilidade se você depender do tamanho do tipo.
float e double — float segue IEEE 754 single precision, 32 bits, com cerca de 7 dígitos decimais de precisão. double é IEEE 754 double precision, 64 bits, com cerca de 15 dígitos. A pegadinha é que printf com %f assume double por padrão. Se passar um float, ele funciona porque acontece a promoção automática, mas expressões com float ficam duplicadas pra double internamente, gastando ciclos extras. void — não é um tipo de variável. É um tipo incompleto usado em ponteiros e retornos de função. Você não consegue declarar void x; e o compilador rejeita. Ponteiros void* podem apontar pra qualquer coisa, mas precisam de cast explícito antes de acessar o valor.
Promoção de tipos e regras que todo mundo esquece
Quando você mistura tipos numa expressão, o C faz uma sequência de conversões chamada usual arithmetic conversion. O tipo menor sobe pros levels maiores. char e short viram int. float vira double. Long double domina tudo. O que quase ninguém sabe é que unsigned e signed juntos geram comportamentoDefinedpelo padrao mas contra-intuitivo. Se unsigned int for maior que signed int na hierarquia, o signed vira unsigned. Isso significa que -1 pode se tornar um número enorme: (unsigned int)-1 é 4294967295 em um sistema de 32 bits. Já vi esse bug destruir condições em loops condicionais.
Outro detalhe importante: sizeof(char) é sempre 1, por definição do padrão. Mas 1 byte não é necessariamente 8 bits. Em algumas arquiteturas DSP, byte tem 16 bits. sizeof sempre retorna o número de bytes, não de bits. Pra saber quantos bits tem um tipo, use CHAR_BIT da diretiva
Tipos fixos: a solução para portabilidade
A biblioteca
👉 Clique no botão abaixo para saber mais sobre o assunto!
Storage classes: o lado que ninguém lê a documentation
Além do tipo de dados, existe a classe de armazenamento. auto é o padrão implícito dentro de funções. register é uma dica pro compilador tentar manter a variável em registrador, mas compiladores modernos ignoram essa dica. static dentro de função mantém o valor entre chamadas. Isso é útil pra contadores ou cache interno, mas torna a função não-reentrante, o que quebra threads. static em escopo global ou de arquivo limita a visibilidade daquela variável ou função ao arquivo de tradução atual. É a forma padrão de criarsymbols internals em bibliotecas. extern declara que uma variável existe em outro arquivo. O link editor resolve o endereço. O erro mais comum aqui é declarar extern sem definir a variável em nenhum arquivo, gerando undefined reference no linker.
thread_local, disponível no C11, cria uma variável com duração de thread. Cada thread tem sua própria cópia. Útil pra variáveis de estado que não podem ser compartilhadas sem mutex, mas adiciona overhead de acesso comparado a variáveis locais normais.
Ponteiros e a camada extra de complexidade
Ponteiros em C não são variáveis comuns. Eles guardam endereços de memória. Declarar int *p; cria um ponteiro pra int, não um array. A diferença entre *p e p é a desreferência: *p acessa o valor no endereço, p é só o endereço em si. A matemática de ponteiros depende do tipo. p++ avança sizeof(int) bytes, não 1 byte. Isso é poderoso mas letal se você não prestar atenção. Ponteiro pra char anda de 1 em 1 byte. Ponteiro pra double anda de 8 em 8 bytes numa arquitetura de 64 bits.
O tipo const aplicado a ponteiros muda completamente o comportamento. const int *p = &x; protege o valor apontado, não o ponteiro. int *const p = &x; protege o endereço, não o valor. const int *const p = &x; protege ambos. A ordem importa e o compilador não adivinha o que você quis dizer.
Alinhamento e padding: o custo invisível
Quando você declara uma struct, o compilador insere padding entre campos pra alinhar cada tipo no endereço natural dele. Um char seguido de um int pode ter 3 bytes de padding entre eles. Isso explica por que sizeof(struct) às vezes é maior que a soma dos campos. A ordem dos campos na struct afeta o tamanho final. Colocar os maiores tipos primeiro reduz padding. Uma struct com char, int, char ocupa 12 bytes em vez de 10 se os ints vierem antes dos chars. Ferramentas como #pragma pack alteram isso, mas quebram alinhamento nativo e podem causar exceptions de acessodesalinhado em arquiteturas RISC.
_Alignof e _Alignas, do C11, permitem controle fino de alinhamento. useful quando você mapeia hardware register ou comunica com DMA, mas raramente necessário em aplicação de usuário comum.
Décima de erros reais com tipos
Um caso que me pegou recentemente: declarei um contador como unsigned int pra evitar negativos, mas fiz uma subtração que resultou em wraparound. Quando o valor chegou a zero e subtraiu mais um, virou UINT_MAX. O loop que deveria terminar rodou bilhões de iterações. A correção foi usar int com verificação de limite em vez de unsigned pra contagem regressiva. Outro erro clássico: usar %d pra imprimir long. Isso causa leitura errada dos bytes porque printf não tem type checking. A flagsa certa é %ld pra long e %lld pra long long. Compiladores modernos avisam com -Wformat mas o aviso fica silencioso se você não ativar warnings. Ativar -Wall -Wextra deve ser o padrão em qualquer build.
Um problema mais sutil: overflow de int em expressões constantes. 100000 * 100000 parecem caber em int mas resultam em 1410065408 em vez de 10000000000. O overflow de signed int é undefined behavior pelo padrão C. Compiladores não avisam e o resultado pode mudar entre otimizações. Sempre use literais com sufixo: 100000L * 100000L.