Toda variável em C tem um tipo, escolhido quando você a declara e fixo por toda a vida dela. O tipo decide três coisas: quantos bytes a variável ocupa, como esses bytes são interpretados e quais operações fazem sentido nela.
A lista de tipos do C é curta. A complexidade está nos modificadores.
Os quatro tipos base
charguarda um byte. É usado para caracteres isolados ('A'), para os bytes de uma string e às vezes como um inteiro minúsculo.inté o tipo burro de carga para números inteiros. Contadores de laço, tamanhos, IDs.floatedoubleguardam números reais.doubletem mais ou menos o dobro da precisão.voidé o quarto tipo, em certo sentido, mas significa "nenhum valor" - você não pode declarar uma variávelvoid, só usá-lo como tipo de retorno de função, lista de parâmetros vazia ou ponteiro sem tipo.
Note o sufixo f em 3.14f. Sem ele o literal é um double, e atribuí-lo a um float perde precisão silenciosamente. Alguns compiladores avisam.
Modificadores: short, long, unsigned
Os tipos inteiros base vêm com modificadores que mudam o tamanho ou o sinal.
Cada tipo inteiro precisa do seu próprio especificador de formato: %hd para short, %d para int, %ld para long, %lld para long long, %u para unsigned. Usar o errado é comportamento indefinido, não um erro de arredondamento.
unsigned remove o bit de sinal e dobra a faixa positiva. Um unsigned int guarda de 0 a cerca de 4,3 bilhões em vez de -2,1 a +2,1 bilhões. É a escolha certa para coisas que genuinamente não podem ser negativas - tamanhos, contagens de bytes, padrões de bits - e uma armadilha para qualquer coisa de que se possa subtrair abaixo de zero.
signed é o padrão para int, short, long e long long, então você quase nunca o escreve. O único lugar onde ele importa é o char: se o char simples é com ou sem sinal é definido pela implementação, então escreva signed char ou unsigned char quando o sinal importar.
Tamanhos: o que você realmente recebe
O padrão C especifica mínimos, não tamanhos exatos. Em qualquer máquina moderna de 64 bits com Linux, macOS ou Windows você vai ver isto:
| Tipo | Tamanho típico | Faixa típica |
|---|---|---|
char | 1 byte | -128 a 127 (ou 0 a 255) |
short | 2 bytes | -32.768 a 32.767 |
int | 4 bytes | -2.147.483.648 a 2.147.483.647 |
long | 8 bytes (4 no Windows) | mais ou menos ±9,2 quintilhões |
long long | 8 bytes | mais ou menos ±9,2 quintilhões |
float | 4 bytes | ~7 dígitos significativos |
double | 8 bytes | ~15 dígitos significativos |
long double | 16 bytes (varia) | mais que o double |
A linha do long é a que pega as pessoas: ele tem 8 bytes no Linux e no macOS, e 4 bytes no Windows de 64 bits. Código que supõe que long guarda um valor de 64 bits não é portável. Use long long, ou os tipos de largura exata de stdint.h (int32_t, uint64_t) quando o tamanho faz parte do requisito.
sizeof: pergunte ao compilador
Nunca adivinhe um tamanho - meça:
sizeof é um operador, não uma função, e é avaliado em tempo de compilação. Ele produz um valor do tipo size_t, que se imprime com %zu.
sizeof(char) tem garantia de ser exatamente 1 - essa é a definição de byte em C. Todo o resto é medido em relação a ele.
limits.h e float.h
As faixas exatas do seu compilador estão disponíveis como constantes nomeadas:
Esses são os valores com que comparar quando você precisa saber se uma operação vai estourar. Verificar if (a > INT_MAX - b) antes de calcular a + b é como se detecta um estouro antes de ele acontecer - o que importa, porque não dá para detectar estouro com sinal depois.
Estouro de inteiros
O que acontece quando um valor excede a faixa do seu tipo depende inteiramente do sinal.
O estouro sem sinal é definido: o valor dá a volta módulo 2^N.
Esse segundo caso é uma fonte real de bugs. Um laço como for (unsigned i = n - 1; i >= 0; i--) nunca termina, porque um valor sem sinal é sempre >= 0.
O estouro com sinal é comportamento indefinido. Não é "dá a volta" - é indefinido. O compilador tem permissão de supor que isso nunca acontece e otimizar com base nisso, o que significa que uma verificação de estouro escrita depois do fato pode ser eliminada:
int sum = a + b;
if (sum < a) { /* o compilador pode remover isto inteiro */ }
Verifique antes, usando os limites:
if (b > 0 && a > INT_MAX - b) {
/* a + b estouraria - trate o caso */
}
Precisão de ponto flutuante
float e double guardam números em binário, e a maioria das frações decimais não tem forma binária exata - do mesmo jeito que 1/3 não tem forma decimal exata.
A regra que decorre disso: nunca compare valores de ponto flutuante com ==. Compare a diferença absoluta contra uma pequena tolerância.
E nunca use ponto flutuante para dinheiro. Guarde centavos como um inteiro; um long long de centavos é exato onde um double de reais não é.
Escolhendo um tipo
Uma lista curta de decisão que cobre a maior parte do código:
- Números inteiros:
int, a menos que você tenha um motivo. É o tipo que a CPU trata com mais eficiência e o tipo em torno do qual todas as regras de aritmética foram construídas. - Qualquer coisa maior que 2 bilhões:
long long, ouint64_tdestdint.h. - Tamanhos, comprimentos e índices de array vindos de
sizeofoustrlen:size_t. Ele é sem sinal e tem garantia de ser grande o bastante para qualquer objeto. - Decimais:
double. Usefloatsó para reduzir memória pela metade em arrays grandes ou em hardware embarcado sem unidade de precisão dupla. - Caracteres isolados e bytes crus:
charpara texto,unsigned charpara dados binários. - Verdadeiro/falso:
booldestdbool.h- veja booleanos em C. - Larguras exatas de bits (formatos de arquivo, protocolos de rede, registradores de hardware):
stdint.h-uint8_t,int16_t,uint32_te assim por diante.
Misturar tipos
Quando você combina dois tipos diferentes numa mesma expressão, C os converte por trás dos panos antes de fazer a aritmética. Isso normalmente ajuda e ocasionalmente é desastroso:
A primeira é divisão inteira: os dois operandos são int, então o resultado é um int e a fração é descartada. A terceira é pior - comparar um valor com sinal com um sem sinal converte o com sinal para sem sinal, transformando -1 num número positivo enorme.
Essas regras de conversão, e como assumir o controle delas com casts explícitos, são o assunto de conversão de tipos em C.
Perguntas frequentes
Quais são os tipos de dados básicos em C?
Quatro tipos base: char para caracteres isolados e bytes, int para números inteiros, float e double para decimais. Modificadores mudam o tamanho e o sinal deles - short, long, long long, signed e unsigned - produzindo o conjunto completo.
Quantos bytes tem um int em C?
Quase sempre 4 bytes (32 bits) em sistemas modernos de desktop e servidor, dando uma faixa de cerca de -2,1 bilhões a 2,1 bilhões. O padrão só garante pelo menos 2 bytes, e microcontroladores de 16 bits realmente usam 2. Use sizeof(int) se precisar saber com certeza.
Qual é a diferença entre float e double em C?
float tem 4 bytes com cerca de 7 dígitos decimais significativos; double tem 8 bytes com cerca de 15. Double é o padrão para literais de ponto flutuante e para as funções matemáticas e, a menos que você esteja guardando milhões de valores ou mirando um chip embarcado, double é a escolha certa.
O que acontece quando um int estoura em C?
Para um int com sinal, o estouro é comportamento indefinido - o compilador pode dar a volta, saturar ou eliminar a verificação por completo. Para um int sem sinal ele é totalmente definido: o valor dá a volta módulo 2^N, então UINT_MAX + 1 é 0. Nunca dependa do estouro com sinal.