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 shortintlonglong 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 de signed char
  • UCHAR_MAX: máximo de unsigned char
  • INT_MIN, INT_MAX: limites de int
  • UINT_MAX: máximo de unsigned int
  • LONG_MIN, LONG_MAX, ULONG_MAX para long
  • LLONG_MIN, LLONG_MAX, ULLONG_MAX para long 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 unsigned para valores que nunca serão negativos (índices, tamanhos).
  • Prefira size_t para tamanhos de objetos (definido em <stddef.h>).
  • Evite suposições sobre tamanhos de tipos; use sizeof e <limits.h>.
  • Cuidado com conversões implícitas entre signed e unsigned: podem causar bugs.

Referências

Exercícios

  1. Escreva um programa que imprima o tamanho em bytes de short, int, long e long long na sua máquina usando sizeof.
  2. ✓ Resposta:
    #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;
    }
  3. Qual é o valor de UINT_MAX? Escreva um programa que o exiba.
  4. ✓ Resposta:
    #include <stdio.h>
    #include <limits.h>
    
    int main() {
        printf("UINT_MAX = %u\n", UINT_MAX);
        return 0;
    }
  5. Explique o que acontece quando somamos 1 a INT_MAX em um signed int. Escreva um código que demonstre (cuidado: comportamento indefinido).
  6. ✓ Resposta:

    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;
    }
  7. Escreva um programa que some 1 a UINT_MAX e exiba o resultado. Explique o resultado.
  8. ✓ Resposta:
    #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).

  9. Declare uma variável unsigned long long e atribua o valor 18.446.744.073.709.551.615. Imprima seu valor. (Dica: use o sufixo ULL).
  10. ✓ Resposta:
    #include <stdio.h>
    
    int main() {
        unsigned long long big = 18446744073709551615ULL;
        printf("%llu\n", big);
        return 0;
    }