As classes de armazenamento em C são um conceito fundamental para controlar como, onde e por quanto tempo as variáveis são armazenadas e acessíveis. Elas determinam o escopo (onde a variável pode ser usada), a duração (quando a memória é alocada e liberada) e o vínculo (se a variável é visível apenas no arquivo atual ou em outros arquivos). Compreender essas classes é essencial para escrever programas eficientes e evitar erros sutis de lógica.

Nesta aula, vamos mergulhar nas quatro classes de armazenamento principais: auto, register, static e extern. Você verá como cada uma afeta o comportamento das variáveis e aprenderá a escolher a classe adequada para cada situação.

auto e register

A classe auto é a padrão para variáveis locais. Ela indica que a variável é automática, ou seja, ela é criada quando o bloco onde está declarada é executado e destruída quando o bloco termina. Na prática, podemos omitir a palavra-chave auto porque ela é assumida por padrão para variáveis locais. Por exemplo:

void funcao() {
    auto int x = 10; // equivalente a int x = 10;
    // x é criada aqui e destruída ao sair da função
}

A classe register é uma dica para o compilador de que a variável será usada com frequência e que, se possível, deve ser armazenada em um registrador da CPU, em vez da memória RAM. Isso pode melhorar o desempenho em loops intensivos. No entanto, o compilador pode ignorar essa sugestão. Além disso, não se pode obter o endereço de uma variável register com o operador &, pois ela pode não ter um endereço de memória.

#include <stdio.h>

int main() {
    register int i;
    for (i = 0; i < 1000000; i++) {
        // algum processamento
    }
    // printf("%p", &i); // ERRO: não é permitido tirar endereço de register
    return 0;
}

Na prática, o uso de register é raro, pois os compiladores modernos são bons em otimização e decidem automaticamente onde colocar as variáveis. Em C11, a palavra-chave register ainda é permitida, mas é apenas uma dica.

static

A classe static tem dois usos principais: para variáveis locais e para variáveis globais (ou funções). Quando usada em uma variável local, ela altera o tempo de vida da variável: em vez de ser criada e destruída a cada chamada da função, ela é inicializada apenas uma vez e mantém seu valor entre chamadas. O escopo continua limitado ao bloco onde foi declarada, mas a variável existe por toda a execução do programa.

#include <stdio.h>

void contador() {
    static int count = 0; // inicializada apenas uma vez
    count++;
    printf("Chamada %d\n", count);
}

int main() {
    contador(); // Chamada 1
    contador(); // Chamada 2
    contador(); // Chamada 3
    return 0;
}

Quando usada em uma variável global ou em uma função, static restringe a visibilidade para o arquivo onde é declarada. Isso é útil para criar variáveis ou funções internas que não devem ser acessadas por outros arquivos, promovendo encapsulamento.

// arquivo: modulo.c
static int variavel_interna = 42; // visível apenas neste arquivo

static void funcao_interna() {
    // usada apenas neste arquivo
}

// arquivo: main.c
int main() {
    // não pode acessar variavel_interna ou funcao_interna
    return 0;
}

Uma variável static global tem tempo de vida igual ao programa, mas escopo restrito ao arquivo. Isso é uma boa prática para evitar conflitos de nomes e melhorar a modularidade.

extern

A classe extern é usada para declarar uma variável ou função que é definida em outro arquivo ou em outro lugar do código. Ela estende a visibilidade da variável para todos os arquivos que a declaram como extern. A definição real (com alocação de memória) acontece em um único lugar, geralmente em um arquivo de implementação.

Por exemplo, suponha que temos um arquivo globals.h com uma declaração:

// globals.h
#ifndef GLOBALS_H
#define GLOBALS_H

extern int contador_global; // declaração, não definição

#endif

E um arquivo globals.c com a definição:

// globals.c
#include "globals.h"
int contador_global = 0; // definição e inicialização

Em outro arquivo main.c, podemos usar a variável:

// main.c
#include <stdio.h>
#include "globals.h"

int main() {
    printf("%d\n", contador_global); // acessa a variável definida em globals.c
    contador_global++;
    return 0;
}

É importante distinguir declaração de definição: extern apenas declara a existência, não aloca memória. A definição deve ocorrer em um único lugar, sem extern.

Tempo de vida e visibilidade

O tempo de vida de uma variável é o período durante o qual ela ocupa memória. Temos dois tipos: automático (para variáveis locais sem static) e estático (para variáveis globais e locais com static). Variáveis automáticas são criadas na entrada do bloco e destruídas na saída. Variáveis estáticas são criadas no início do programa e destruídas quando o programa termina.

A visibilidade (ou escopo) é a região do código onde a variável pode ser referenciada. O escopo pode ser bloco (entre chaves), função, arquivo ou programa inteiro, dependendo da classe de armazenamento e do local de declaração.

Para resumir:

  • auto: escopo de bloco, vida automática, visível apenas no bloco.
  • register: igual a auto, mas com dica de otimização.
  • static local: escopo de bloco, vida estática, visível apenas no bloco.
  • static global: escopo de arquivo, vida estática, visível apenas no arquivo.
  • extern: pode ter escopo de bloco ou global, vida estática (se for definida em outro lugar), visível conforme a declaração.

A tabela a seguir ilustra as combinações:

// Exemplo de diferentes classes de armazenamento
#include <stdio.h>

int global_auto = 1; // variável global, vida estática, visível em todo o programa
static int global_static = 2; // variável global estática, visível apenas neste arquivo

void funcao() {
    int local_auto = 3; // vida automática, visível apenas na função
    static int local_static = 4; // vida estática, visível apenas na função
    printf("%d %d %d %d\n", global_auto, global_static, local_auto, local_static);
}

int main() {
    funcao();
    funcao();
    return 0;
}

É crucial entender essas diferenças para evitar erros como usar uma variável antes de ser inicializada ou tentar acessar uma variável fora do seu escopo.

Boas práticas e observações finais

Ao usar classes de armazenamento, siga estas boas práticas:

  • Evite variáveis globais sempre que possível; prefira passar valores por parâmetros.
  • Use static para funções e variáveis que não precisam ser acessadas fora do arquivo, melhorando a modularidade.
  • Use extern apenas quando necessário, como em projetos com múltiplos arquivos.
  • Não abuse de register; confie nas otimizações do compilador.
  • Inicialize sempre as variáveis, especialmente as estáticas, para evitar valores indefinidos.

Referências

Exercícios

  1. Explique a diferença entre tempo de vida automático e estático. Dê um exemplo de cada.

    ✓ Resposta: Tempo de vida automático: a variável é criada quando o bloco é executado e destruída quando o bloco termina. Exemplo: int x = 10; dentro de uma função. Tempo de vida estático: a variável é criada no início do programa e destruída quando o programa termina. Exemplo: static int contador; dentro de uma função, que mantém seu valor entre chamadas.
  2. Qual é a diferença entre static e extern para variáveis globais? Dê um exemplo de uso de cada.

    ✓ Resposta: static em uma variável global limita sua visibilidade ao arquivo onde está definida, enquanto extern é usado para declarar uma variável que está definida em outro arquivo, permitindo acesso a partir do arquivo atual. Exemplo: static int variavel_arquivo; em um arquivo .c; extern int variavel_global; em um cabeçalho para acessar uma variável definida em outro arquivo.
  3. Escreva uma função em C que utilize uma variável local static para contar quantas vezes foi chamada. Teste chamando a função três vezes e imprima o valor.

    ✓ Resposta:
    #include <stdio.h>
    
    void contador() {
        static int count = 0;
        count++;
        printf("Chamada %d\n", count);
    }
    
    int main() {
        contador();
        contador();
        contador();
        return 0;
    }
    
  4. O que acontece se você tentar usar o operador & em uma variável declarada com register? Por quê?

    ✓ Resposta: O código não compila, pois não é permitido tirar o endereço de uma variável register. Isso ocorre porque a variável pode ser armazenada em um registrador da CPU, que não possui endereço de memória.
  5. Considere dois arquivos: a.c e main.c. Em a.c, defina uma variável global int valor = 10; e uma função void mostrar() que imprime o valor. Em main.c, use extern para acessar a variável e chamar a função. Escreva o código completo.

    ✓ Resposta:
    // a.c
    #include <stdio.h>
    int valor = 10;
    void mostrar() {
        printf("%d\n", valor);
    }
    
    // main.c
    #include <stdio.h>
    extern int valor;
    extern void mostrar();
    
    int main() {
        mostrar();
        return 0;
    }