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.
- Por que
intelongnã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_te osuint…_t, sem sinal. - Os limites
INT32_MAX,UINT8_MAX… e o que acontece quando você passa deles. - Como mostrar esses tipos no
printfsem avisos em nenhum compilador:PRId64,PRIu32(do<inttypes.h>) eSCNd64noscanf. size_t(o tipo dosizeofe dostrlen) e o laço que nunca termina.intptr_t, os tiposleastefast,intmax_te a macroINT64_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.
| Tipo | Bits | Menor valor | Maior valor | Exemplo de uso |
|---|---|---|---|---|
int8_t | 8 | −128 | 127 | temperatura de uma geladeira |
uint8_t | 8 | 0 | 255 | um byte; cada cor de um pixel RGB |
int16_t | 16 | −32.768 | 32.767 | amostra de áudio |
uint16_t | 16 | 0 | 65.535 | número de porta de rede |
int32_t | 32 | −2.147.483.648 | 2.147.483.647 | uso geral, contadores |
uint32_t | 32 | 0 | 4.294.967.295 | endereço IPv4, cores em 0xRRGGBB |
int64_t | 64 | ≈ −9,2 × 1018 | 9.223.372.036.854.775.807 | dinheiro em centavos, tempo em milissegundos |
uint64_t | 64 | 0 | 18.446.744.073.709.551.615 | tamanho 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 contat + 1é feita emint(dá 128, sem problema) e o 128 é convertido de volta paraint8_t, onde não cabe. O gcc e o Clang guardam −128. (Já estourar umintou umint64_tnuma 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…eSCN…. 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:dcom sinal,usem sinal,x/Xhexadecimal. - 64 / 32 / 8
- Os bits do tipo:
PRId64paraint64_t,PRIu32parauint32_t. - 4000000000u
- O
uno 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;
}
%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,%jde%ju(ojserve 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ção | Use | Por quê |
|---|---|---|
| Contas do dia a dia, contadores pequenos | int | Simples, rápido; 32 bits em todos os computadores comuns de hoje |
Tamanhos, índices de arrays, resultado de strlen e sizeof | size_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_t | 64 bits garantidos, ao contrário do long |
| Dados de arquivos binários, rede, imagens, hardware | uint8_t, uint16_t, uint32_t | O formato exige um tamanho exato (aula Arquivos Binários) |
| Bits e máscaras | uint8_t, uint32_t | Sem sinal e com largura conhecida (aula Operadores de Bits) |
| Guardar um ponteiro como número | uintptr_t | Garante espaço para o endereço |
| Qualquer coisa que precise de "64 bits" | Não long | Tem 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
| Escrita | Cabeçalho | O 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:
2. A macro do printf
Complete para mostrar um uint32_t chamado 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
intelongsó têm tamanho mínimo garantido. Olongtem 8 bytes no gcc do laboratório e 4 no editor (e no Windows).<stdint.h>C99 trazint8_taint64_teuint8_tauint64_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. Noscanf,SCNd64. O%w64ddo C23 ainda não funciona no editor. size_té o tipo dos tamanhos (sizeof,strlen,%zu): sem sinal, entãoi >= 0é sempre verdade. Não misture comintnegativo.uintptr_tguarda um ponteiro como número;int_leastN_t,int_fastN_teintmax_tcompletam a família;INT64_C(n)faz a conta em 64 bits desde o começo.