Classes de armazenamento
Nesta aula, você aprenderá sobre as classes de armazenamento em C, que definem o escopo, a visibilidade e o tempo de vida das variáveis. Exploraremos auto, register, static e extern, com exemplos práticos e boas práticas para escrever código mais eficiente e organizado.
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
staticpara funções e variáveis que não precisam ser acessadas fora do arquivo, melhorando a modularidade. - Use
externapenas 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
- cppreference - Storage duration
- cppreference - Scope
- GNU C Introduction - Storage Class Specifiers
- Wikipedia - Storage class
- IBM Documentation - Storage classes
- TutorialsPoint - C Storage Classes
Exercícios
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.Qual é a diferença entre
staticeexternpara variáveis globais? Dê um exemplo de uso de cada.✓ Resposta:staticem uma variável global limita sua visibilidade ao arquivo onde está definida, enquantoexterné 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.Escreva uma função em C que utilize uma variável local
staticpara 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; }O que acontece se você tentar usar o operador
&em uma variável declarada comregister? Por quê?✓ Resposta: O código não compila, pois não é permitido tirar o endereço de uma variávelregister. Isso ocorre porque a variável pode ser armazenada em um registrador da CPU, que não possui endereço de memória.Considere dois arquivos:
a.cemain.c. Ema.c, defina uma variável globalint valor = 10;e uma funçãovoid mostrar()que imprime o valor. Emmain.c, useexternpara 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; }