C Inteiros
Números inteiros estão em todo lugar: a quantidade de maçãs da lojinha, a idade de um cliente, os pontos de um jogo, os segundos de um cronômetro. O C tem uma família de tipos inteiros, de tamanhos diferentes, com e sem sinal. Nesta aula você vai conhecer cada um, descobrir até onde cada caixa vai e ver o que acontece quando uma conta passa do limite, um dos assuntos mais importantes (e perigosos) do C.
- A família:
short,int,long,long long, e a letra de cada um noprintf. - Os limites de cada tipo, com
limits.h(INT_MAX,LLONG_MAX…). - Por que o
longtem 8 bytes no gcc do laboratório e 4 no editor do guia. unsigned: só positivos, e o que acontece com3 - 5.- Estouro (overflow): no
unsigned, a conta dá a volta e isso é garantido; noint, é comportamento indefinido. - Como escrever inteiros:
0xFF,0b1010e1'000'000C23, e a armadilha do zero à esquerda.
Todos os programas desta aula foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook, Linux 64 bits), na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa.
A ideia: o hodômetro do carro
O hodômetro de um carro antigo tem 6 rodinhas: mostra de 000000 a 999999 quilômetros. Ele não consegue mostrar um milhão. Se o carro rodar mais 1 km depois de 999999, as rodinhas voltam para 000000.
Os inteiros do computador são iguais: cada tipo tem um número fixo de "rodinhas" (bits), e por isso um maior valor e um menor valor. Para números maiores, você precisa de uma caixa com mais rodinhas. E o que acontece quando a conta passa do limite depende do tipo, como você vai ver na seção 4.
1. A família dos inteiros
#include <stdio.h>
int main(void) {
short pequeno = 1200;
int normal = 2000000;
long grande = 2000000000L;
long long enorme = 9000000000000LL;
unsigned int positivo = 4000000000U;
printf("short: %hd\n", pequeno);
printf("int: %d\n", normal);
printf("long: %ld\n", grande);
printf("long long: %lld\n", enorme);
printf("unsigned: %u\n", positivo);
return 0;
}
$ gcc -std=c23 -Wall -Wextra familia.c -o familia $ ./familia short: 1200 int: 2000000 long: 2000000000 long long: 9000000000000 unsigned: 4000000000
- short
- "Curto": inteiro pequeno. O nome completo é
short int; quase ninguém escreve oint. Noprintf:%hd(hde half, "meio"). - int
- O inteiro padrão, o que o computador calcula mais à vontade. Na dúvida, use
int. Noprintf:%d. - long … 2000000000L
- "Longo". O
Lno fim do número diz "este literal élong". Noprintf:%ld. - long long … 9000000000000LL
- "Longo longo" C99: o maior inteiro garantido, com pelo menos 64 bits.
LLno número,%lldnoprintf. - unsigned int … 4000000000U
- "Sem sinal": só zero e positivos (seção 3).
Uno número,%unoprintf.
Todos estes tipos são, na verdade, signed ("com sinal") quando você não diz nada: int e signed int são a mesma coisa. Cada um tem a sua versão unsigned: unsigned short (%hu), unsigned int ou só unsigned (%u), unsigned long (%lu) e unsigned long long (%llu).
2. Os limites: limits.h
O cabeçalho limits.h ("limites") traz o maior e o menor valor de cada tipo, com nomes em maiúsculas: INT_MAX (o máximo do int), INT_MIN (o mínimo), e assim por diante.
#include <stdio.h>
#include <limits.h>
int main(void) {
printf("char: %d a %d\n", CHAR_MIN, CHAR_MAX);
printf("short: %d a %d\n", SHRT_MIN, SHRT_MAX);
printf("int: %d a %d\n", INT_MIN, INT_MAX);
printf("long: %ld a %ld\n", LONG_MIN, LONG_MAX);
printf("long long: %lld a %lld\n", LLONG_MIN, LLONG_MAX);
printf("unsigned: 0 a %u\n", UINT_MAX);
printf("unsigned long long: 0 a %llu\n", ULLONG_MAX);
return 0;
}
$ gcc -std=c23 -Wall -Wextra limites.c -o limites $ ./limites char: -128 a 127 short: -32768 a 32767 int: -2147483648 a 2147483647 long: -9223372036854775808 a 9223372036854775807 long long: -9223372036854775808 a 9223372036854775807 unsigned: 0 a 4294967295 unsigned long long: 0 a 18446744073709551615
| Tipo | Bytes (gcc) | Faixa no gcc do laboratório | Em palavras |
|---|---|---|---|
short | 2 | -32 768 a 32 767 | uns 32 mil |
int | 4 | -2 147 483 648 a 2 147 483 647 | uns 2,1 bilhões |
long | 8 | -9 223 372 036 854 775 808 a 9 223 372 036 854 775 807 | uns 9,2 quintilhões |
long long | 8 | igual ao long aqui | uns 9,2 quintilhões |
unsigned | 4 | 0 a 4 294 967 295 | uns 4,2 bilhões |
unsigned long long | 8 | 0 a 18 446 744 073 709 551 615 | uns 18 quintilhões |
Repare que o lado negativo sempre tem um a mais (-128 e 127): o zero "ocupa" uma vaga do lado positivo. E o unsigned vai até o dobro, porque usa para os positivos as vagas que seriam dos negativos.
O long do gcc e o long do editor
Rode o mesmo limites.c no editor do guia e uma linha muda:
long: -2147483648 a 2147483647 « no editor: o long tem 4 bytes, igual ao int
Não é defeito. A norma do C só exige que o long tenha pelo menos 32 bits. Cada sistema escolhe:
| Sistema | int | long | long long |
|---|---|---|---|
| Linux e macOS de 64 bits (o gcc do laboratório) | 4 | 8 | 8 |
| Windows de 64 bits | 4 | 4 | 8 |
| O editor do guia (WebAssembly de 32 bits) | 4 | 4 | 8 |
Moral: não conte com o tamanho do long. Para números que podem passar de 2 bilhões, use long long, que tem 8 bytes nos três. Quando o tamanho exato importa (arquivos, protocolos de rede), existem os tipos de tamanho fixo, como int32_t e int64_t (aula Inteiros de Tamanho Fixo). As missões desta aula nunca dependem do tamanho do long.
Quando o valor não cabe na caixa
Escrever direto um número grande demais para o tipo gera um aviso, e o valor guardado é outro:
#include <stdio.h>
int main(void) {
int populacao = 8100000000;
short ano = 70000;
printf("%d %hd\n", populacao, ano);
return 0;
}
$ gcc -std=c23 -Wall -Wextra naocabe.c -o naocabe
naocabe.c: In function ‘main’:
naocabe.c:4:21: warning: overflow in conversion from ‘long int’ to ‘int’ changes value from ‘8100000000’ to ‘-489934592’ [-Woverflow]
4 | int populacao = 8100000000;
| ^~~~~~~~~~
naocabe.c:5:17: warning: overflow in conversion from ‘int’ to ‘short int’ changes value from ‘70000’ to ‘4464’ [-Woverflow]
5 | short ano = 70000;
| ^~~~~
$ ./naocabe
-489934592 4464
- overflow in conversion from ‘long int’ to ‘int’
- "Estouro na conversão de
longparaint". O número 8100000000 não cabe numint, então o próprio literal já élong(o C escolhe o primeiro tipo em que ele cabe). Guardar numintobriga a cortar. - changes value from ‘8100000000’ to ‘-489934592’
- "Muda o valor": o gcc até conta qual número vai sair. Sobraram só os 32 bits de baixo, que formam um número negativo sem nenhuma relação com a população do mundo.
O Clang do editor avisa o mesmo com outras palavras: implicit conversion from 'long long' to 'int' changes value from 8100000000 to -489934592 [-Wconstant-conversion] (lá o literal é long long, porque o long do editor é pequeno). Conserto: long long populacao = 8'100'000'000;.
3. unsigned: só positivos
Um unsigned não tem sinal: não guarda números negativos. Parece ótimo para quantidades ("estoque nunca é negativo"), mas veja o que acontece quando uma conta dá menos que zero:
#include <stdio.h>
int main(void) {
unsigned int estoque = 3;
estoque = estoque - 5;
printf("Estoque: %u\n", estoque);
unsigned int menos_um = -1;
printf("menos_um: %u\n", menos_um);
return 0;
}
$ gcc -std=c23 -Wall -Wextra negativo.c -o negativo $ ./negativo Estoque: 4294967294 « 3 - 5 deu quase 4,3 bilhões menos_um: 4294967295 « e -1 virou o maior unsigned
Nenhum aviso, nem com -Wall -Wextra. O unsigned não "fica negativo": ele dá a volta por cima, como o hodômetro andando para trás. 3 - 5 = -2, que vira 4294967296 - 2 = 4294967294. Por isso o conselho da maioria dos guias de estilo é: use int para contas comuns, mesmo de coisas que "nunca são negativas", e deixe o unsigned para quando você realmente precisar dele (bits, tamanhos, aula Operadores de Bits).
Missão: a Ana tinha 3 cadernos e vendeu 5 (2 ficaram encomendados). O programa mostra um estoque de 4 bilhões! Troque o tipo de estoque para int (e a letra do printf para %d), para mostrar Estoque: -2: um número negativo que avisa que faltam 2 cadernos.
#include <stdio.h>
int main(void) {
unsigned int estoque = 3;
estoque = estoque - 5;
printf("Estoque: %u\n", estoque);
return 0;
}
#include <stdio.h>
int main(void) {
int estoque = 3;
estoque = estoque - 5;
printf("Estoque: %d\n", estoque);
return 0;
}
4. Estouro: quando a conta passa do limite
Estouro (overflow, "transbordar") é quando o resultado de uma conta não cabe no tipo. Aqui o C trata os dois tipos de inteiro de jeitos muito diferentes.
Sem sinal (unsigned): dá a volta, e isso é garantido
#include <stdio.h>
#include <limits.h>
int main(void) {
unsigned int u = UINT_MAX;
printf("u = %u\n", u);
u = u + 1;
printf("u + 1 = %u\n", u);
unsigned char c = 250;
for (int i = 0; i < 8; i++) {
printf("%d ", c);
c = c + 1;
}
printf("\n");
return 0;
}
$ gcc -std=c23 -Wall -Wextra volta.c -o volta $ ./volta u = 4294967295 u + 1 = 0 250 251 252 253 254 255 0 1
O maior unsigned mais 1 é 0, exatamente como o hodômetro. O unsigned char (1 byte, de 0 a 255) mostra isso devagar: 254, 255, 0, 1. (O for só repete a linha 8 vezes; ele tem aula própria, for.)
A norma do C garante esse comportamento: contas com unsigned são feitas "módulo 2bits", ou seja, dão a volta. Todo compilador, em todo computador, dá o mesmo resultado. Não é um erro da linguagem; às vezes é justamente o que se quer (relógios, contadores circulares, cálculos de hash).
Com sinal (int, long long…): comportamento indefinido
Agora o mesmo com int:
#include <stdio.h>
#include <limits.h>
int main(void) {
int pontos = INT_MAX;
printf("pontos = %d\n", pontos);
pontos = pontos + 1;
printf("pontos + 1 = %d\n", pontos);
return 0;
}
$ gcc -std=c23 -Wall -Wextra estouro.c -o estouro $ ./estouro pontos = 2147483647 pontos + 1 = -2147483648 « o jogador ficou com 2 bilhões de pontos negativos
Parece que deu a volta também. Mas, para o int, a norma diz que o estouro é comportamento indefinido (undefined behavior, UB): o programa está errado, e a linguagem não promete nada sobre o resultado. O -2147483648 foi o que esta máquina fez desta vez. O gcc tem uma opção que vigia isso enquanto o programa roda, o -fsanitize=undefined ("sanitizar o indefinido"):
$ gcc -std=c23 -Wall -Wextra -fsanitize=undefined estouro.c -o estouro $ ./estouro pontos = 2147483647 estouro.c:7:12: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int' pontos + 1 = -2147483648
Runtime error: signed integer overflow = "erro durante a execução: estouro de inteiro com sinal: 2147483647 + 1 não pode ser representado no tipo int". Ele aponta a linha 7 e a coluna 12, o +, e deixa o programa continuar. (Os sanitizers têm aula própria, Sanitizers, e não funcionam no editor do guia.)
Por que "indefinido" é pior do que "dá a volta"
Como o estouro de int "não pode acontecer" num programa correto, o compilador tem o direito de supor que ele nunca acontece. Veja uma função que tenta descobrir se ainda cabe mais um:
#include <stdio.h>
#include <limits.h>
int cabe_mais_um(int x) {
return x + 1 > x;
}
int main(void) {
printf("%d\n", cabe_mais_um(10));
printf("%d\n", cabe_mais_um(INT_MAX));
return 0;
}
$ gcc -std=c23 -Wall -Wextra otimiza.c -o otimiza $ ./otimiza 1 1 « INT_MAX + 1 > INT_MAX? o gcc diz que sim
Se a conta desse a volta, INT_MAX + 1 seria -2147483648, e a resposta seria 0 (falso). Mas o gcc raciocinou assim: "x + 1 é sempre maior que x, porque estouro não existe" e trocou a conta inteira por 1. No editor do guia, o Clang compilou a conta de verdade e mostrou 1 e 0. Dois compiladores, duas respostas, e os dois estão "certos": com comportamento indefinido, qualquer resultado vale. A aula Comportamento Indefinido mostra outros casos.
Escolha um tipo grande o bastante (long long para valores que passam de 2 bilhões) e, quando o valor vem de fora (do teclado, de um arquivo), confira antes da conta se ela cabe: if (x < INT_MAX) x = x + 1;. Para testar seus programas, compile de vez em quando com -fsanitize=undefined.
O estouro escondido na conta
Uma armadilha clássica: guardar o resultado num tipo grande não basta, se a conta foi feita num tipo pequeno.
#include <stdio.h>
int main(void) {
int anos = 100;
long long segundos = anos * 365 * 24 * 60 * 60;
printf("Segundos em %d anos: %lld\n", anos, segundos);
return 0;
}
$ gcc -std=c23 -Wall -Wextra segundos.c -o segundos $ ./segundos Segundos em 100 anos: -1141367296
A caixa segundos é long long, mas a conta anos * 365 * 24 * 60 * 60 só tem int, então é feita em int. O resultado certo (3 153 600 000) passa de INT_MAX: estourou antes de chegar na caixa. Nem o gcc nem o Clang avisaram, porque anos é uma variável. Para a conta ser feita em long long, pelo menos um dos números tem de ser long long desde o começo: long long anos = 100; ou anos * 365LL * …. A missão final é sobre isso.
5. Como escrever números inteiros (literais)
#include <stdio.h>
int main(void) {
int decimal = 255;
int hexa = 0xFF;
int binario = 0b11111111;
int octal = 0377;
int milhao = 1'000'000;
printf("%d %d %d %d %d\n", decimal, hexa, binario, octal, milhao);
int cep_errado = 010;
printf("010 vale %d\n", cep_errado);
printf("%x %X %o\n", 255, 255, 255);
return 0;
}
$ gcc -std=c23 -Wall -Wextra literais.c -o literais $ ./literais 255 255 255 255 1000000 010 vale 8 ff FF 377
- 255
- Decimal, base 10, o jeito de sempre.
- 0xFF
- Hexadecimal, base 16: começa com
0xe usa os dígitos 0 a 9 e A a F (A = 10 … F = 15).FF= 15 × 16 + 15 = 255. Muito usado para cores (#FF8000) e endereços de memória. - 0b11111111
- Binário, base 2 C23: começa com
0be só usa 0 e 1. Oito 1s = 255. Antes do C23 era uma extensão do gcc e do Clang. - 0377
- Octal, base 8: qualquer número que começa com
0! 3 × 64 + 7 × 8 + 7 = 255. - 1'000'000
- Separador de dígitos C23: os apóstrofos só ajudam a ler e são ignorados. Funciona em qualquer base:
0b1111'0000. - 010 vale 8
- A armadilha: o zero à esquerda transforma o número em octal. Nunca escreva
010querendo dez. (É mais um motivo para guardar CEP e telefone como texto.) - %x %X %o
- O
printftambém mostra em hexadecimal (%xminúsculo,%Xmaiúsculo) e em octal (%o). O C23 criou o%b, para binário: no laboratório,printf("%b\n", 10)mostra1010, mas a biblioteca do editor do guia ainda não o conhece e não mostra nada. Veja Formatos do printf.
Pedindo o C17 com -Wpedantic, o gcc lembra que essas duas escritas são novas, e o separador nem compila:
$ gcc -std=c17 -Wall -Wextra -Wpedantic literais.c -o literais
literais.c: In function ‘main’:
literais.c:6:19: warning: binary constants are a C23 feature or GCC extension [-Wpedantic]
6 | int binario = 0b11111111;
| ^~~~~~~~~~
literais.c:8:19: warning: multi-character character constant [-Wmultichar]
8 | int milhao = 1'000'000;
| ^~~~~
literais.c:8:19: error: expected ‘,’ or ‘;’ before '\x303030'
No C17, o apóstrofo abre um caractere (como 'A'), e o gcc leu '000' como um caractere estranho de três letras. Se você vir esse erro em código moderno, o compilador está num padrão antigo.
Sufixos: o tipo do literal
| Sufixo | Tipo | Exemplo |
|---|---|---|
| (nenhum) | int (ou maior, se não couber) | 42 |
U ou u | unsigned int | 42U |
L ou l | long | 42L (prefira o L maiúsculo: l parece 1) |
LL | long long | 42LL |
UL, ULL | unsigned long, unsigned long long | 42ULL |
Missão: a cor laranja do site da lojinha é #FF8000: vermelho FF, verde 80 e azul 00, em hexadecimal. Escreva os valores de r e g como literais hexadecimais (com 0x) para o programa mostrar R=255 G=128 B=0.
#include <stdio.h>
int main(void) {
int r = 0;
int g = 0;
int b = 0x00;
printf("R=%d G=%d B=%d\n", r, g, b);
return 0;
}
#include <stdio.h>
int main(void) {
int r = 0xFF;
int g = 0x80;
int b = 0x00;
printf("R=%d G=%d B=%d\n", r, g, b);
return 0;
}
Quando dá errado
warning: overflow in conversion from ‘long int’ to ‘int’ changes value…
O que significa: o número escrito não cabe no tipo da variável. O valor guardado será outro (o gcc diz qual). No Clang: implicit conversion … changes value [-Wconstant-conversion].
Como consertar: use um tipo maior, em geral long long (com %lld).
warning: integer overflow in expression of type ‘int’ results in ‘-2147483648’
O que significa: uma conta só com valores fixos (como INT_MAX + 1) estoura, e o gcc percebeu na hora de compilar:
$ gcc -std=c23 -Wall -Wextra constante.c -o constante
constante.c: In function ‘main’:
constante.c:5:21: warning: integer overflow in expression of type ‘int’ results in ‘-2147483648’ [-Woverflow]
5 | int x = INT_MAX + 1;
| ^
Como consertar: faça a conta num tipo maior (INT_MAX + 1LL, guardado num long long). O Clang diz overflow in expression; result is -2'147'483'648 with type 'int' [-Winteger-overflow]. Com variáveis, nenhum dos dois avisa: aí só o -fsanitize=undefined pega.
O que significa: uma conta com unsigned deu menos que zero e deu a volta. Nenhum aviso aparece.
Como consertar: use int (ou long long) para contas; confira if (a >= b) antes de fazer a - b com unsigned.
O que significa: estouro de inteiro com sinal, muitas vezes escondido numa conta feita em int e guardada em long long.
Como consertar: faça pelo menos um operando ser long long desde o início (365LL ou uma variável long long). Teste com -fsanitize=undefined.
010 mostra 8
O que significa: número com zero à esquerda é octal (base 8).
Como consertar: tire o zero. Se os zeros à esquerda fazem parte do dado (CEP, código de barras), guarde como texto.
Referência rápida
| Tipo | printf | Máximo (limits.h) | Bytes gcc / editor |
|---|---|---|---|
short | %hd | SHRT_MAX = 32767 | 2 / 2 |
unsigned short | %hu | USHRT_MAX = 65535 | 2 / 2 |
int | %d | INT_MAX = 2147483647 | 4 / 4 |
unsigned int | %u | UINT_MAX = 4294967295 | 4 / 4 |
long | %ld | LONG_MAX (depende do sistema) | 8 / 4 |
unsigned long | %lu | ULONG_MAX (depende do sistema) | 8 / 4 |
long long | %lld | LLONG_MAX = 9223372036854775807 | 8 / 8 |
unsigned long long | %llu | ULLONG_MAX = 18446744073709551615 | 8 / 8 |
Literais: 255, 0xFF, 0b1111'1111 C23, 0377 (octal), 1'000'000 C23, sufixos U, L, LL. | |||
Estouro: unsigned dá a volta (definido); com sinal é comportamento indefinido. | |||
Exercícios
1. A letra do long long
Complete o formato para mostrar um long long:
2. O máximo do int
Qual é o nome, em limits.h, do maior valor que um int guarda?
3. unsigned int u = 0; u = u - 1; Quanto vale u no gcc do laboratório?
4. E int x = INT_MAX; x = x + 1;?
5. Quanto vale 012 em C?
6. Você precisa guardar 5 bilhões e quer que funcione igual no Linux, no Windows e no editor do guia. Qual tipo?
Missão final: quantos segundos tem um século?
Missão: o programa calcula quantos segundos existem em 100 anos (de 365 dias), mas mostra um número negativo: a conta estoura o int antes de chegar na caixa long long. Conserte para mostrar Segundos em 100 anos: 3153600000. Dica: basta mudar o tipo de anos (e a letra do printf que mostra anos).
#include <stdio.h>
int main(void) {
int anos = 100;
long long segundos = anos * 365 * 24 * 60 * 60;
printf("Segundos em %d anos: %lld\n", anos, segundos);
return 0;
}
#include <stdio.h>
int main(void) {
long long anos = 100;
long long segundos = anos * 365 * 24 * 60 * 60;
printf("Segundos em %lld anos: %lld\n", anos, segundos);
return 0;
}
Outra solução que funciona: deixar anos como int e escrever anos * 365LL * 24 * 60 * 60. Assim que um número da conta é long long, o resto da conta é feita em long long (as regras exatas estão em Conversão de Tipos).
Resumo
- Família:
short(%hd),int(%d),long(%ld),long long(%lld), cada um com versãounsigned(%hu,%u,%lu,%llu). Na dúvida,int. limits.htraz os limites:INT_MAX= 2147483647,LLONG_MAX≈ 9,2 quintilhões.- O
longtem 8 bytes no gcc do Linux 64 bits e 4 no editor e no Windows. Para valores grandes em qualquer lugar,long long. unsignednão guarda negativos:3 - 5dá 4294967294. Useintpara contas comuns.- Estouro de
unsigneddá a volta (garantido). Estouro de inteiro com sinal é comportamento indefinido: o compilador pode supor que nunca acontece.-fsanitize=undefinedajuda a achar. - Uma conta só com
inté feita emint, mesmo que o resultado vá para umlong long. - Literais:
0xFF(hexa),0b1010e1'000'000C23, e cuidado:010é octal (8).