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

  1. Escreva um programa que imprima os limites de short, int, long, long long e suas versões unsigned usando limits.h.
  2. ✓ Resposta:
    #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;
    }
  3. Dado unsigned int x = 0;, o que acontece se você fizer x = x - 1;? Escreva um programa para verificar.
  4. ✓ Resposta:

    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;
    }
  5. Explique por que o código a seguir pode causar um loop infinito: for (unsigned int i = 10; i >= 0; i--) { ... }.
  6. ✓ Resposta:

    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.

  7. Escreva uma função que verifica se a soma de dois unsigned int causa overflow, retornando 1 se seguro, 0 se overflow.
  8. ✓ Resposta:
    #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;
    }
  9. Qual a diferença entre int e long int em um sistema de 64 bits? Use sizeof para verificar.
  10. ✓ Resposta:

    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;
    }

Referências