Menu

Tipos de dados em C: int, float, char, double e outros

Toda variável em C tem um tipo que fixa seu tamanho e o que ela pode guardar. Aqui estão todos os tipos básicos, seus tamanhos reais, com e sem sinal, sizeof, limits.h e o que acontece no estouro.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

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

  • char guarda 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.
  • float e double guardam números reais. double tem 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ável void, 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:

TipoTamanho típicoFaixa típica
char1 byte-128 a 127 (ou 0 a 255)
short2 bytes-32.768 a 32.767
int4 bytes-2.147.483.648 a 2.147.483.647
long8 bytes (4 no Windows)mais ou menos ±9,2 quintilhões
long long8 bytesmais ou menos ±9,2 quintilhões
float4 bytes~7 dígitos significativos
double8 bytes~15 dígitos significativos
long double16 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, ou int64_t de stdint.h.
  • Tamanhos, comprimentos e índices de array vindos de sizeof ou strlen: size_t. Ele é sem sinal e tem garantia de ser grande o bastante para qualquer objeto.
  • Decimais: double. Use float só para reduzir memória pela metade em arrays grandes ou em hardware embarcado sem unidade de precisão dupla.
  • Caracteres isolados e bytes crus: char para texto, unsigned char para dados binários.
  • Verdadeiro/falso: bool de stdbool.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_t e 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.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR