Tipos inteiros em detalhe
Esta aula detalha os tipos inteiros em C, incluindo os modificadores signed/unsigned, short, long, long long, seus tamanhos e limites definidos em limits.h, e o fenômeno de overflow. O conteúdo é complementado com exemplos práticos, exercícios e referências.
Os tipos inteiros são fundamentais na linguagem C, pois representam números sem parte fracionária. Compreender seus modificadores, tamanhos e limites é essencial para escrever código eficiente e evitar erros comuns, como overflow. Nesta aula, exploraremos os modificadores signed, unsigned, short, long e long long, além de como consultar os limites usando a biblioteca limits.h.
signed e unsigned
Em C, os tipos inteiros podem ser signed (com sinal) ou unsigned (sem sinal). Por padrão, int é signed, podendo representar números positivos e negativos. Já unsigned armazena apenas valores não negativos, o que dobra o limite superior positivo em relação ao signed correspondente.
A diferença está na interpretação do bit mais significativo (MSB). Em tipos signed, o MSB indica o sinal (0 para positivo, 1 para negativo), usando representação complemento de dois. Em unsigned, todos os bits são usados para magnitude. Por exemplo, um unsigned char de 8 bits pode representar valores de 0 a 255, enquanto um signed char representa de -128 a 127.
#include <stdio.h>
int main() {
signed int a = -10; // explícito, mas redundante
unsigned int b = 20; // apenas não negativos
printf("a = %d, b = %u\n", a, b);
return 0;
}
short, long, long long
Os modificadores short, long e long long alteram o tamanho (em bytes) do tipo inteiro. short int (ou apenas short) geralmente ocupa 2 bytes, long int (ou long) pelo menos 4 bytes, e long long int (ou long long) pelo menos 8 bytes. O tamanho exato depende da arquitetura e do compilador.
Esses modificadores podem ser combinados com signed e unsigned. Por exemplo: unsigned long long int. É importante notar que short e long são garantias mínimas: short tem pelo menos 16 bits, long pelo menos 32 bits, e long long pelo menos 64 bits.
#include <stdio.h>
int main() {
short s = 1;
long l = 2;
long long ll = 3;
printf("short: %zu bytes\n", sizeof(s));
printf("long: %zu bytes\n", sizeof(l));
printf("long long: %zu bytes\n", sizeof(ll));
return 0;
}
Tamanhos e limites (limits.h)
A biblioteca <limits.h> define constantes que representam os valores mínimos e máximos para cada tipo inteiro. Por exemplo: INT_MIN, INT_MAX, UINT_MAX, SHRT_MIN, SHRT_MAX, LONG_MIN, LONG_MAX, LLONG_MIN, LLONG_MAX, etc. Essas constantes são úteis para verificar limites e evitar overflow.
O operador sizeof retorna o tamanho em bytes de um tipo ou variável. Combinando sizeof com limits.h, podemos determinar a faixa de valores de cada tipo. Lembre-se que o número de bits é sizeof(tipo) * CHAR_BIT (onde CHAR_BIT geralmente é 8).
#include <stdio.h>
#include <limits.h>
int main() {
printf("int: %zu bytes, min: %d, max: %d\n", sizeof(int), INT_MIN, INT_MAX);
printf("unsigned int: %zu bytes, max: %u\n", sizeof(unsigned int), UINT_MAX);
printf("short: %zu bytes, min: %d, max: %d\n", sizeof(short), SHRT_MIN, SHRT_MAX);
printf("long: %zu bytes, min: %ld, max: %ld\n", sizeof(long), LONG_MIN, LONG_MAX);
printf("long long: %zu bytes, min: %lld, max: %lld\n", sizeof(long long), LLONG_MIN, LLONG_MAX);
return 0;
}
Overflow
Overflow ocorre quando uma operação aritmética produz um valor além dos limites do tipo. Em C, overflow de inteiros com sinal é comportamento indefinido (undefined behavior), ou seja, o programa pode fazer qualquer coisa – desde resultados inesperados até travamentos. Já para tipos unsigned, o overflow é bem definido: o valor é reduzido módulo (2^n), ou seja, ocorre wrap-around.
Por exemplo, adicionar 1 ao maior unsigned int resulta em 0. Já para signed int, o resultado é imprevisível. Para evitar overflow, é importante verificar os limites antes de realizar operações, especialmente em loops ou cálculos críticos.
#include <stdio.h>
#include <limits.h>
int main() {
unsigned int u = UINT_MAX;
printf("u = %u\n", u);
u = u + 1; // wrap-around: 0
printf("u + 1 = %u\n", u);
int s = INT_MAX;
printf("s = %d\n", s);
s = s + 1; // undefined behavior
printf("s + 1 = %d (pode ser negativo ou algo inesperado)\n", s);
return 0;
}
Boas práticas
Ao trabalhar com inteiros, prefira tipos unsigned para valores que nunca são negativos (como índices, contagens). Use size_t para tamanhos e índices de arrays. Evite assumir tamanhos fixos; utilize sizeof e limits.h para portabilidade. Sempre verifique possíveis overflows em operações críticas, especialmente com entradas do usuário.
Exercícios
- Escreva um programa que imprima os limites de
short,int,long,long longe suas versões unsigned usandolimits.h. - Dado
unsigned int x = 0;, o que acontece se você fizerx = x - 1;? Escreva um programa para verificar. - Explique por que o código a seguir pode causar um loop infinito:
for (unsigned int i = 10; i >= 0; i--) { ... }. - Escreva uma função que verifica se a soma de dois
unsigned intcausa overflow, retornando 1 se seguro, 0 se overflow. - Qual a diferença entre
intelong intem um sistema de 64 bits? Usesizeofpara verificar.
#include <stdio.h>
#include <limits.h>
int main() {
printf("short: %d a %d\n", SHRT_MIN, SHRT_MAX);
printf("unsigned short: 0 a %u\n", USHRT_MAX);
printf("int: %d a %d\n", INT_MIN, INT_MAX);
printf("unsigned int: 0 a %u\n", UINT_MAX);
printf("long: %ld a %ld\n", LONG_MIN, LONG_MAX);
printf("unsigned long: 0 a %lu\n", ULONG_MAX);
printf("long long: %lld a %lld\n", LLONG_MIN, LLONG_MAX);
printf("unsigned long long: 0 a %llu\n", ULLONG_MAX);
return 0;
}
Como unsigned int é sem sinal, subtrair 1 de 0 causa wrap-around para o valor máximo (UINT_MAX). O programa:
#include <stdio.h>
#include <limits.h>
int main() {
unsigned int x = 0;
x = x - 1;
printf("x = %u\n", x); // imprime 4294967295 (em sistemas de 32 bits)
return 0;
}
Quando i é 0 e decrementa, o valor se torna UINT_MAX (wrap-around), que é maior ou igual a 0, então a condição continua verdadeira. O loop nunca termina. Para corrigir, use int ou altere a condição para i > 0.
#include <limits.h>
int safe_add(unsigned int a, unsigned int b, unsigned int *result) {
if (a > UINT_MAX - b) {
return 0; // overflow
}
*result = a + b;
return 1;
}
Em muitos sistemas de 64 bits (como Linux), int tem 4 bytes e long tem 8 bytes. Mas em Windows, long também tem 4 bytes. O programa abaixo mostra os tamanhos:
#include <stdio.h>
int main() {
printf("int: %zu bytes\n", sizeof(int));
printf("long: %zu bytes\n", sizeof(long));
return 0;
}