C Inteiros de Tamanho Fixo

Quantos bytes tem um long? A resposta honesta do C é: depende. No gcc do laboratório da Ana, 8. No editor deste guia, 4. O mesmo programa pode dar resultados diferentes em computadores diferentes, e isso já derrubou muito software. O cabeçalho <stdint.h> C99 resolve o problema com tipos que têm o mesmo tamanho em qualquer lugar: int8_t, int32_t, uint64_t e companhia.

Você vai aprender
  • Por que int e long não têm tamanho garantido, com um exemplo real: o mesmo programa no gcc e no editor.
  • Os tipos de tamanho fixo int8_t, int16_t, int32_t, int64_t e os uint…_t, sem sinal.
  • Os limites INT32_MAX, UINT8_MAX… e o que acontece quando você passa deles.
  • Como mostrar esses tipos no printf sem avisos em nenhum compilador: PRId64, PRIu32 (do <inttypes.h>) e SCNd64 no scanf.
  • size_t (o tipo do sizeof e do strlen) e o laço que nunca termina.
  • intptr_t, os tipos least e fast, intmax_t e a macro INT64_C.

Antes, é bom conhecer Tipos Inteiros e sizeof. Os programas foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook, Linux de 64 bits), na pasta ~/c, com gcc -std=c23 -Wall -Wextra arquivo.c -o programa, e também no editor do guia (Clang em WebAssembly de 32 bits), porque aqui a diferença entre os dois é justamente o assunto.


A ideia: palmo e centímetro

Se a Ana pede "uma mesa de 10 palmos", cada marceneiro entrega uma mesa diferente: o palmo depende da mão de quem mede. Se ela pede "uma mesa de 2 metros", todo mundo entrega a mesma.

Os tipos tradicionais do C (short, int, long) são palmos: o padrão só garante um tamanho mínimo (int tem pelo menos 16 bits, long pelo menos 32) e deixa cada compilador escolher o resto, para aproveitar melhor cada processador. Os tipos de <stdint.h> são centímetros: int32_t tem exatamente 32 bits em qualquer lugar.


1. O problema: o long muda de tamanho

#include <stdio.h>

int main(void) {
    printf("char:      %zu byte\n", sizeof(char));
    printf("short:     %zu bytes\n", sizeof(short));
    printf("int:       %zu bytes\n", sizeof(int));
    printf("long:      %zu bytes\n", sizeof(long));
    printf("long long: %zu bytes\n", sizeof(long long));
    return 0;
}

No laboratório da Ana (Linux de 64 bits, gcc):

$ gcc -std=c23 -Wall -Wextra tamanhos.c -o tamanhos
$ ./tamanhos
char:      1 byte
short:     2 bytes
int:       4 bytes
long:      8 bytes
long long: 8 bytes

Agora rode o mesmo programa no editor:

Experimente: clique em Rodar e compare a linha do long com a saída do laboratório acima.

#include <stdio.h>

int main(void) {
    printf("char:      %zu byte\n", sizeof(char));
    printf("short:     %zu bytes\n", sizeof(short));
    printf("int:       %zu bytes\n", sizeof(int));
    printf("long:      %zu bytes\n", sizeof(long));
    printf("long long: %zu bytes\n", sizeof(long long));
    return 0;
}

No editor, o long tem 4 bytes. O editor roda em WebAssembly de 32 bits; o laboratório, num Linux de 64 bits. No Windows de 64 bits, o long também tem 4 bytes. Nenhum está errado: o padrão C permite as duas escolhas.

O que isso quebra

A Ana quer saber quantos bytes tem um HD de 4 terabytes (4 × 10004):

#include <stdio.h>

int main(void) {
    long bytes = 4L * 1000 * 1000 * 1000 * 1000;
    printf("Um HD de 4 TB tem %ld bytes\n", bytes);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra hd.c -o hd
$ ./hd
Um HD de 4 TB tem 4000000000000 bytes

No laboratório, perfeito. No editor, o mesmo código dá dois avisos e um número sem sentido:

$ clang -std=c23 -Wall -Wextra hd.c -o hd
hd.c:4:35: warning: overflow in expression; result is -294'967'296 with type 'long' [-Winteger-overflow]
    4 |     long bytes = 4L * 1000 * 1000 * 1000 * 1000;
      |                  ~~~~~~~~~~~~~~~~~^~~~~~
hd.c:4:42: warning: overflow in expression; result is 1'385'447'424 with type 'long' [-Winteger-overflow]
    4 |     long bytes = 4L * 1000 * 1000 * 1000 * 1000;
      |                  ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
2 warnings generated.
$ ./hd
Um HD de 4 TB tem 1385447424 bytes

Overflow in expression = "estouro na expressão": 4 trilhões não cabem em 32 bits (o máximo é 2.147.483.647), e a conta "deu a volta". O L em 4L pede uma conta em long, mas o long do editor é pequeno demais. O programa estava certo "por sorte" no laboratório. A missão no fim da aula conserta isso.


2. <stdint.h>: tamanho escrito no nome

O nome de cada tipo conta tudo:

u
Opcional, no começo: unsigned, sem sinal (só zero e positivos).
int
Inteiro.
32
Quantos bits (não bytes!): 8, 16, 32 ou 64.
_t
De type, "tipo". É a convenção do C para nomes de tipos criados com typedef (aula typedef).
#include <stdio.h>
#include <stdint.h>

int main(void) {
    printf("int8_t:   %zu byte\n", sizeof(int8_t));
    printf("int16_t:  %zu bytes\n", sizeof(int16_t));
    printf("int32_t:  %zu bytes\n", sizeof(int32_t));
    printf("int64_t:  %zu bytes\n", sizeof(int64_t));
    printf("uint8_t:  %zu byte\n", sizeof(uint8_t));
    printf("uint64_t: %zu bytes\n", sizeof(uint64_t));
    return 0;
}
$ gcc -std=c23 -Wall -Wextra fixos.c -o fixos
$ ./fixos
int8_t:   1 byte
int16_t:  2 bytes
int32_t:  4 bytes
int64_t:  8 bytes
uint8_t:  1 byte
uint64_t: 8 bytes

No editor, a saída é idêntica. Esse é o ponto: os nomes são os mesmos em todo lugar, e por trás deles cada compilador escolhe o tipo certo. Na glibc do laboratório, int64_t é um apelido para long; na biblioteca do editor, é um apelido para long long. Você escreve int64_t e não precisa saber.

TipoBitsMenor valorMaior valorExemplo de uso
int8_t8−128127temperatura de uma geladeira
uint8_t80255um byte; cada cor de um pixel RGB
int16_t16−32.76832.767amostra de áudio
uint16_t16065.535número de porta de rede
int32_t32−2.147.483.6482.147.483.647uso geral, contadores
uint32_t3204.294.967.295endereço IPv4, cores em 0xRRGGBB
int64_t64≈ −9,2 × 10189.223.372.036.854.775.807dinheiro em centavos, tempo em milissegundos
uint64_t64018.446.744.073.709.551.615tamanho de arquivos, contadores enormes

3. Os limites: INT32_MAX e companhia

Para cada tipo, <stdint.h> também traz macros com o menor e o maior valor: INT8_MIN, INT8_MAX, UINT8_MAX e assim por diante. (Os sem sinal não têm _MIN: o mínimo é sempre 0.)

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

int main(void) {
    printf("int8_t:   %d a %d\n", INT8_MIN, INT8_MAX);
    printf("uint8_t:  0 a %d\n", UINT8_MAX);
    printf("int16_t:  %d a %d\n", INT16_MIN, INT16_MAX);
    printf("uint16_t: 0 a %d\n", UINT16_MAX);
    printf("int32_t:  %d a %d\n", INT32_MIN, INT32_MAX);
    printf("uint32_t: 0 a %u\n", UINT32_MAX);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra limites.c -o limites
$ ./limites
int8_t:   -128 a 127
uint8_t:  0 a 255
int16_t:  -32768 a 32767
uint16_t: 0 a 65535
int32_t:  -2147483648 a 2147483647
uint32_t: 0 a 4294967295

Os limites dos tipos tradicionais (INT_MAX, LONG_MAX) moram em outro cabeçalho, <limits.h>, e mudam de compilador para compilador: LONG_MAX é 9223372036854775807 no laboratório e 2147483647 no editor. Os de <stdint.h> são sempre os mesmos.

Passar do limite

O brilho de uma lâmpada inteligente da Ana vai de 0 a 255, um uint8_t. O que acontece se o programa continuar somando?

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

int main(void) {
    uint8_t brilho = 250;
    for (int i = 0; i < 8; i++) {
        printf("%d ", brilho);
        brilho = brilho + 1;
    }
    printf("\n");
    int8_t t = 127;
    t = t + 1;
    printf("int8_t 127 + 1 = %d\n", t);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra estouro8.c -o estouro8
$ ./estouro8
250 251 252 253 254 255 0 1
int8_t 127 + 1 = -128
  • Os sem sinal "dão a volta" de forma garantida: depois de 255 vem 0, como um hodômetro.
  • No int8_t, a conta t + 1 é feita em int (dá 128, sem problema) e o 128 é convertido de volta para int8_t, onde não cabe. O gcc e o Clang guardam −128. (Já estourar um int ou um int64_t numa conta é comportamento indefinido: veja Comportamento Indefinido.)
  • Nenhum compilador avisou: o valor veio de uma variável. Tipo de tamanho fixo garante o tamanho, não protege contra estouro. Escolha um tipo onde o maior valor possível cabe com folga.

4. Mostrar no printf: o problema do %ld

Qual letra do printf serve para um int64_t? Como ele é long num lugar e long long em outro, nenhuma letra fixa serve nos dois. Veja o %ld:

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

int main(void) {
    int64_t populacao = 8000000000;
    printf("População: %ld\n", populacao);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra fmt_ld.c -o fmt_ld
$ ./fmt_ld
População: 8000000000

No laboratório, sem aviso. No editor:

$ clang -std=c23 -Wall -Wextra fmt_ld.c -o fmt_ld
fmt_ld.c:6:34: warning: format specifies type 'long' but the argument has type 'int64_t' (aka 'long long') [-Wformat]
    6 |     printf("População: %ld\n", populacao);
      |                        ~~~     ^~~~~~~~~
      |                        %lld
1 warning generated.
$ ./fmt_ld
População: -589934592

E se você trocar para %lld, como o Clang sugere? Aí o gcc é que reclama (o programa funciona, mas o aviso fica):

$ gcc -std=c23 -Wall -Wextra fmt_lld.c -o fmt_lld
fmt_lld.c: In function ‘main’:
fmt_lld.c:6:27: warning: format ‘%lld’ expects argument of type ‘long long int’, but argument 2 has type ‘int64_t’ {aka ‘long int’} [-Wformat=]
    6 |     printf("População: %lld\n", populacao);
      |                        ~~~^     ~~~~~~~~~
      |                           |     |
      |                           |     int64_t {aka long int}
      |                           long long int
      |                        %ld

Repare nos apelidos: ‘int64_t’ {aka ‘long int’} no gcc, 'int64_t' (aka 'long long') no Clang ("aka" = also known as, "também conhecido como"). Cada um quer uma letra diferente.

A solução: <inttypes.h> e PRId64

O cabeçalho <inttypes.h> traz macros com a letra certa para o compilador em que o programa está sendo compilado. PRId64 vira "ld" no laboratório e "lld" no editor:

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

int main(void) {
    int64_t populacao = 8000000000;
    uint32_t visitas = 4000000000u;
    uint8_t brilho = 200;
    printf("População: %" PRId64 "\n", populacao);
    printf("Visitas:   %" PRIu32 "\n", visitas);
    printf("Brilho:    %" PRIu8 "\n", brilho);
    printf("Em hexa:   %" PRIX32 "\n", visitas);
    printf("PRId64 é \"%s\" aqui\n", PRId64);
    return 0;
}
#include <inttypes.h>
Traz as macros PRI… e SCN…. Ele já inclui o <stdint.h> sozinho, mas incluir os dois deixa claro o que você usa.
"%" PRId64 "\n"
Parece estranho, mas são três textos lado a lado: "%", a macro (que vira "ld" ou "lld") e "\n". O C junta textos vizinhos numa única string: "%" "ld" "\n" é o mesmo que "%ld\n".
PRI
De print: para o printf.
d / u / X
A conversão, como no printf: d com sinal, u sem sinal, x/X hexadecimal.
64 / 32 / 8
Os bits do tipo: PRId64 para int64_t, PRIu32 para uint32_t.
4000000000u
O u no fim diz que o número é sem sinal.
$ gcc -std=c23 -Wall -Wextra inttypes.c -o inttypes
$ ./inttypes
População: 8000000000
Visitas:   4000000000
Brilho:    200
Em hexa:   EE6B2800
PRId64 é "ld" aqui

Nenhum aviso, nem no gcc nem no Clang. No editor, a última linha muda para PRId64 é "lld" aqui, e todas as outras ficam iguais.

Missão: rode e veja o aviso e o número errado. Conserte o printf com PRId64 (não esqueça o #include <inttypes.h>) para mostrar População: 8000000000 sem nenhum aviso, aqui e em qualquer compilador.

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

int main(void) {
    int64_t populacao = 8000000000;
    printf("População: %ld\n", populacao);
    return 0;
}
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

int main(void) {
    int64_t populacao = 8000000000;
    printf("População: %" PRId64 "\n", populacao);
    return 0;
}

E para ler: SCNd64

O scanf tem a mesma família de macros, começando com SCN (de scan): SCNd64, SCNu32…

Experimente: a entrada já tem o número de passos que a Ana deu no ano. Troque por um número maior que 2 bilhões (que não caberia num int32_t) e rode de novo.

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

int main(void) {
    int64_t passos = 0;
    printf("Passos no ano: ");
    if (scanf("%" SCNd64, &passos) == 1) {
        printf("\nVocê digitou %" PRId64 "\n", passos);
    }
    return 0;
}
C23 O jeito novo: %w64d

O C23 criou um modificador para dizer os bits direto no printf: %w64d para int64_t, %w8u para uint8_t. A glibc do laboratório já aceita (o gcc 15 mostrou [8000000000] [200]). Mas a biblioteca do editor ainda não: o Clang avisa invalid conversion specifier '6' e o printf não mostra nada. Até isso chegar em todo lugar, fique com PRId64.


5. size_t: o tipo dos tamanhos

Você já usou size_t sem perceber: é o tipo do resultado do sizeof e do strlen, e o que o %zu do printf espera. Ele é sem sinal e grande o bastante para o tamanho de qualquer coisa na memória. Por isso também muda: 8 bytes no laboratório, 4 no editor. Ele vem em <stddef.h>, e também aparece quando você inclui <stdio.h>, <string.h> ou <stdlib.h>.

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

int main(void) {
    const char *nome = "Ana Souza";
    size_t n = strlen(nome);
    printf("\"%s\" tem %zu letras\n", nome, n);
    printf("sizeof(size_t) = %zu\n", sizeof(size_t));
    printf("SIZE_MAX = %zu\n", SIZE_MAX);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra sizet.c -o sizet
$ ./sizet
"Ana Souza" tem 9 letras
sizeof(size_t) = 8
SIZE_MAX = 18446744073709551615

O SIZE_MAX, maior valor de um size_t, vem de <stdint.h>. Sem esse #include, o gcc diz error: ‘SIZE_MAX’ undeclared (first use in this function) e ainda ensina o conserto: note: ‘SIZE_MAX’ is defined in header ‘<stdint.h>’; this is probably fixable by adding ‘#include <stdint.h>’. No editor, o SIZE_MAX vale 4294967295.

A armadilha: contar para trás com size_t

Como size_t não tem negativos, "zero menos um" dá a volta para o maior valor possível. Um laço que conta para trás até i >= 0 nunca termina (este tem um freio, voltas, só para a demonstração):

#include <stdio.h>

int main(void) {
    int voltas = 0;
    for (size_t i = 2; i >= 0; i--) {
        printf("i = %zu\n", i);
        voltas++;
        if (voltas == 5) break;
    }
    return 0;
}
$ gcc -std=c23 -Wall -Wextra contrario.c -o contrario
contrario.c: In function ‘main’:
contrario.c:5:26: warning: comparison of unsigned expression in ‘>= 0’ is always true [-Wtype-limits]
    5 |     for (size_t i = 2; i >= 0; i--) {
      |                          ^~
$ ./contrario
i = 2
i = 1
i = 0
i = 18446744073709551615 « 0 - 1 deu a volta
i = 18446744073709551614

Comparison of unsigned expression in ‘>= 0’ is always true = "comparar uma expressão sem sinal com >= 0 é sempre verdadeiro". O aviso vem do -Wextra, mais um motivo para sempre usá-lo. (No editor, o número gigante é 4294967295, e o Clang não avisa.) Para contar para trás com size_t, uma forma segura é for (size_t i = n; i > 0; i--) usando i - 1 como índice dentro do laço.

Misturar com e sem sinal

Comparar um size_t com um int negativo dá resultados absurdos, porque o int é convertido para sem sinal antes da comparação:

#include <stdio.h>
#include <string.h>

int main(void) {
    const char *nome = "Ana";
    int limite = -1;
    if (strlen(nome) > limite) {
        printf("maior\n");
    } else {
        printf("menor ou igual?!\n");
    }
    return 0;
}
$ gcc -std=c23 -Wall -Wextra compara.c -o compara
compara.c: In function ‘main’:
compara.c:7:22: warning: comparison of integer expressions of different signedness: ‘size_t’ {aka ‘long unsigned int’} and ‘int’ [-Wsign-compare]
    7 |     if (strlen(nome) > limite) {
      |                      ^
$ ./compara
menor ou igual?!         « 3 não é maior que -1?!

Different signedness = "com sinal diferente". O −1 virou 18446744073709551615, e 3 não é maior que isso. O conserto é não misturar: use size_t para tamanhos e índices, e compare com valores do mesmo tipo.


6. intptr_t: um inteiro do tamanho de um ponteiro

Um ponteiro guarda um endereço de memória (aula Ponteiros). Às vezes, em código de baixo nível, alguém precisa tratar esse endereço como número: para ver se ele é múltiplo de 16, por exemplo. intptr_t (com sinal) e uintptr_t (sem sinal) são inteiros com espaço garantido para um ponteiro ir e voltar sem perder nada:

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

int main(void) {
    int idade = 29;
    int *p = &idade;
    uintptr_t numero = (uintptr_t)p;
    printf("sizeof(uintptr_t) = %zu\n", sizeof numero);
    printf("o endereço termina em %" PRIuPTR "\n", numero % 16);
    int *de_volta = (int *)numero;
    printf("de volta: %d\n", *de_volta);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra ptr.c -o ptr
$ ./ptr
sizeof(uintptr_t) = 8
o endereço termina em 12
de volta: 29

No editor, sizeof(uintptr_t) é 4 (ponteiros de 32 bits) e o resto da divisão muda. Guardar um ponteiro num int ou num long pode cortar o endereço em algum computador; num uintptr_t, nunca. Para a maioria dos programas, porém, você não precisa disso: use ponteiros como ponteiros.


7. Mais tipos: least, fast e intmax_t

O <stdint.h> tem mais três famílias, menos usadas:

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

int main(void) {
    printf("int_least8_t:  %zu\n", sizeof(int_least8_t));
    printf("int_least16_t: %zu\n", sizeof(int_least16_t));
    printf("int_fast8_t:   %zu\n", sizeof(int_fast8_t));
    printf("int_fast16_t:  %zu\n", sizeof(int_fast16_t));
    printf("int_fast32_t:  %zu\n", sizeof(int_fast32_t));
    printf("intmax_t:      %zu\n", sizeof(intmax_t));
    intmax_t grande = INTMAX_MAX;
    printf("INTMAX_MAX = %jd\n", grande);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra variantes.c -o variantes
$ ./variantes
int_least8_t:  1
int_least16_t: 2
int_fast8_t:   1
int_fast16_t:  8         « no editor: 2
int_fast32_t:  8         « no editor: 4
intmax_t:      8
INTMAX_MAX = 9223372036854775807
int_least16_t
"O menor tipo com pelo menos 16 bits". Sempre existe, mesmo num processador estranho que não tenha um tipo de exatamente 16 bits.
int_fast16_t
"O tipo mais rápido com pelo menos 16 bits". A glibc acha que, num processador de 64 bits, o mais rápido é usar 8 bytes; a biblioteca do editor escolheu 2. Mais um "palmo".
intmax_t / uintmax_t
O maior inteiro que o compilador tem. No printf, %jd e %ju (o j serve só para eles).

Números grandes escritos no código: INT64_C

Um número escrito no código, como 3000000, é int. Multiplicar dois int faz a conta em int, mesmo que o resultado vá para um int64_t:

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

int main(void) {
    int64_t a = 3000000 * 1000;
    int64_t b = INT64_C(3000000) * 1000;
    printf("a = %" PRId64 "\n", a);
    printf("b = %" PRId64 "\n", b);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra literais.c -o literais
literais.c: In function ‘main’:
literais.c:6:25: warning: integer overflow in expression of type ‘int’ results in ‘-1294967296’ [-Woverflow]
    6 |     int64_t a = 3000000 * 1000;
      |                         ^
$ ./literais
a = -1294967296
b = 3000000000

A macro INT64_C(3000000) escreve o número já como int64_t (ela acrescenta o L ou o LL certo para cada compilador), e a conta inteira passa a ser feita em 64 bits. Existem também UINT64_C, INT32_C, UINT32_C… Outra saída é converter: (int64_t)3000000 * 1000.


8. Qual usar

SituaçãoUsePor quê
Contas do dia a dia, contadores pequenosintSimples, rápido; 32 bits em todos os computadores comuns de hoje
Tamanhos, índices de arrays, resultado de strlen e sizeofsize_tÉ o tipo que a biblioteca usa; cabe qualquer tamanho
Valores que podem passar de 2 bilhões (dinheiro em centavos, milissegundos, bytes de arquivo)int64_t / uint64_t64 bits garantidos, ao contrário do long
Dados de arquivos binários, rede, imagens, hardwareuint8_t, uint16_t, uint32_tO formato exige um tamanho exato (aula Arquivos Binários)
Bits e máscarasuint8_t, uint32_tSem sinal e com largura conhecida (aula Operadores de Bits)
Guardar um ponteiro como númerouintptr_tGarante espaço para o endereço
Qualquer coisa que precise de "64 bits"Não longTem 4 bytes no editor e no Windows

Quando dá errado

warning: format ‘%lld’ expects argument of type ‘long long int’, but argument 2 has type ‘int64_t’ {aka ‘long int’}

O que significa: a letra do printf não combina com o tipo. Com int64_t, %ld acerta num compilador e %lld no outro; no editor o aviso é format specifies type 'long' but the argument has type 'int64_t' (aka 'long long'), e o número sai errado.

Como consertar: #include <inttypes.h> e printf("%" PRId64 "\n", x); (ou PRIu64, PRIu32… conforme o tipo).

error: unknown type name ‘uint8_t’ ou ‘SIZE_MAX’ undeclared

O que significa: faltou #include <stdint.h>. O gcc costuma até sugerir: is defined in header ‘<stdint.h>’; this is probably fixable by adding ‘#include <stdint.h>’.

Como consertar: acrescente #include <stdint.h> (ou <inttypes.h>, que inclui o primeiro).

warning: comparison of unsigned expression in ‘>= 0’ is always true [-Wtype-limits]

O que significa: uma variável sem sinal (size_t, uint32_t) é sempre >= 0; um laço que depende disso nunca termina, e o índice vira um número gigante.

Como consertar: conte para trás com for (size_t i = n; i > 0; i--) e use i - 1 dentro, ou use um tipo com sinal se os valores são pequenos.

warning: integer overflow in expression of type ‘int’ results in ‘-1294967296’

O que significa: a conta foi feita em int (os dois números eram int) e estourou, mesmo que o resultado vá para um tipo grande. No editor, o mesmo acontece com contas em long: overflow in expression; result is … with type 'long'.

Como consertar: faça a conta no tipo grande desde o começo: INT64_C(3000000) * 1000 ou (int64_t)3000000 * 1000.

warning: comparison of integer expressions of different signedness

O que significa: você comparou um valor sem sinal (size_t) com um com sinal (int). Se o int for negativo, a comparação dá o resultado errado.

Como consertar: use o mesmo tipo dos dois lados (geralmente size_t para tamanhos) ou confira o negativo antes.


Referência rápida

EscritaCabeçalhoO que é
int8_t … int64_t<stdint.h>Inteiro com sinal de exatamente 8, 16, 32 ou 64 bits
uint8_t … uint64_t<stdint.h>O mesmo, sem sinal
INT32_MIN, INT32_MAX, UINT8_MAX…<stdint.h>Limites de cada tipo
INT64_C(n), UINT32_C(n)<stdint.h>Escreve o número n já no tipo certo
size_t, SIZE_MAX<stddef.h>, <stdint.h>Tamanhos (sem sinal); %zu no printf
intptr_t, uintptr_t<stdint.h>Inteiro que guarda um ponteiro
int_least16_t, int_fast16_t<stdint.h>Menor / mais rápido com pelo menos N bits
intmax_t<stdint.h>O maior inteiro; %jd no printf
PRId64, PRIu32, PRIX32, PRIuPTR<inttypes.h>Letras do printf: "%" PRId64
SCNd64, SCNu32<inttypes.h>Letras do scanf

Exercícios

1. O tipo do pixel

Cada cor de um pixel vai de 0 a 255. Complete com o tipo de tamanho fixo certo:

vermelho = 255;

2. A macro do printf

Complete para mostrar um uint32_t chamado visitas:

printf("%" "\n", visitas);

3. Quantos bytes tem um long?

4. Um uint8_t vale 255. Quanto ele vale depois de x = x + 1;?

5. Por que for (size_t i = 5; i >= 0; i--) nunca termina?

6. O que é "%" PRId64 "\n" para o compilador?

Missão final: o HD de 4 TB em qualquer computador

Missão: este é o programa da seção 1, que funciona no laboratório e quebra aqui. Troque o long por int64_t, faça a conta em 64 bits (com INT64_C(4) no lugar de 4L) e mostre com PRId64. Sem avisos. A saída tem que ser:

Um HD de 4 TB tem 4000000000000 bytes
#include <stdio.h>

int main(void) {
    long bytes = 4L * 1000 * 1000 * 1000 * 1000;
    printf("Um HD de 4 TB tem %ld bytes\n", bytes);
    return 0;
}
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

int main(void) {
    int64_t bytes = INT64_C(4) * 1000 * 1000 * 1000 * 1000;
    printf("Um HD de 4 TB tem %" PRId64 " bytes\n", bytes);
    return 0;
}

A solução mostra a mesma linha no laboratório e no editor. É isso que "portátil" quer dizer: o programa dá o mesmo resultado em qualquer computador.


Resumo

  • int e long só têm tamanho mínimo garantido. O long tem 8 bytes no gcc do laboratório e 4 no editor (e no Windows).
  • <stdint.h> C99 traz int8_t a int64_t e uint8_t a uint64_t: o número é a quantidade exata de bits, em qualquer lugar.
  • Limites: INT32_MAX, UINT8_MAX… Os sem sinal dão a volta (255 + 1 = 0); tamanho fixo não impede estouro.
  • No printf, use <inttypes.h>: "%" PRId64, "%" PRIu32. No scanf, SCNd64. O %w64d do C23 ainda não funciona no editor.
  • size_t é o tipo dos tamanhos (sizeof, strlen, %zu): sem sinal, então i >= 0 é sempre verdade. Não misture com int negativo.
  • uintptr_t guarda um ponteiro como número; int_leastN_t, int_fastN_t e intmax_t completam a família; INT64_C(n) faz a conta em 64 bits desde o começo.