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.

Você vai aprender
  • A família: short, int, long, long long, e a letra de cada um no printf.
  • Os limites de cada tipo, com limits.h (INT_MAX, LLONG_MAX…).
  • Por que o long tem 8 bytes no gcc do laboratório e 4 no editor do guia.
  • unsigned: só positivos, e o que acontece com 3 - 5.
  • Estouro (overflow): no unsigned, a conta dá a volta e isso é garantido; no int, é comportamento indefinido.
  • Como escrever inteiros: 0xFF, 0b1010 e 1'000'000 C23, 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 o int. No printf: %hd (h de half, "meio").
int
O inteiro padrão, o que o computador calcula mais à vontade. Na dúvida, use int. No printf: %d.
long … 2000000000L
"Longo". O L no fim do número diz "este literal é long". No printf: %ld.
long long … 9000000000000LL
"Longo longo" C99: o maior inteiro garantido, com pelo menos 64 bits. LL no número, %lld no printf.
unsigned int … 4000000000U
"Sem sinal": só zero e positivos (seção 3). U no número, %u no printf.

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
TipoBytes (gcc)Faixa no gcc do laboratórioEm palavras
short2-32 768 a 32 767uns 32 mil
int4-2 147 483 648 a 2 147 483 647uns 2,1 bilhões
long8-9 223 372 036 854 775 808 a 9 223 372 036 854 775 807uns 9,2 quintilhões
long long8igual ao long aquiuns 9,2 quintilhões
unsigned40 a 4 294 967 295uns 4,2 bilhões
unsigned long long80 a 18 446 744 073 709 551 615uns 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:

Sistemaintlonglong long
Linux e macOS de 64 bits (o gcc do laboratório)488
Windows de 64 bits448
O editor do guia (WebAssembly de 32 bits)448

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 long para int". O número 8100000000 não cabe num int, então o próprio literal já é long (o C escolhe o primeiro tipo em que ele cabe). Guardar num int obriga 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.

Regra: nunca deixe um inteiro com sinal estourar

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 0x e 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 0b e 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 010 querendo dez. (É mais um motivo para guardar CEP e telefone como texto.)
%x %X %o
O printf também mostra em hexadecimal (%x minúsculo, %X maiúsculo) e em octal (%o). O C23 criou o %b, para binário: no laboratório, printf("%b\n", 10) mostra 1010, 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

SufixoTipoExemplo
(nenhum)int (ou maior, se não couber)42
U ou uunsigned int42U
L ou llong42L (prefira o L maiúsculo: l parece 1)
LLlong long42LL
UL, ULLunsigned long, unsigned long long42ULL

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.

Uma quantidade "nunca negativa" virou 4294967294

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 resultado de uma conta grande saiu negativo

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

TipoprintfMáximo (limits.h)Bytes gcc / editor
short%hdSHRT_MAX = 327672 / 2
unsigned short%huUSHRT_MAX = 655352 / 2
int%dINT_MAX = 21474836474 / 4
unsigned int%uUINT_MAX = 42949672954 / 4
long%ldLONG_MAX (depende do sistema)8 / 4
unsigned long%luULONG_MAX (depende do sistema)8 / 4
long long%lldLLONG_MAX = 92233720368547758078 / 8
unsigned long long%lluULLONG_MAX = 184467440737095516158 / 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:

printf("\n", habitantes);

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ão unsigned (%hu, %u, %lu, %llu). Na dúvida, int.
  • limits.h traz os limites: INT_MAX = 2147483647, LLONG_MAX ≈ 9,2 quintilhões.
  • O long tem 8 bytes no gcc do Linux 64 bits e 4 no editor e no Windows. Para valores grandes em qualquer lugar, long long.
  • unsigned não guarda negativos: 3 - 5 dá 4294967294. Use int para contas comuns.
  • Estouro de unsigned dá a volta (garantido). Estouro de inteiro com sinal é comportamento indefinido: o compilador pode supor que nunca acontece. -fsanitize=undefined ajuda a achar.
  • Uma conta só com int é feita em int, mesmo que o resultado vá para um long long.
  • Literais: 0xFF (hexa), 0b1010 e 1'000'000 C23, e cuidado: 010 é octal (8).