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, comandogcc -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 esize_ttê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.
| Tipo | gcc (x86-64) bytes / alinh. | Editor (wasm32) bytes / alinh. | O C garante | printf | Aula |
|---|---|---|---|---|---|
char, signed char, unsigned char | 1 / 1 | 1 / 1 | exatamente 1 byte (o byte tem CHAR_BIT = 8 bits) | %c, %hhd | char |
bool C23 | 1 / 1 | 1 / 1 | guarda 0 ou 1 | %d | bool |
short | 2 / 2 | 2 / 2 | pelo menos 16 bits | %hd | Inteiros |
int | 4 / 4 | 4 / 4 | pelo menos 16 bits | %d | Inteiros |
long | 8 / 8 | 4 / 4 | pelo menos 32 bits | %ld | Inteiros |
long long C99 | 8 / 8 | 8 / 8 | pelo menos 64 bits | %lld | Inteiros |
float | 4 / 4 | 4 / 4 | (quase sempre IEEE 754, 32 bits) | %f | float e double |
double | 8 / 8 | 8 / 8 | (quase sempre IEEE 754, 64 bits) | %f (no scanf, %lf) | float e double |
long double | 16 / 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 / 8 | 4 / 4 | nada | %p | Ponteiros |
size_t (sem sinal), ptrdiff_t (com sinal) | 8 / 8 | 4 / 4 | cabe o tamanho de qualquer objeto | %zu, %td | sizeof |
intptr_t, uintptr_t | 8 / 8 | 4 / 4 | cabe um ponteiro | PRIdPTR | stdint.h |
intmax_t, uintmax_t | 8 / 8 | 8 / 8 | o maior inteiro | %jd, %ju | stdint.h |
max_align_t C11 | 32 / 16 | 32 / 16 | o maior alinhamento comum (o do malloc) | — | malloc e free |
wchar_t | 4 / 4 | 4 / 4 | cabe qualquer caractere do sistema | %lc, %ls | Acentos e UTF-8 |
char8_t C23, char16_t, char32_t C11 | 1, 2, 4 | (não existe), 2, 4 | pedaços de UTF-8, UTF-16, UTF-32 | — | Acentos e UTF-8 |
nullptr_t C23 | 8 / 8 | 4 / 4 | o tipo do nullptr (em <stddef.h>) | — | Ponteiro Nulo |
_BitInt(N) C23 | _BitInt(7): 1 / 1; _BitInt(100): 16 / 8 | igual | um inteiro de exatamente N bits (até BITINT_MAXWIDTH) | — | Novidades |
int_fast16_t, int_fast32_t | 8, 8 | 2, 4 | "o mais rápido com pelo menos N bits" | PRIdFAST16 | stdint.h |
struct | a soma dos campos, mais os buracos de alinhamento | nada | — | Structs | |
charcom ou sem sinal: nos dois,charsozinho tem sinal (vai de −128 a 127). Em processadores ARM (celulares, Raspberry Pi), ele costuma não ter (0 a 255). Para bytes, useunsigned charouuint8_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 dostdint.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;
}
| Macro | gcc 15 (x86-64) | Editor (wasm32) | Aula |
|---|---|---|---|
CHAR_BIT | 8 | 8 | Bits |
SCHAR_MIN, SCHAR_MAX | -128, 127 | igual | char |
UCHAR_MAX | 255 | igual | char |
CHAR_MIN, CHAR_MAX | -128, 127 (o char tem sinal) | igual | char |
SHRT_MIN, SHRT_MAX | -32768, 32767 | igual | Inteiros |
USHRT_MAX | 65535 | igual | Inteiros |
INT_MIN, INT_MAX | -2147483648, 2147483647 (cerca de ±2,1 bilhões) | igual | Inteiros |
UINT_MAX | 4294967295 | igual | Inteiros |
LONG_MIN, LONG_MAX | -9223372036854775808, 9223372036854775807 | -2147483648, 2147483647 | Inteiros |
ULONG_MAX | 18446744073709551615 | 4294967295 | Inteiros |
LLONG_MIN, LLONG_MAX | -9223372036854775808, 9223372036854775807 (cerca de ±9,2 quintilhões) | igual | Inteiros |
ULLONG_MAX | 18446744073709551615 | igual | Inteiros |
BOOL_WIDTH, CHAR_WIDTH, SHRT_WIDTH, INT_WIDTH C23 | 1, 8, 16, 32 (bits) | igual | Novidades |
LONG_WIDTH, LLONG_WIDTH C23 | 64, 64 | 32, 64 | Novidades |
BITINT_MAXWIDTH C23 | 65535 | 128 | Novidades |
MB_LEN_MAX | 16 | 4 | Acentos 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"
| Tipo | Bytes | Faixa (macros _MIN/_MAX) | printf |
|---|---|---|---|
int8_t, uint8_t | 1 | INT8_MIN = −128 a INT8_MAX = 127; UINT8_MAX = 255 | PRId8, PRIu8 |
int16_t, uint16_t | 2 | −32768 a 32767; UINT16_MAX = 65535 | PRId16, PRIu16 |
int32_t, uint32_t | 4 | −2147483648 a 2147483647; UINT32_MAX = 4294967295 | PRId32, PRIu32 |
int64_t, uint64_t | 8 | −9223372036854775808 a 9223372036854775807; UINT64_MAX = 18446744073709551615 | PRId64, PRIu64 |
int_leastN_t | o menor com pelo menos N bits (nos dois, igual ao intN_t) | INT_LEAST8_MAX… | PRIdLEAST8… |
int_fastN_t | o mais rápido com pelo menos N bits: int_fast16_t tem 8 no gcc e 2 no editor | INT_FAST16_MAX… | PRIdFAST16… |
intmax_t, uintmax_t | 8 | INTMAX_MAX = 9223372036854775807 | %jd ou PRIdMAX |
intptr_t, uintptr_t | 8 no gcc, 4 no editor | INTPTR_MAX: 9223372036854775807 no gcc, 2147483647 no editor | PRIdPTR |
size_t | 8 no gcc, 4 no editor | SIZE_MAX: 18446744073709551615 no gcc, 4294967295 no editor | %zu |
ptrdiff_t | 8 no gcc, 4 no editor | PTRDIFF_MAX: 9223372036854775807 no gcc, 2147483647 no editor | %td |
- Por que
PRId64? Porque oint64_té umlongno gcc (PRId64vira"ld") e umlong longno 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)eUINT32_C(7)escrevem o número já com o tipo certo. - No editor, os casts
(size_t)SIZE_MAXsão necessários: lá,SIZE_MAX,PTRDIFF_MAXeINTPTR_MAXsãounsigned int/int, e o Clang avisaformat specifies type 'size_t' (aka 'unsigned long') but the argument has type 'unsigned int'num%zusem 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 é | float | double | long doublegcc / editor |
|---|---|---|---|---|
…_DIG | algarismos decimais que sempre sobrevivem à ida e volta (texto → número → texto) | 6 | 15 | 18 / 33 |
…_DECIMAL_DIG C11 | algarismos que você precisa imprimir para não perder nada (número → texto → número) | 9 | 17 | 21 / 36 |
…_MANT_DIG | bits da mantissa (a parte dos algarismos) | 24 | 53 | 64 / 113 |
…_EPSILON | a distância entre 1 e o próximo número que o tipo consegue guardar | 1.19209e-07 | 2.22045e-16 | 1.0842e-19 / 1.92593e-34 |
…_MIN | o menor positivo normal (não é o mais negativo! Esse é -…_MAX) | 1.17549e-38 | 2.22507e-308 | 3.3621e-4932 (igual) |
…_TRUE_MIN C11 | o menor positivo que existe (subnormal) | 1.4013e-45 | 4.94066e-324 | — |
…_MAX | o maior valor finito | 3.40282e+38 | 1.79769e+308 | 1.18973e+4932 (igual) |
…_MAX_10_EXP, …_MIN_10_EXP | maior e menor expoente de 10 | 38, −37 | 308, −307 | — |
FLT_RADIX | a base usada por dentro | 2 (binário), nos dois | ||
FLT_EVAL_METHOD | em que tipo as contas são feitas (0 = no próprio tipo) | 0, nos dois | ||
DECIMAL_DIG | o …_DECIMAL_DIG do maior tipo | 21 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.
| Literal | Tipo | Exemplo | Aula |
|---|---|---|---|
| inteiro sem sufixo | int; se não couber, long; depois long long | 42 → int; 2147483648 → long no gcc, long long no editor | Inteiros |
| hexadecimal ou octal sem sufixo | como acima, mas pode virar unsigned antes de crescer | 0xFFFFFFFF → unsigned int | Bits |
u / U | unsigned int (ou maior) | 42u | Inteiros |
l / L, ll / LL | long, long long (com u: sem sinal) | 42L, 42LL, 42ull | Inteiros |
wb, uwb C23 | _BitInt(N) do menor tamanho que cabe | sizeof(3wb) → 1 | Novidades |
0b… C23 | binário | 0b1010 = 10 | Bits |
1'000'000 C23 | o ' só separa os dígitos, para ler melhor | 1'000'000 = 1000000 | Novidades |
com ponto ou e | double | 3.14, 1e3 | float e double |
f / F, L | float, long double | 3.14f, 3.14L | float e double |
'A' | int (em C, não char!) | sizeof('A') = 4 | char |
true, false C23 | bool | — | bool |
nullptr C23 | nullptr_t | — | Ponteiro Nulo |
"Ana" | char[4] (3 letras + '\0') | sizeof "Ana" = 4 | Strings |
Conversões e estouros
| Situação | Regra | Exemplo → resultado (igual nos dois) | Aula |
|---|---|---|---|
conta com char ou short | viram int antes da conta ("promoção") | c + 1 → int | Conversão de Tipos |
conta com int e unsigned | o int vira unsigned | 1u + -2 → 4294967295; -1 < 1u → 0 (falso!) | Conversão de Tipos |
conta com inteiro e double | o inteiro vira double | 7 / 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 → 0 | Inteiros |
| com sinal, conversão que não cabe | o resultado depende do compilador ("definido pela implementação"); o gcc e o Clang dão a volta | (signed char)200 → -56 | Inteiros |
| com sinal, conta que estoura | comportamento indefinido: o compilador pode fazer qualquer coisa | INT_MAX + 1 deu -2147483648 nos dois, mas não é garantido | Comportamento Indefinido |
double → inteiro | corta a parte decimal (em direção ao zero) | (int)3.99 → 3; (int)-3.99 → -3. Para arredondar: lround do math.h | Conversão de Tipos |
inteiro grande → float | perde os últimos algarismos | float f = 16777217; → 16777216.0 | float e double |
float × double | o mesmo número decimal vira valores binários diferentes | 0.1f == 0.1 → 0 | float 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
| Assunto | Editor (wasm32) | gcc 15 (x86-64) |
|---|---|---|
sizeof de long, ponteiro, size_t, ptrdiff_t, intptr_t, nullptr_t | 4 | 8 |
LONG_MAX, ULONG_MAX | 2147483647, 4294967295 | 9223372036854775807, 18446744073709551615 |
SIZE_MAX | 4294967295 (e é um unsigned int) | 18446744073709551615 |
tipo de 2147483648 | long long | long |
int64_t é… | long long (PRId64 = "lld") | long (PRId64 = "ld") |
int_fast16_t, int_fast32_t | 2 e 4 bytes | 8 e 8 bytes |
long double | 113 bits de mantissa, 33 algarismos (LDBL_DIG); %Lf precisa de -lc-printscan-long-double | 64 bits de mantissa, 18 algarismos |
char8_t | não existe | existe (1 byte) |
BITINT_MAXWIDTH, MB_LEN_MAX | 128, 4 | 65535, 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
| Linha | gcc 15 | Clang (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 < 1u | comparison 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 ==. |
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:
char1,short2,int4,long long8,float4,double8 bytes. - Mudam:
long, ponteiros esize_t(8 bytes no Linux de 64 bits, 4 no editor). Para tamanho exato,int32_t,int64_t… dostdint.h, comPRId64noprintf. - 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 é umint.