Duas perguntas sobre toda variável
Toda variável em C tem duas propriedades separadas, e confundi-las é a origem da maioria das surpresas desta página:
- Escopo - onde o nome pode ser usado. Uma questão de tempo de compilação.
- Tempo de vida - por quanto tempo o armazenamento existe. Uma questão de tempo de execução.
Normalmente os dois andam juntos (uma local comum é visível no seu bloco e vive exatamente esse tempo), mas o static os desacopla, e é por isso que ele é a palavra-chave que vale entender aqui.
Escopo de bloco
Uma variável declarada dentro de chaves é visível da sua declaração até a chave de fechamento, e em nenhum outro lugar.
O bloco interno enxerga outer, porque está aninhado dentro dele. O bloco externo não enxerga inner. Isso vale para todo par de chaves - corpos de função, corpos de if, corpos de laço, ou um bloco solto como o acima.
Os laços deixam isso concreto. Um contador declarado no cabeçalho de um for pertence ao laço:
for (int i = 0; i < 5; i++) {
/* i vive aqui */
}
/* i nao existe aqui */
O que é exatamente o que você quer: dois laços na mesma função podem usar i cada um sem interferência, e o contador não pode ser lido por acidente depois do laço. Se você precisa do valor depois - o índice em que uma busca parou - declare-o antes do laço.
Parâmetros de função têm o escopo do corpo da função, então se comportam como locais declaradas no topo dele.
O C99 permite uma declaração em qualquer ponto de um bloco, não só no topo. Declare variáveis onde você precisa delas pela primeira vez; uma variável com escopo curto é uma variável com menos maneiras de estar errada.
Tempo de vida: armazenamento automático
Uma local comum tem armazenamento automático: ela passa a existir quando o controle entra no seu bloco e é destruída quando o controle sai. Sua memória vive na pilha.
Isso imprime 1 três vezes. Cada chamada ganha um count novinho, inicializado com 0, e descartado no retorno. É também por isso que cada quadro de uma função recursiva tem a própria cópia de cada variável local.
Duas consequências que vale dizer com todas as letras. Uma local não inicializada contém lixo, não zero - os bytes que estavam na pilha. E devolver um ponteiro para uma local é comportamento indefinido, porque o armazenamento some no instante em que a função retorna:
/* BUG: buf nao existe depois do return */
char *broken(void) {
char buf[64] = "hello";
return buf;
}
Locais static: memória entre chamadas
Coloque static numa local e o tempo de vida dela passa a ser o programa inteiro, enquanto o escopo continua exatamente o mesmo.
Agora o contador imprime 1, 2, 3. O = 0 executa uma vez, antes de main começar - não a cada chamada.
Três regras para locais static:
- O inicializador precisa ser uma expressão constante, porque é aplicado no início do programa, não em tempo de execução.
- Sem inicializador, uma
staticé inicializada com zero (ao contrário de uma local automática, que guarda lixo). - A variável continua privada da função. Nenhum outro código consegue nomeá-la.
Esse último ponto é todo o atrativo: uma função pode lembrar algo entre chamadas sem expor uma global que qualquer coisa poderia modificar. Use isso para contadores de chamadas, tabelas de consulta construídas no primeiro uso e sinalizadores de inicialização única.
O custo é que a função deixa de ser um mapeamento puro de entrada para saída - os mesmos argumentos podem dar respostas diferentes - o que dificulta o teste e torna inseguro chamá-la de várias threads ao mesmo tempo sem proteção. Use locais static deliberadamente, não por conveniência.
Escopo de arquivo: globais
Uma variável declarada fora de todas as funções tem escopo de arquivo. Ela é visível da sua declaração até o fim do arquivo, em todas as funções abaixo dela, e seu tempo de vida é o programa inteiro.
Globais são inicializadas com zero por padrão, então totalOperations começa em 0 mesmo sem o = 0.
Por padrão uma global também tem ligação externa: outros arquivos .c do mesmo programa podem alcançá-la declarando-a como extern:
/* em stats.c */
int totalOperations = 0; /* a definicao - exatamente uma no programa */
/* em main.c */
extern int totalOperations; /* uma declaracao: "ela existe em algum lugar" */
Coloque a declaração extern num arquivo de cabeçalho para que todos os usuários vejam a mesma. Note a assimetria: a declaração extern diz que a variável existe, e exatamente um arquivo .c precisa de fato defini-la.
Por que globais mordem
Globais são a maneira mais fácil de compartilhar dados e a maneira mais fácil de criar bugs que você não consegue localizar. Quatro problemas concretos:
Qualquer coisa pode alterá-las. Quando totalOperations guarda um valor errado, o culpado é qualquer linha de qualquer arquivo. Com um parâmetro, os suspeitos são os pontos de chamada que você consegue ver.
Funções ficam impossíveis de testar. Uma função que lê uma global não pode ser chamada isoladamente; você precisa preparar o estado global antes e lembrar de restaurá-lo depois.
O nome vale para todo o programa. Uma global chamada count ou buffer acabará colidindo com a de outra pessoa.
Seus inicializadores precisam ser constantes de tempo de compilação. Em C, uma global só pode ser inicializada com uma expressão constante - int limit = readConfig(); não compila, e int b = a * 2; em escopo de arquivo também não. Qualquer coisa calculada precisa ser atribuída em tempo de execução por alguma função de preparação, e todo arquivo que toca a global antes de essa chamada rodar enxerga um 0 silencioso.
As alternativas quase sempre estão disponíveis: passe valores como parâmetros, devolva resultados e agrupe estado relacionado numa struct que é passada explicitamente. Existem exceções genuínas - um objeto de configuração para todo o programa, um manipulador de log - e mesmo essas ficam melhores como static em escopo de arquivo com funções de acesso, que é a próxima seção.
static em escopo de arquivo: privacidade
Numa variável global ou numa função, static significa algo completamente diferente do que significa numa local: ligação interna. O nome vira privado do seu próprio arquivo .c e não pode ser alcançado de nenhum outro.
/* counter.c */
#include "counter.h"
static int count = 0; /* privado deste arquivo - nenhum outro o toca */
static void validate(void) { /* um auxiliar privado, fora da interface */
if (count < 0) count = 0;
}
void increment(void) { /* publico: declarado em counter.h */
count++;
validate();
}
int getCount(void) { /* publico */
return count;
}
Outro arquivo pode chamar increment e getCount, mas não enxerga count e não pode chamar validate - o ligador não vai resolver esses nomes. Esse é o sistema de módulos do C, tal como ele é: um cabeçalho declarando as funções públicas, e static em todo o resto.
Dois benefícios práticos além da arrumação. Dois arquivos podem ter cada um a sua static void validate(void) sem colisão, o que não poderiam se as funções fossem públicas. E o compilador sabe que uma função static não tem chamadores fora do arquivo, então pode inliná-la mais agressivamente ou avisar que ela não é usada.
Ou seja, a palavra-chave tem dois significados distintos, decididos por onde ela aparece:
static numa variavel LOCAL -> o tempo de vida vira permanente (escopo inalterado)
static em escopo de ARQUIVO -> a ligacao vira interna (tempo de vida inalterado)
Sombreamento
Declarar num escopo interno um nome que já existe num escopo externo sombreia o nome externo: dentro do escopo interno, o nome se refere à nova variável e a externa fica inalcançável.
Isso é legal, e ocasionalmente intencional. Com mais frequência é um acidente, e caro: uma função que deveria atualizar uma global atualiza em vez disso uma local de mesmo nome, e a global nunca muda. O mesmo acontece quando uma local sombreia um parâmetro, e a atribuição que você queria fazer na entrada não vai a lugar nenhum.
O gcc -Wshadow reporta todos os casos. Ele não está em -Wall, então ligue-o explicitamente:
gcc -Wall -Wextra -Wshadow programa.c -o programa
O hábito que evita o problema por completo é nomear pelo papel em vez de pelo tipo: totalScore e itemScore não podem se sombrear, enquanto duas variáveis ambas chamadas n acabarão se sombreando.
Uma referência rápida
declarada dentro de um bloco escopo de bloco, tempo de vida automatico, lixo se nao inicializada
parametro de funcao escopo de bloco do corpo da funcao, uma copia do argumento
static dentro de uma funcao escopo de bloco, tempo de vida do PROGRAMA, zerada, mantida entre chamadas
declarada fora de toda funcao escopo de arquivo, tempo de vida do programa, zerada, visivel a outros arquivos
static fora de toda funcao escopo de arquivo, tempo de vida do programa, PRIVADA deste arquivo .c
declaracao extern nomeia uma variavel definida em outro arquivo
Perguntas frequentes
O que é escopo em C?
É a região do código onde um nome é visível. Uma variável declarada dentro de um bloco (um par de chaves) só é visível naquele bloco - isso é escopo de bloco. Uma declarada fora de todas as funções tem escopo de arquivo e é visível da sua declaração até o fim do arquivo.
O que o static faz com uma variável local em C?
Ele muda o tempo de vida da variável sem mudar o escopo dela. Uma local static é criada uma vez, inicializada uma vez e mantém o valor entre chamadas, mas continua visível apenas dentro da sua função. É a forma de dar memória a uma função sem usar uma global.
Qual a diferença entre static e global em C?
Uma global comum é visível para todos os arquivos do programa, que podem alcançá-la com extern. Uma variável ou função static em escopo de arquivo é privada daquele único arquivo .c - outros arquivos não conseguem se ligar a ela. static em escopo de arquivo é sobre esconder; static numa local é sobre tempo de vida.
Por que variáveis globais são ruins em C?
Qualquer função pode alterá-las, então um bug pode ser causado de qualquer ponto do programa; funções que as leem não podem ser testadas isoladamente; e o nome ocupa o programa inteiro, convidando a colisões. Passe valores como parâmetros e devolva resultados; e onde o estado compartilhado for realmente necessário, torne-o static em escopo de arquivo, para que só um arquivo possa tocá-lo.