C Escopo e Variáveis static
Toda variável tem um território: um pedaço do código onde o nome dela existe. Fora dele, o compilador simplesmente não a conhece. Esse território se chama escopo. Nesta aula você vai entender por que a variável de uma função não aparece em outra, por que o i do for some depois do laço, o que acontece quando dois nomes iguais se sobrepõem e como uma variável static consegue lembrar o seu valor de uma chamada para a outra.
- O que é escopo (onde o nome existe) e tempo de vida (quando a variável existe).
- Variáveis locais: criadas dentro de uma função, invisíveis para as outras.
- Variáveis de bloco: as que nascem dentro de
{ }, como oidofor. - Variáveis globais: visíveis no arquivo inteiro, e por que elas causam problemas.
- Sombreamento: um nome que esconde outro, e o aviso
-Wshadow. staticdentro de uma função: a variável que guarda o valor entre chamadas.
Todos os programas desta aula foram compilados com o gcc 15.2 na máquina fictícia da Ana, na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa.
A ideia: os cômodos da casa
Imagine a casa da Ana. Cada cômodo tem as suas coisas: a panela fica na cozinha, a escova de dentes no banheiro. Quem está no quarto não alcança a panela sem ir até a cozinha. E se a cozinha e a churrasqueira têm, cada uma, uma "faca", são duas facas diferentes, com o mesmo nome.
| Na casa | Em C |
|---|---|
| Um cômodo | Uma função, ou um bloco { } dentro dela |
| A panela da cozinha | Uma variável local: só existe dentro daquela função |
| Uma gaveta dentro da cozinha | Um bloco (if, for): o que está ali só existe ali dentro |
| O quadro de avisos no corredor, que todo mundo vê e rabisca | Uma variável global |
| A agenda que fica na gaveta da cozinha e não é jogada fora | Uma variável static dentro da função |
1. Variáveis locais: só existem dentro da função
Uma variável declarada dentro de uma função é local a ela. O main não enxerga a variável de outra função:
#include <stdio.h>
void vender(void) {
int vendidos = 3;
printf("Vendidos hoje: %d\n", vendidos);
}
int main(void) {
vender();
printf("No main: %d\n", vendidos);
return 0;
}
$ gcc -std=c23 -Wall -Wextra local1.c -o local1
local1.c: In function ‘main’:
local1.c:10:29: error: ‘vendidos’ undeclared (first use in this function)
10 | printf("No main: %d\n", vendidos);
| ^~~~~~~~
local1.c:10:29: note: each undeclared identifier is reported only once for each function it appears in
- ‘vendidos’ undeclared (first use in this function)
- "
vendidosnão declarada (primeiro uso nesta função)". Para omain, esse nome não existe: ele pertence avender. - note: each undeclared identifier is reported only once…
- "Cada identificador não declarado é informado uma vez só por função". Se
vendidosaparecesse mais vezes nomain, o gcc não repetiria o erro.
No editor, o Clang diz use of undeclared identifier 'vendidos'. Para o main receber o valor, a função tem de devolvê-lo (Valor de Retorno). Os parâmetros também são variáveis locais: só existem dentro da sua função (Parâmetros).
O lado bom: nomes iguais não se misturam
Como cada função tem o seu território, duas funções podem usar o mesmo nome sem confusão:
#include <stdio.h>
void manha(void) {
int vendas = 4;
printf("Vendas da manhã: %d\n", vendas);
}
void tarde(void) {
int vendas = 7;
printf("Vendas da tarde: %d\n", vendas);
}
int main(void) {
manha();
tarde();
return 0;
}
$ gcc -std=c23 -Wall -Wextra mesmonome2.c -o mesmonome2 $ ./mesmonome2 Vendas da manhã: 4 Vendas da tarde: 7
São duas variáveis vendas, em lugares diferentes da memória. Isso é ótimo: quem escreve tarde não precisa saber quais nomes manha usou.
Tempo de vida: a local nasce e morre a cada chamada
Além de onde o nome vale (escopo), existe quando a variável existe (tempo de vida). Uma variável local é criada quando a função começa e destruída quando ela termina. Na próxima chamada, ela nasce de novo, do zero:
#include <stdio.h>
void tocar_campainha(void) {
int vezes = 0;
vezes = vezes + 1;
printf("Plim! (campainha tocada %d vez(es))\n", vezes);
}
int main(void) {
tocar_campainha();
tocar_campainha();
tocar_campainha();
return 0;
}
$ gcc -std=c23 -Wall -Wextra semstatic.c -o semstatic $ ./semstatic Plim! (campainha tocada 1 vez(es)) Plim! (campainha tocada 1 vez(es)) Plim! (campainha tocada 1 vez(es)) « a contagem nunca passa de 1
A Ana queria contar quantas vezes a campainha tocou, mas vezes é recriada valendo 0 a cada chamada. A seção 5 mostra o conserto.
2. Variáveis de bloco
Um bloco é qualquer trecho entre { e }: o corpo de um if, de um for, de um while. Uma variável declarada dentro de um bloco só existe dentro dele. Isso inclui a variável declarada no cabeçalho do for:
#include <stdio.h>
int main(void) {
for (int i = 1; i <= 3; i++) {
int dobro = i * 2;
printf("%d ", dobro);
}
printf("\nÚltimo i: %d\n", i);
return 0;
}
$ gcc -std=c23 -Wall -Wextra bloco.c -o bloco
bloco.c: In function ‘main’:
bloco.c:8:32: error: ‘i’ undeclared (first use in this function)
8 | printf("\nÚltimo i: %d\n", i);
| ^
bloco.c:8:32: note: each undeclared identifier is reported only once for each function it appears in
O i nasceu no for (int i = 1; …) e morreu no } que fecha o laço. O mesmo vale para dobro, que é recriada a cada volta. Se você precisa do valor depois do laço, declare a variável antes dele:
int i = 1;
for (; i <= 3; i++) { // o for usa o i que já existe
printf("%d ", i * 2);
}
printf("\nÚltimo i: %d\n", i); // mostra 4
for C99
Declarar a variável dentro do for (int i = …) chegou no C99. Em código muito antigo (C89), você vai ver int i; lá no começo da função e for (i = 0; …). Prefira o jeito novo: quanto menor o território de uma variável, menos chance de alguém mexer nela por engano.
Experimente: rode e leia o erro. Depois conserte de dois jeitos: (1) apague a linha do Último i; (2) declare int i = 1; antes do laço e use for (; i <= 3; i++). Que valor o Último i mostra, e por quê?
#include <stdio.h>
int main(void) {
for (int i = 1; i <= 3; i++) {
int dobro = i * 2;
printf("%d ", dobro);
}
printf("\nÚltimo i: %d\n", i);
return 0;
}
3. Variáveis globais (e por que evitar)
Uma variável declarada fora de todas as funções é global: vale do ponto da declaração até o fim do arquivo, em todas as funções, e existe durante o programa inteiro.
#include <stdio.h>
int clientes = 0;
void atender(void) {
clientes = clientes + 1;
printf("Atendendo o cliente %d\n", clientes);
}
int main(void) {
atender();
atender();
atender();
printf("Total de clientes: %d\n", clientes);
return 0;
}
$ gcc -std=c23 -Wall -Wextra global.c -o global $ ./global Atendendo o cliente 1 Atendendo o cliente 2 Atendendo o cliente 3 Total de clientes: 3
Funciona: atender e main enxergam a mesma clientes, e ela não some entre as chamadas. Uma diferença importante: uma global sem valor inicial começa valendo zero (as locais, não: começam com lixo, veja Variáveis):
#include <stdio.h>
int pontos;
int main(void) {
static int vidas;
printf("pontos = %d, vidas = %d\n", pontos, vidas);
return 0;
}
$ gcc -std=c23 -Wall -Wextra globalzero.c -o globalzero $ ./globalzero pontos = 0, vidas = 0
(O static int vidas também começa em zero; ele é o assunto da seção 5.)
O problema das globais
Uma global é o quadro de avisos do corredor: qualquer função pode rabiscar nele, e quando algo dá errado ninguém sabe quem foi. Veja um erro clássico: duas funções usam a mesma global i como contador de laço.
#include <stdio.h>
int i;
void estrelas(void) {
for (i = 0; i < 5; i++) {
printf("*");
}
printf("\n");
}
int main(void) {
for (i = 0; i < 3; i++) {
estrelas();
}
return 0;
}
$ gcc -std=c23 -Wall -Wextra globali.c -o globali $ ./globali ***** « a Ana queria 3 linhas de estrelas; saiu 1
- O
maincomeça o laço comi = 0e chamaestrelas. estrelasusa o mesmoi: conta de 0 a 5 e termina comivalendo 5.- De volta ao
main, oi++fazivaler 6. Como 6 < 3 é falso, o laço acaba depois de uma volta só.
Nenhum aviso: para o compilador, está tudo certo. Com for (int i = 0; …) em cada função (duas locais independentes), o programa mostraria as três linhas. É por isso que a regra geral é: prefira variáveis locais e parâmetros; use global só quando o valor é realmente do programa inteiro, e mesmo assim com cuidado. A palavra static aplicada a uma global (para escondê-la dos outros arquivos) e o extern ficam para static e extern.
4. Sombreamento: quando um nome esconde outro
Num bloco de dentro, você pode declarar uma variável com o mesmo nome de uma de fora. A de dentro esconde (faz sombra sobre) a de fora, até o fim do bloco:
#include <stdio.h>
int total = 100;
int main(void) {
int total = 5;
printf("total = %d\n", total);
{
int total = 7;
printf("dentro do bloco: total = %d\n", total);
}
printf("depois do bloco: total = %d\n", total);
return 0;
}
$ gcc -std=c23 -Wall -Wextra sombra.c -o sombra $ ./sombra total = 5 dentro do bloco: total = 7 depois do bloco: total = 5
Existem três variáveis total: a global (100), a do main (5) e a do bloco (7). Em cada ponto, vale a mais próxima. A global nunca aparece: está escondida o tempo todo. O -Wall -Wextra não disse nada, mas existe uma opção que avisa, o -Wshadow ("sombra"):
$ gcc -std=c23 -Wall -Wextra -Wshadow sombra.c -o sombra
sombra.c: In function ‘main’:
sombra.c:6:9: warning: declaration of ‘total’ shadows a global declaration [-Wshadow]
6 | int total = 5;
| ^~~~~
sombra.c:3:5: note: shadowed declaration is here
3 | int total = 100;
| ^~~~~
sombra.c:9:13: warning: declaration of ‘total’ shadows a previous local [-Wshadow]
9 | int total = 7;
| ^~~~~
sombra.c:6:9: note: shadowed declaration is here
6 | int total = 5;
| ^~~~~
- declaration of ‘total’ shadows a global declaration
- "A declaração de
totalfaz sombra sobre uma declaração global". - shadows a previous local
- "Faz sombra sobre uma local anterior": a do bloco esconde a do
main. - note: shadowed declaration is here
- "A declaração escondida está aqui".
O sombreamento é permitido, mas quase sempre é um engano: você acha que está mudando a variável de fora e está mudando outra. O Clang, com a mesma opção, escreve declaration shadows a variable in the global scope e declaration shadows a local variable. Use nomes diferentes e, em projetos seus, ligue o -Wshadow (aula Avisos).
5. static dentro da função: a variável que lembra
Volte à campainha da seção 1: a contagem nunca passava de 1. Com uma palavra a mais, static, a variável passa a viver o programa inteiro, mas continua visível só dentro da função:
#include <stdio.h>
void tocar_campainha(void) {
static int vezes = 0;
vezes = vezes + 1;
printf("Plim! (campainha tocada %d vez(es))\n", vezes);
}
int main(void) {
tocar_campainha();
tocar_campainha();
tocar_campainha();
return 0;
}
$ gcc -std=c23 -Wall -Wextra contador.c -o contador $ ./contador Plim! (campainha tocada 1 vez(es)) Plim! (campainha tocada 2 vez(es)) Plim! (campainha tocada 3 vez(es))
- static int vezes = 0;
static("estático", que fica parado) muda o tempo de vida: a variável é criada uma vez, quando o programa começa, e só é destruída quando ele termina.- = 0 (roda uma vez só)
- A inicialização acontece uma única vez, antes da primeira chamada. Nas chamadas seguintes, essa linha não zera nada:
vezescontinua com o valor que tinha. - vezes = vezes + 1;
- Soma 1 ao valor guardado da chamada anterior: 1, 2, 3…
- Escopo continua local
- O
mainnão enxergavezes(daria‘vezes’ undeclared). Sótocar_campainhapode mexer nela: é a vantagem sobre uma global.
Compare as três escolhas para a contagem:
| Local comum | static local | Global | |
|---|---|---|---|
| Quem enxerga | Só a função | Só a função | Todas as funções do arquivo |
| Quando existe | Durante cada chamada | O programa inteiro | O programa inteiro |
| Guarda o valor entre chamadas | Não | Sim | Sim |
| Valor inicial se você não der um | Lixo | Zero | Zero |
| Contagem da campainha | Sempre 1 | 1, 2, 3 (certo, e protegido) | 1, 2, 3 (certo, mas qualquer um pode estragar) |
O valor inicial de uma static tem de ser constante
Como a inicialização acontece antes de o programa começar a rodar, ela não pode depender de uma conta feita em tempo de execução, como o resultado de uma função:
#include <stdio.h>
int ler(void) { return 5; }
void f(void) {
static int x = ler();
printf("%d\n", x);
}
int main(void) {
f();
return 0;
}
$ gcc -std=c23 -Wall -Wextra staticvar.c -o staticvar
staticvar.c: In function ‘f’:
staticvar.c:6:20: error: initializer element is not constant
6 | static int x = ler();
| ^~~
Initializer element is not constant = "o valor inicial não é constante". Use um número (ou uma conta só com números) na declaração e, se precisar, atribua o valor calculado depois. No editor: initializer element is not a compile-time constant. As globais seguem a mesma regra.
Missão: a fila da lojinha distribui senhas, mas toda chamada devolve a senha 1. Conserte a função proxima_senha com uma palavra, para que ela devolva 1, 2, 3…
Senha 1 Senha 2 Senha 3
#include <stdio.h>
int proxima_senha(void) {
int senha = 0;
senha++;
return senha;
}
int main(void) {
printf("Senha %d\n", proxima_senha());
printf("Senha %d\n", proxima_senha());
printf("Senha %d\n", proxima_senha());
return 0;
}
#include <stdio.h>
int proxima_senha(void) {
static int senha = 0;
senha++;
return senha;
}
int main(void) {
printf("Senha %d\n", proxima_senha());
printf("Senha %d\n", proxima_senha());
printf("Senha %d\n", proxima_senha());
return 0;
}
Qual usar
| Situação | Use |
|---|---|
| Um valor usado só dentro de uma função, numa chamada | Variável local (a escolha padrão) |
Um valor usado só dentro de um laço ou if | Variável de bloco, declarada ali dentro |
| Um valor que vem de quem chama | Parâmetro (Parâmetros) |
| Um resultado que vai para quem chamou | return (Valor de Retorno) |
| Uma função que precisa lembrar algo entre chamadas (contador, "já fiz isso?") | static local |
| Um valor que o programa inteiro precisa e que quase nunca muda | Global, de preferência const (Constantes) |
| Dados que várias funções mudam | Evite global: passe por parâmetro (com ponteiro, Passar por Ponteiro) ou junte numa struct (Structs) |
Quando dá errado
error: ‘x’ undeclared (first use in this function)
O que significa: nesse ponto, o nome não existe. Ou a variável é de outra função, ou de um bloco que já fechou (como o i do for), ou foi declarada mais abaixo, ou o nome está escrito diferente. No editor: use of undeclared identifier 'x'.
Como consertar: para usar o valor em outra função, passe por parâmetro ou devolva com return. Para usar depois de um bloco, declare a variável antes dele.
O que significa: provavelmente um sombreamento: você declarou de novo (int total = …) num bloco de dentro e mudou essa outra variável. Ou mudou um parâmetro, que é uma cópia.
Como consertar: compile com -Wshadow; para mudar a variável existente, escreva sem o tipo (total = 7;).
warning: declaration of ‘x’ shadows a previous local / a global declaration
O que significa: (só com -Wshadow) um nome de dentro esconde outro de fora.
Como consertar: dê outro nome a uma das variáveis.
O que significa: a variável é local comum: nasce de novo, com o valor inicial, em toda chamada.
Como consertar: declare-a como static dentro da função.
error: initializer element is not constant
O que significa: o valor inicial de uma static (ou de uma global) depende de algo calculado com o programa rodando, como uma chamada de função ou outra variável.
Como consertar: inicialize com um número e faça a atribuição depois, dentro do código.
O que significa: pode ser uma global compartilhada, como o i do globali.c: outra função mudou o valor no meio do laço.
Como consertar: use for (int i = 0; …), com uma variável de bloco em cada laço.
Referência rápida
| Onde é declarada | Escopo (onde o nome vale) | Tempo de vida | Valor inicial padrão |
|---|---|---|---|
| Dentro de uma função | Da declaração até o } da função | Uma chamada | Lixo |
Dentro de um bloco { } ou no for (int i…) | Até o } do bloco | Uma passagem pelo bloco | Lixo |
| Parâmetro | A função inteira | Uma chamada | O argumento |
static dentro de uma função | Até o } da função | O programa inteiro | Zero |
| Fora de todas as funções (global) | Da declaração até o fim do arquivo | O programa inteiro | Zero |
Exercícios
1. A palavra que faz lembrar
Complete para que contagem guarde o valor entre as chamadas da função:
2. A opção de aviso
Qual opção do gcc avisa quando uma variável esconde outra com o mesmo nome?
3. O valor inicial
Uma variável global int erros;, sem valor na declaração, começa valendo quanto?
4. O que acontece com for (int k = 0; k < 3; k++) { } printf("%d", k);?
5. O que este programa mostra? int x = 1; int main(void) { int x = 2; { int x = 3; } printf("%d", x); }
6. Qual a vantagem de um contador static dentro da função sobre um contador global?
Missão final: os pedidos da lojinha
Missão: complete a função registrar_pedido. Ela deve ter duas variáveis static: um int com o número de pedidos e um double com o total do dia. A cada chamada, some 1 ao número, some o valor ao total e mostre a linha no formato abaixo (use %.2f). Nada de variáveis globais.
Pedido 1: R$ 12.50 (total do dia: R$ 12.50) Pedido 2: R$ 3.00 (total do dia: R$ 15.50) Pedido 3: R$ 7.25 (total do dia: R$ 22.75)
#include <stdio.h>
void registrar_pedido(double valor) {
// suas variáveis static aqui
// atualize e mostre
}
int main(void) {
registrar_pedido(12.50);
registrar_pedido(3.00);
registrar_pedido(7.25);
return 0;
}
#include <stdio.h>
void registrar_pedido(double valor) {
static int pedidos = 0;
static double total = 0.0;
pedidos++;
total = total + valor;
printf("Pedido %d: R$ %.2f (total do dia: R$ %.2f)\n", pedidos, valor, total);
}
int main(void) {
registrar_pedido(12.50);
registrar_pedido(3.00);
registrar_pedido(7.25);
return 0;
}
Resumo
- Escopo é onde o nome vale; tempo de vida é quando a variável existe.
- Variável local: só dentro da sua função, nasce e morre a cada chamada, começa com lixo se você não der valor. Nomes iguais em funções diferentes não se misturam.
- Variável de bloco: só até o
}do bloco. Oidefor (int i…)não existe depois do laço (‘i’ undeclared). - Variável global: visível no arquivo todo, vive o programa inteiro, começa em zero. Qualquer função pode estragá-la; prefira locais e parâmetros.
- Sombreamento: um nome de dentro esconde o de fora. É permitido, mas costuma ser engano; o
-Wshadow(fora do-Wall) avisa. staticdentro da função: escopo local, vida longa. É inicializada uma vez (com constante) e guarda o valor entre chamadas: ideal para contadores. Ostaticfora das funções fica para static e extern.