C Tipos e Limites

Esta é a página de consulta rápida dos tipos do C: quantos bytes cada um ocupa, o menor e o maior valor que cabe nele, o tipo de cada número escrito no código e o que acontece quando um valor não cabe. O C não fixa quase nenhum tamanho: ele muda de máquina para máquina. Por isso, cada tabela mostra os valores medidos em duas máquinas diferentes, o gcc da Ana e o editor do guia. Para aprender com calma, veja as aulas Tipos de Dados, Inteiros, float e double, sizeof e stdint.h.

gcc 15.2 da Ana
Linux de 64 bits num processador x86-64 (Intel/AMD), com a glibc. Pasta ~/c/ref, comando gcc -std=c23 -Wall -Wextra. É como a maioria dos computadores com Linux e macOS.
Editor do guia
Clang 22 gerando WebAssembly de 32 bits (wasm32), com a wasi-libc. Aqui, long, ponteiros e size_t têm 4 bytes, como nos computadores de 32 bits de antigamente.

Atalhos: Tamanhos (sizeof) · limits.h · stdint.h · float.h · O tipo dos literais · Conversões e estouros · Editor × gcc · Erros comuns · Experimente


Tamanhos: sizeof e alignof

sizeof(T) dá o tamanho do tipo em bytes; alignof(T) C23 (no C11, _Alignof) dá o alinhamento: o endereço de uma variável desse tipo é sempre múltiplo desse número. Medidos com este programa:

#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <wchar.h>
#include <uchar.h>

#define MOSTRA(T) printf("%-20s %2zu %2zu\n", #T, sizeof(T), alignof(T))

int main(void) {
    printf("%-20s %2s %2s\n", "tipo", "sz", "al");
    MOSTRA(char);
    MOSTRA(bool);
    MOSTRA(short);
    MOSTRA(int);
    MOSTRA(long);
    MOSTRA(long long);
    MOSTRA(float);
    MOSTRA(double);
    MOSTRA(long double);
    MOSTRA(void *);
    MOSTRA(int (*)(void));
    MOSTRA(size_t);
    MOSTRA(ptrdiff_t);
    MOSTRA(intptr_t);
    MOSTRA(intmax_t);
    MOSTRA(max_align_t);
    MOSTRA(wchar_t);
    MOSTRA(char8_t);
    MOSTRA(char16_t);
    MOSTRA(char32_t);
    MOSTRA(nullptr_t);
    MOSTRA(_BitInt(7));
    MOSTRA(_BitInt(100));
    MOSTRA(int_least8_t);
    MOSTRA(int_fast8_t);
    MOSTRA(int_fast16_t);
    MOSTRA(int_fast32_t);
    MOSTRA(int_fast64_t);
    printf("char tem sinal? %s\n", (char)-1 < 0 ? "sim" : "não");
    printf("wchar_t tem sinal? %s\n", (wchar_t)-1 < 0 ? "sim" : "não");
    return 0;
}
$ gcc -std=c23 -Wall -Wextra tamanhos.c -o tamanhos
$ ./tamanhos
tipo                 sz al
char                  1  1
bool                  1  1
short                 2  2
int                   4  4
long                  8  8
long long             8  8
float                 4  4
double                8  8
long double          16 16
void *                8  8
int (*)(void)         8  8
size_t                8  8
ptrdiff_t             8  8
intptr_t              8  8
intmax_t              8  8
max_align_t          32 16
wchar_t               4  4
char8_t               1  1
char16_t              2  2
char32_t              4  4
nullptr_t             8  8
_BitInt(7)            1  1
_BitInt(100)         16  8
int_least8_t          1  1
int_fast8_t           1  1
int_fast16_t          8  8
int_fast32_t          8  8
int_fast64_t          8  8
char tem sinal? sim
wchar_t tem sinal? sim

No editor, o char8_t não existe (error: use of undeclared identifier 'char8_t': a <uchar.h> da wasi-libc ainda não o tem). Sem essa linha, o programa roda, e as diferenças estão na tabela.

Tipogcc (x86-64)
bytes / alinh.
Editor (wasm32)
bytes / alinh.
O C garanteprintfAula
char, signed char, unsigned char1 / 11 / 1exatamente 1 byte (o byte tem CHAR_BIT = 8 bits)%c, %hhdchar
bool C231 / 11 / 1guarda 0 ou 1%dbool
short2 / 22 / 2pelo menos 16 bits%hdInteiros
int4 / 44 / 4pelo menos 16 bits%dInteiros
long8 / 84 / 4pelo menos 32 bits%ldInteiros
long long C998 / 88 / 8pelo menos 64 bits%lldInteiros
float4 / 44 / 4(quase sempre IEEE 754, 32 bits)%ffloat e double
double8 / 88 / 8(quase sempre IEEE 754, 64 bits)%f (no scanf, %lf)float e double
long double16 / 16 (usa 10: 64 bits de mantissa)16 / 16 (usa todos: 113 bits de mantissa)pelo menos a precisão do double%Lf (no editor, veja o aviso)float e double
ponteiros (void *, int *, int (*)(void))8 / 84 / 4nada%pPonteiros
size_t (sem sinal), ptrdiff_t (com sinal)8 / 84 / 4cabe o tamanho de qualquer objeto%zu, %tdsizeof
intptr_t, uintptr_t8 / 84 / 4cabe um ponteiroPRIdPTRstdint.h
intmax_t, uintmax_t8 / 88 / 8o maior inteiro%jd, %justdint.h
max_align_t C1132 / 1632 / 16o maior alinhamento comum (o do malloc)—malloc e free
wchar_t4 / 44 / 4cabe qualquer caractere do sistema%lc, %lsAcentos e UTF-8
char8_t C23, char16_t, char32_t C111, 2, 4(não existe), 2, 4pedaços de UTF-8, UTF-16, UTF-32—Acentos e UTF-8
nullptr_t C238 / 84 / 4o tipo do nullptr (em <stddef.h>)—Ponteiro Nulo
_BitInt(N) C23_BitInt(7): 1 / 1; _BitInt(100): 16 / 8igualum inteiro de exatamente N bits (até BITINT_MAXWIDTH)—Novidades
int_fast16_t, int_fast32_t8, 82, 4"o mais rápido com pelo menos N bits"PRIdFAST16stdint.h
structa soma dos campos, mais os buracos de alinhamentonada—Structs
  • char com ou sem sinal: nos dois, char sozinho tem sinal (vai de −128 a 127). Em processadores ARM (celulares, Raspberry Pi), ele costuma não ter (0 a 255). Para bytes, use unsigned char ou uint8_t.
  • Os unsigned (unsigned short, unsigned int…) têm o mesmo tamanho do tipo com sinal.
  • Só long, ponteiros e os tipos ligados a eles (size_t, ptrdiff_t, intptr_t, nullptr_t) mudam entre as duas máquinas. Se você precisa de um tamanho exato, use os tipos do stdint.h.

limits.h: o menor e o maior valor de cada inteiro

#include <stdio.h>
#include <limits.h>

int main(void) {
    printf("CHAR_BIT   = %d\n", CHAR_BIT);
    printf("SCHAR_MIN  = %d\n", SCHAR_MIN);
    printf("SCHAR_MAX  = %d\n", SCHAR_MAX);
    printf("UCHAR_MAX  = %d\n", UCHAR_MAX);
    printf("CHAR_MIN   = %d\n", CHAR_MIN);
    printf("CHAR_MAX   = %d\n", CHAR_MAX);
    printf("SHRT_MIN   = %d\n", SHRT_MIN);
    printf("SHRT_MAX   = %d\n", SHRT_MAX);
    printf("USHRT_MAX  = %d\n", USHRT_MAX);
    printf("INT_MIN    = %d\n", INT_MIN);
    printf("INT_MAX    = %d\n", INT_MAX);
    printf("UINT_MAX   = %u\n", UINT_MAX);
    printf("LONG_MIN   = %ld\n", LONG_MIN);
    printf("LONG_MAX   = %ld\n", LONG_MAX);
    printf("ULONG_MAX  = %lu\n", ULONG_MAX);
    printf("LLONG_MIN  = %lld\n", LLONG_MIN);
    printf("LLONG_MAX  = %lld\n", LLONG_MAX);
    printf("ULLONG_MAX = %llu\n", ULLONG_MAX);
    printf("BOOL_WIDTH = %d, CHAR_WIDTH = %d, SHRT_WIDTH = %d, INT_WIDTH = %d\n", BOOL_WIDTH, CHAR_WIDTH, SHRT_WIDTH, INT_WIDTH);
    printf("LONG_WIDTH = %d, LLONG_WIDTH = %d\n", LONG_WIDTH, LLONG_WIDTH);
    printf("BITINT_MAXWIDTH = %llu\n", (unsigned long long)BITINT_MAXWIDTH);
    printf("MB_LEN_MAX = %d\n", MB_LEN_MAX);
    return 0;
}
Macrogcc 15 (x86-64)Editor (wasm32)Aula
CHAR_BIT88Bits
SCHAR_MIN, SCHAR_MAX-128, 127igualchar
UCHAR_MAX255igualchar
CHAR_MIN, CHAR_MAX-128, 127 (o char tem sinal)igualchar
SHRT_MIN, SHRT_MAX-32768, 32767igualInteiros
USHRT_MAX65535igualInteiros
INT_MIN, INT_MAX-2147483648, 2147483647 (cerca de ±2,1 bilhões)igualInteiros
UINT_MAX4294967295igualInteiros
LONG_MIN, LONG_MAX-9223372036854775808, 9223372036854775807-2147483648, 2147483647Inteiros
ULONG_MAX184467440737095516154294967295Inteiros
LLONG_MIN, LLONG_MAX-9223372036854775808, 9223372036854775807 (cerca de ±9,2 quintilhões)igualInteiros
ULLONG_MAX18446744073709551615igualInteiros
BOOL_WIDTH, CHAR_WIDTH, SHRT_WIDTH, INT_WIDTH C231, 8, 16, 32 (bits)igualNovidades
LONG_WIDTH, LLONG_WIDTH C2364, 6432, 64Novidades
BITINT_MAXWIDTH C2365535128Novidades
MB_LEN_MAX164Acentos e UTF-8

Os tipos com sinal vão de −2N−1 a 2N−1 − 1, e os sem sinal, de 0 a 2N − 1, onde N é o número de bits. Por isso o mínimo tem "um a mais" que o máximo: INT_MIN = −2147483648, INT_MAX = 2147483647.

stdint.h: inteiros de tamanho fixo

Quando o tamanho importa (um arquivo binário, um protocolo de rede, um valor que passa de 2 bilhões), use os tipos do <stdint.h> C99: eles têm o mesmo tamanho em toda máquina. Aula: stdint.h.

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

int main(void) {
    printf("INT8_MIN   = %" PRId8 ", INT8_MAX = %" PRId8 ", UINT8_MAX = %" PRIu8 "\n", INT8_MIN, INT8_MAX, UINT8_MAX);
    printf("INT16_MIN  = %" PRId16 ", INT16_MAX = %" PRId16 ", UINT16_MAX = %" PRIu16 "\n", INT16_MIN, INT16_MAX, UINT16_MAX);
    printf("INT32_MIN  = %" PRId32 ", INT32_MAX = %" PRId32 "\n", INT32_MIN, INT32_MAX);
    printf("UINT32_MAX = %" PRIu32 "\n", UINT32_MAX);
    printf("INT64_MIN  = %" PRId64 "\n", INT64_MIN);
    printf("INT64_MAX  = %" PRId64 "\n", INT64_MAX);
    printf("UINT64_MAX = %" PRIu64 "\n", UINT64_MAX);
    printf("INTMAX_MAX = %" PRIdMAX "\n", INTMAX_MAX);
    printf("SIZE_MAX   = %zu\n", (size_t)SIZE_MAX);
    printf("PTRDIFF_MAX = %td\n", (ptrdiff_t)PTRDIFF_MAX);
    printf("INTPTR_MAX = %" PRIdPTR "\n", (intptr_t)INTPTR_MAX);
    printf("PRId64 = \"%s\", PRIu64 = \"%s\", PRIdPTR = \"%s\"\n", PRId64, PRIu64, PRIdPTR);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra fixos.c -o fixos
$ ./fixos
INT8_MIN   = -128, INT8_MAX = 127, UINT8_MAX = 255
INT16_MIN  = -32768, INT16_MAX = 32767, UINT16_MAX = 65535
INT32_MIN  = -2147483648, INT32_MAX = 2147483647
UINT32_MAX = 4294967295
INT64_MIN  = -9223372036854775808
INT64_MAX  = 9223372036854775807
UINT64_MAX = 18446744073709551615
INTMAX_MAX = 9223372036854775807
SIZE_MAX   = 18446744073709551615
PTRDIFF_MAX = 9223372036854775807
INTPTR_MAX = 9223372036854775807
PRId64 = "ld", PRIu64 = "lu", PRIdPTR = "ld"
TipoBytesFaixa (macros _MIN/_MAX)printf
int8_t, uint8_t1INT8_MIN = −128 a INT8_MAX = 127; UINT8_MAX = 255PRId8, PRIu8
int16_t, uint16_t2−32768 a 32767; UINT16_MAX = 65535PRId16, PRIu16
int32_t, uint32_t4−2147483648 a 2147483647; UINT32_MAX = 4294967295PRId32, PRIu32
int64_t, uint64_t8−9223372036854775808 a 9223372036854775807; UINT64_MAX = 18446744073709551615PRId64, PRIu64
int_leastN_to menor com pelo menos N bits (nos dois, igual ao intN_t)INT_LEAST8_MAX…PRIdLEAST8…
int_fastN_to mais rápido com pelo menos N bits: int_fast16_t tem 8 no gcc e 2 no editorINT_FAST16_MAX…PRIdFAST16…
intmax_t, uintmax_t8INTMAX_MAX = 9223372036854775807%jd ou PRIdMAX
intptr_t, uintptr_t8 no gcc, 4 no editorINTPTR_MAX: 9223372036854775807 no gcc, 2147483647 no editorPRIdPTR
size_t8 no gcc, 4 no editorSIZE_MAX: 18446744073709551615 no gcc, 4294967295 no editor%zu
ptrdiff_t8 no gcc, 4 no editorPTRDIFF_MAX: 9223372036854775807 no gcc, 2147483647 no editor%td
  • Por que PRId64? Porque o int64_t é um long no gcc (PRId64 vira "ld") e um long long no editor (vira "lld"). Escrever %ld à mão funcionaria só numa das duas máquinas. Veja a tabela do printf.
  • Literais de tamanho fixo: INT64_C(5000000000) e UINT32_C(7) escrevem o número já com o tipo certo.
  • No editor, os casts (size_t)SIZE_MAX são necessários: lá, SIZE_MAX, PTRDIFF_MAX e INTPTR_MAX são unsigned int/int, e o Clang avisa format specifies type 'size_t' (aka 'unsigned long') but the argument has type 'unsigned int' num %zu sem o cast. No gcc, não faz diferença.

float.h: limites do ponto flutuante

#include <stdio.h>
#include <float.h>

int main(void) {
    printf("FLT_RADIX = %d, FLT_EVAL_METHOD = %d\n", FLT_RADIX, FLT_EVAL_METHOD);
    printf("float:  MANT_DIG = %d, DIG = %d, DECIMAL_DIG = %d\n", FLT_MANT_DIG, FLT_DIG, FLT_DECIMAL_DIG);
    printf("        MIN = %g, MAX = %g, EPSILON = %g, TRUE_MIN = %g\n", FLT_MIN, FLT_MAX, FLT_EPSILON, FLT_TRUE_MIN);
    printf("double: MANT_DIG = %d, DIG = %d, DECIMAL_DIG = %d\n", DBL_MANT_DIG, DBL_DIG, DBL_DECIMAL_DIG);
    printf("        MIN = %g, MAX = %g, EPSILON = %g, TRUE_MIN = %g\n", DBL_MIN, DBL_MAX, DBL_EPSILON, DBL_TRUE_MIN);
    printf("long double: MANT_DIG = %d, DIG = %d, DECIMAL_DIG = %d\n", LDBL_MANT_DIG, LDBL_DIG, LDBL_DECIMAL_DIG);
    printf("        MIN = %Lg, MAX = %Lg, EPSILON = %Lg\n", LDBL_MIN, LDBL_MAX, LDBL_EPSILON);
    printf("FLT_MIN_EXP = %d, FLT_MAX_EXP = %d, FLT_MAX_10_EXP = %d\n", FLT_MIN_EXP, FLT_MAX_EXP, FLT_MAX_10_EXP);
    printf("DBL_MIN_10_EXP = %d, DBL_MAX_10_EXP = %d\n", DBL_MIN_10_EXP, DBL_MAX_10_EXP);
    printf("DECIMAL_DIG = %d\n", DECIMAL_DIG);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra flutuante.c -o flutuante
$ ./flutuante
FLT_RADIX = 2, FLT_EVAL_METHOD = 0
float:  MANT_DIG = 24, DIG = 6, DECIMAL_DIG = 9
        MIN = 1.17549e-38, MAX = 3.40282e+38, EPSILON = 1.19209e-07, TRUE_MIN = 1.4013e-45
double: MANT_DIG = 53, DIG = 15, DECIMAL_DIG = 17
        MIN = 2.22507e-308, MAX = 1.79769e+308, EPSILON = 2.22045e-16, TRUE_MIN = 4.94066e-324
long double: MANT_DIG = 64, DIG = 18, DECIMAL_DIG = 21
        MIN = 3.3621e-4932, MAX = 1.18973e+4932, EPSILON = 1.0842e-19
FLT_MIN_EXP = -125, FLT_MAX_EXP = 128, FLT_MAX_10_EXP = 38
DBL_MIN_10_EXP = -307, DBL_MAX_10_EXP = 308
DECIMAL_DIG = 21

No editor, por causa do %Lg, o programa precisa de data-flags="-std=c23 -Wall -Wextra -lc-printscan-long-double" (veja o aviso do printf). Com isso, float e double saem iguais; o long double é diferente (tabela abaixo).

Macro (FLT_ = float, DBL_ = double, LDBL_ = long double)O que éfloatdoublelong double
gcc / editor
…_DIGalgarismos decimais que sempre sobrevivem à ida e volta (texto → número → texto)61518 / 33
…_DECIMAL_DIG C11algarismos que você precisa imprimir para não perder nada (número → texto → número)91721 / 36
…_MANT_DIGbits da mantissa (a parte dos algarismos)245364 / 113
…_EPSILONa distância entre 1 e o próximo número que o tipo consegue guardar1.19209e-072.22045e-161.0842e-19 / 1.92593e-34
…_MINo menor positivo normal (não é o mais negativo! Esse é -…_MAX)1.17549e-382.22507e-3083.3621e-4932 (igual)
…_TRUE_MIN C11o menor positivo que existe (subnormal)1.4013e-454.94066e-324—
…_MAXo maior valor finito3.40282e+381.79769e+3081.18973e+4932 (igual)
…_MAX_10_EXP, …_MIN_10_EXPmaior e menor expoente de 1038, −37308, −307—
FLT_RADIXa base usada por dentro2 (binário), nos dois
FLT_EVAL_METHODem que tipo as contas são feitas (0 = no próprio tipo)0, nos dois
DECIMAL_DIGo …_DECIMAL_DIG do maior tipo21 no gcc, 36 no editor

Na prática: um float guarda uns 6 a 7 algarismos; um double, 15 a 16. Para dinheiro, guarde centavos num inteiro (long long centavos). Para comparar dois double, use uma tolerância: fabs(a - b) < 1e-9. Aula: float e double.

O tipo de cada literal

Todo número escrito no código (um literal) tem um tipo. Medido com _Generic (aula _Generic), que escolhe um texto conforme o tipo:

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

#define TIPO(x) _Generic((x), \
    char: "char", int: "int", unsigned: "unsigned int", \
    long: "long", unsigned long: "unsigned long", \
    long long: "long long", unsigned long long: "unsigned long long", \
    float: "float", double: "double", long double: "long double", \
    bool: "bool", nullptr_t: "nullptr_t", default: "outro")

int main(void) {
    printf("42          -> %s\n", TIPO(42));
    printf("42u         -> %s\n", TIPO(42u));
    printf("42L         -> %s\n", TIPO(42L));
    printf("42LL        -> %s\n", TIPO(42LL));
    printf("42ull       -> %s\n", TIPO(42ull));
    printf("2147483648  -> %s\n", TIPO(2147483648));
    printf("0xFFFFFFFF  -> %s\n", TIPO(0xFFFFFFFF));
    printf("3.14        -> %s\n", TIPO(3.14));
    printf("3.14f       -> %s\n", TIPO(3.14f));
    printf("3.14L       -> %s\n", TIPO(3.14L));
    printf("'A'         -> %s\n", TIPO('A'));
    printf("true        -> %s\n", TIPO(true));
    printf("nullptr     -> %s\n", TIPO(nullptr));
    printf("1'000'000   -> %d\n", 1'000'000);
    printf("0b1010      -> %d\n", 0b1010);
    printf("sizeof(3wb) = %zu, sizeof(200uwb) = %zu\n", sizeof(3wb), sizeof(200uwb));
    char c = 'A';
    printf("c + 1       -> %s\n", TIPO(c + 1));
    printf("1u + -2     -> %s = %u\n", TIPO(1u + -2), 1u + -2);
    printf("-1 < 1u     -> %d\n", -1 < 1u);
    printf("(unsigned char)300 = %d, (signed char)200 = %d\n", (unsigned char)300, (signed char)200);
    printf("UINT_MAX + 1u = %u\n", UINT_MAX + 1u);
    printf("(int)3.99 = %d, (int)-3.99 = %d\n", (int)3.99, (int)-3.99);
    float f = 16777217;
    printf("float 16777217 -> %.1f\n", f);
    printf("0.1f == 0.1 -> %d\n", 0.1f == 0.1);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra literais.c -o literais
literais.c: In function ‘main’:
literais.c:32:38: warning: comparison of integer expressions of different signedness: ‘int’ and ‘unsigned int’ [-Wsign-compare]
   32 |     printf("-1 < 1u     -> %d\n", -1 < 1u);
      |                                      ^
$ ./literais
42          -> int
42u         -> unsigned int
42L         -> long
42LL        -> long long
42ull       -> unsigned long long
2147483648  -> long                               « no editor: long long
0xFFFFFFFF  -> unsigned int
3.14        -> double
3.14f       -> float
3.14L       -> long double
'A'         -> int
true        -> bool
nullptr     -> nullptr_t
1'000'000   -> 1000000
0b1010      -> 10
sizeof(3wb) = 1, sizeof(200uwb) = 1
c + 1       -> int
1u + -2     -> unsigned int = 4294967295
-1 < 1u     -> 0
(unsigned char)300 = 44, (signed char)200 = -56
UINT_MAX + 1u = 0
(int)3.99 = 3, (int)-3.99 = -3
float 16777217 -> 16777216.0
0.1f == 0.1 -> 0

No editor, o Clang não dá o aviso do -1 < 1u, mas dá outros dois (veja Erros comuns). A única diferença na saída é a do 2147483648.

LiteralTipoExemploAula
inteiro sem sufixoint; se não couber, long; depois long long42 → int; 2147483648 → long no gcc, long long no editorInteiros
hexadecimal ou octal sem sufixocomo acima, mas pode virar unsigned antes de crescer0xFFFFFFFF → unsigned intBits
u / Uunsigned int (ou maior)42uInteiros
l / L, ll / LLlong, long long (com u: sem sinal)42L, 42LL, 42ullInteiros
wb, uwb C23_BitInt(N) do menor tamanho que cabesizeof(3wb) → 1Novidades
0b… C23binário0b1010 = 10Bits
1'000'000 C23o ' só separa os dígitos, para ler melhor1'000'000 = 1000000Novidades
com ponto ou edouble3.14, 1e3float e double
f / F, Lfloat, long double3.14f, 3.14Lfloat e double
'A'int (em C, não char!)sizeof('A') = 4char
true, false C23bool—bool
nullptr C23nullptr_t—Ponteiro Nulo
"Ana"char[4] (3 letras + '\0')sizeof "Ana" = 4Strings

Conversões e estouros

SituaçãoRegraExemplo → resultado (igual nos dois)Aula
conta com char ou shortviram int antes da conta ("promoção")c + 1 → intConversão de Tipos
conta com int e unsignedo int vira unsigned1u + -2 → 4294967295; -1 < 1u → 0 (falso!)Conversão de Tipos
conta com inteiro e doubleo inteiro vira double7 / 2.0 → 3.5 (mas 7 / 2 → 3)Aritméticos
sem sinal que não cabe"dá a volta" (módulo 2N): definido pelo C(unsigned char)300 → 44; UINT_MAX + 1u → 0Inteiros
com sinal, conversão que não cabeo resultado depende do compilador ("definido pela implementação"); o gcc e o Clang dão a volta(signed char)200 → -56Inteiros
com sinal, conta que estouracomportamento indefinido: o compilador pode fazer qualquer coisaINT_MAX + 1 deu -2147483648 nos dois, mas não é garantidoComportamento Indefinido
double → inteirocorta a parte decimal (em direção ao zero)(int)3.99 → 3; (int)-3.99 → -3. Para arredondar: lround do math.hConversão de Tipos
inteiro grande → floatperde os últimos algarismosfloat f = 16777217; → 16777216.0float e double
float × doubleo mesmo número decimal vira valores binários diferentes0.1f == 0.1 → 0float e double

Para saber, antes de fazer a conta, se ela vai estourar, o C23 trouxe o <stdckdint.h>: ckd_add(&total, a, b) devolve true se estourou. Aula: Inteiros.

Editor × gcc

AssuntoEditor (wasm32)gcc 15 (x86-64)
sizeof de long, ponteiro, size_t, ptrdiff_t, intptr_t, nullptr_t48
LONG_MAX, ULONG_MAX2147483647, 42949672959223372036854775807, 18446744073709551615
SIZE_MAX4294967295 (e é um unsigned int)18446744073709551615
tipo de 2147483648long longlong
int64_t é…long long (PRId64 = "lld")long (PRId64 = "ld")
int_fast16_t, int_fast32_t2 e 4 bytes8 e 8 bytes
long double113 bits de mantissa, 33 algarismos (LDBL_DIG); %Lf precisa de -lc-printscan-long-double64 bits de mantissa, 18 algarismos
char8_tnão existeexiste (1 byte)
BITINT_MAXWIDTH, MB_LEN_MAX128, 465535, 16
long x = 3000000000;não cabe: vira -1294967296 (e o Clang avisa)cabe

Iguais nos dois: char, short, int, long long, float, double, bool, wchar_t, todos os intN_t, INT_MAX, os limites de float e double, as regras de conversão e o char com sinal. Para um programa funcionar igual em qualquer máquina, use int, long long e os tipos do stdint.h, nunca long.


Erros comuns

#include <stdio.h>
#include <limits.h>

int main(void) {
    int populacao = 3000000000;
    char letra = 300;
    int depois = INT_MAX + 1;
    unsigned int positivo = -1;
    long grande = 3000000000;
    printf("%d %d %d %u %ld\n", populacao, letra, depois, positivo, grande);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra estouro.c -o estouro
estouro.c: In function ‘main’:
estouro.c:6:18: warning: overflow in conversion from ‘int’ to ‘char’ changes value from ‘300’ to ‘44’ [-Woverflow]
    6 |     char letra = 300;
      |                  ^~~
estouro.c:7:26: warning: integer overflow in expression of type ‘int’ results in ‘-2147483648’ [-Woverflow]
    7 |     int depois = INT_MAX + 1;
      |                          ^
$ ./estouro
-1294967296 44 -2147483648 4294967295 3000000000
Linhagcc 15Clang (editor)O que acontece e como consertar
int populacao = 3000000000;(nenhum aviso!)implicit conversion from 'long long' to 'int' changes value from 3000000000 to -1294967296 [-Wconstant-conversion]3 bilhões não cabem num int: virou -1294967296. O gcc só avisa com -Wconversion (conversion from ‘long int’ to ‘int’ changes value from ‘3000000000’ to ‘-1294967296’). Use long long ou int64_t.
char letra = 300;overflow in conversion from ‘int’ to ‘char’ changes value from ‘300’ to ‘44’ [-Woverflow]implicit conversion from 'int' to 'char' changes value from 300 to 44 [-Wconstant-conversion]Um char vai até 127: sobrou 300 − 256 = 44.
int depois = INT_MAX + 1;integer overflow in expression of type ‘int’ results in ‘-2147483648’ [-Woverflow]overflow in expression; result is -2'147'483'648 with type 'int' [-Winteger-overflow]Estouro de conta com sinal: comportamento indefinido. Faça a conta num tipo maior: (long long)INT_MAX + 1.
unsigned int positivo = -1;(nenhum aviso)(nenhum aviso)É permitido e vira o maior unsigned: 4294967295. Às vezes é de propósito; às vezes é um bug.
long grande = 3000000000;(nenhum aviso: cabe)implicit conversion from 'long long' to 'long' changes value from 3000000000 to -1294967296 [-Wconstant-conversion]Funciona no gcc e não no editor: o long muda de tamanho. Use long long.
-1 < 1ucomparison of integer expressions of different signedness: ‘int’ and ‘unsigned int’ [-Wsign-compare](nenhum aviso)Dá falso: o −1 vira 4294967295. Acontece muito com strlen e sizeof (que são size_t): if (i < strlen(s) - 1) com string vazia. Compare tipos iguais.
float f = 16777217;(nenhum aviso)implicit conversion from 'int' to 'float' changes value from 16777217 to 16777216 [-Wimplicit-const-int-float-conversion]O float só tem 24 bits de mantissa. Use double.
0.1f == 0.1(nenhum aviso)floating-point comparison is always false; constant cannot be represented exactly in type 'float' [-Wliteral-range]Não misture float e double numa comparação; e não compare ponto flutuante com ==.
A soma das vendas ficou negativa

Com int janeiro = 1500000000, fevereiro = 1500000000;, a conta janeiro + fevereiro dá -1294967296, sem aviso nenhum (os valores só são conhecidos na execução). Guardar o resultado num long long não basta: a conta é feita em int e estoura antes. Converta um dos lados antes da conta: (long long)janeiro + fevereiro. É a missão abaixo.


Experimente

Experimente: veja os tamanhos no editor (WebAssembly de 32 bits) e compare com a tabela do gcc. Acrescente outros tipos: unsigned short, int16_t, uma struct sua…

#include <stdio.h>
#include <stdint.h>
#include <limits.h>

struct produto {
    char codigo;
    int quantidade;
    double preco;
};

int main(void) {
    printf("int: %zu bytes, long: %zu, long long: %zu\n", sizeof(int), sizeof(long), sizeof(long long));
    printf("ponteiro: %zu bytes, size_t: %zu\n", sizeof(void *), sizeof(size_t));
    printf("int64_t: %zu bytes\n", sizeof(int64_t));
    printf("struct produto: %zu bytes (1 + 4 + 8 = 13 sem os buracos)\n", sizeof(struct produto));
    printf("INT_MAX = %d, LONG_MAX = %ld\n", INT_MAX, LONG_MAX);
    return 0;
}

Missão: a lojinha da Ana vendeu R$ 1,5 bilhão em janeiro e outro tanto em fevereiro (é uma lojinha otimista), mas o total sai negativo. Guarde o total num long long, faça a conta nesse tipo e mostre com %lld, para sair Total: 3000000000. (Não use long: no editor ele tem 4 bytes e estoura igual.)

#include <stdio.h>

int main(void) {
    int janeiro = 1500000000;
    int fevereiro = 1500000000;
    int total = janeiro + fevereiro;
    printf("Total: %d\n", total);
    return 0;
}
#include <stdio.h>

int main(void) {
    int janeiro = 1500000000;
    int fevereiro = 1500000000;
    long long total = (long long)janeiro + fevereiro;
    printf("Total: %lld\n", total);
    return 0;
}

Resumo

  • Iguais em quase toda máquina: char 1, short 2, int 4, long long 8, float 4, double 8 bytes.
  • Mudam: long, ponteiros e size_t (8 bytes no Linux de 64 bits, 4 no editor). Para tamanho exato, int32_t, int64_t… do stdint.h, com PRId64 no printf.
  • Limites: INT_MAX = 2147483647, LLONG_MAX = 9223372036854775807 (limits.h); DBL_EPSILON, DBL_DIG = 15 (float.h).
  • Sem sinal dá a volta; conta com sinal que estoura é comportamento indefinido. Converta antes da conta: (long long)a + b.
  • Cuidado com int × unsigned (-1 < 1u é falso) e com 'A', que é um int.