Tipos inteiros em detalhe
Esta aula detalha os tipos inteiros em C, abordando os modificadores signed e unsigned, os especificadores short, long e long long, os tamanhos e limites definidos em limits.h, e o comportamento de overflow. O conteúdo inclui exemplos práticos e exercícios para fixação.
Nesta aula, vamos mergulhar nos tipos inteiros da linguagem C, explorando modificadores que controlam sinal e tamanho. Entender esses detalhes é crucial para escrever código eficiente e evitar bugs sutis relacionados a limites e estouro de valores.
Os tipos inteiros em C não se resumem apenas a int. Com os modificadores signed, unsigned, short, long e long long, podemos ajustar o intervalo de valores e o espaço de memória ocupado. Vamos ver cada um deles em detalhe.
signed e unsigned
Por padrão, tipos inteiros como int e char são signed, ou seja, podem representar valores positivos e negativos. O modificador unsigned indica que a variável só armazenará valores não negativos, dobrando o limite superior positivo em relação ao signed correspondente.
Exemplo: um unsigned int de 32 bits pode armazenar valores de 0 a 4.294.967.295, enquanto um signed int vai de -2.147.483.648 a 2.147.483.647. A representação em complemento de dois é usada para números negativos.
#include <stdio.h>
int main() {
signed int a = -10;
unsigned int b = 10;
// unsigned int c = -10; // Erro: warning ou comportamento indefinido
printf("signed: %d\n", a);
printf("unsigned: %u\n", b);
return 0;
}short, long, long long
Os especificadores short e long alteram o tamanho (em bytes) do tipo inteiro. short geralmente ocupa 2 bytes, long 4 ou 8 bytes (dependendo da arquitetura) e long long pelo menos 8 bytes. O padrão C garante que short ≤ int ≤ long ≤ long long.
Exemplo de declarações:
short int si = 10; // ou apenas "short"
long int li = 100000L; // ou "long"
long long int lli = 10000000000LL; // ou "long long"O sufixo L ou LL pode ser usado em literais para forçar o tipo long ou long long.
Tamanhos e limites (limits.h)
O cabeçalho <limits.h> define constantes que indicam os valores mínimos e máximos para cada tipo inteiro, dependendo da implementação. Por exemplo:
CHAR_BIT: número de bits em um char (geralmente 8)SCHAR_MIN,SCHAR_MAX: limites designed charUCHAR_MAX: máximo deunsigned charINT_MIN,INT_MAX: limites deintUINT_MAX: máximo deunsigned intLONG_MIN,LONG_MAX,ULONG_MAXparalongLLONG_MIN,LLONG_MAX,ULLONG_MAXparalong long
Exemplo de uso:
#include <stdio.h>
#include <limits.h>
int main() {
printf("int max: %d\n", INT_MAX);
printf("int min: %d\n", INT_MIN);
printf("unsigned int max: %u\n", UINT_MAX);
printf("long long max: %lld\n", LLONG_MAX);
return 0;
}Overflow
Overflow ocorre quando uma operação aritmética produz um valor além do intervalo representável pelo tipo. Para tipos signed, o overflow é comportamento indefinido (o programa pode crashar ou produzir resultados inesperados). Para tipos unsigned, o overflow é bem definido: o valor é reduzido módulo (MAX+1), ou seja, ocorre wraparound.
Exemplo de overflow com signed:
#include <stdio.h>
#include <limits.h>
int main() {
int a = INT_MAX;
a = a + 1; // overflow: comportamento indefinido
printf("%d\n", a);
return 0;
}Exemplo de overflow com unsigned (wraparound):
#include <stdio.h>
#include <limits.h>
int main() {
unsigned int u = UINT_MAX;
u = u + 1; // wraps to 0
printf("%u\n", u); // 0
return 0;
}Para evitar overflow, sempre verifique os limites antes de operações, especialmente em entradas do usuário.
Boas práticas
- Use
unsignedpara valores que nunca serão negativos (índices, tamanhos). - Prefira
size_tpara tamanhos de objetos (definido em<stddef.h>). - Evite suposições sobre tamanhos de tipos; use
sizeofe<limits.h>. - Cuidado com conversões implícitas entre signed e unsigned: podem causar bugs.
Referências
- cppreference: Arithmetic types
- cppreference: Limits of integer types
- GNU C Library: Integer Types
- Microsoft C++: Integer Constants
- Wikipedia: C data types
Exercícios
- Escreva um programa que imprima o tamanho em bytes de
short,int,longelong longna sua máquina usandosizeof. - Qual é o valor de
UINT_MAX? Escreva um programa que o exiba. - Explique o que acontece quando somamos 1 a
INT_MAXem umsigned int. Escreva um código que demonstre (cuidado: comportamento indefinido). - Escreva um programa que some 1 a
UINT_MAXe exiba o resultado. Explique o resultado. - Declare uma variável
unsigned long longe atribua o valor 18.446.744.073.709.551.615. Imprima seu valor. (Dica: use o sufixo ULL).
#include <stdio.h>
int main() {
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;
}#include <stdio.h>
#include <limits.h>
int main() {
printf("UINT_MAX = %u\n", UINT_MAX);
return 0;
}O comportamento é indefinido. O programa pode exibir um valor negativo, crashar ou fazer qualquer coisa. Exemplo:
#include <stdio.h>
#include <limits.h>
int main() {
int a = INT_MAX;
a = a + 1;
printf("%d\n", a);
return 0;
}#include <stdio.h>
#include <limits.h>
int main() {
unsigned int u = UINT_MAX;
u = u + 1;
printf("%u\n", u);
return 0;
}O resultado é 0, pois o overflow em unsigned é bem definido: wraparound módulo (UINT_MAX+1).
#include <stdio.h>
int main() {
unsigned long long big = 18446744073709551615ULL;
printf("%llu\n", big);
return 0;
}