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.

Você vai aprender
  • 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 o i do for.
  • Variáveis globais: visíveis no arquivo inteiro, e por que elas causam problemas.
  • Sombreamento: um nome que esconde outro, e o aviso -Wshadow.
  • static dentro 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 casaEm C
Um cômodoUma função, ou um bloco { } dentro dela
A panela da cozinhaUma variável local: só existe dentro daquela função
Uma gaveta dentro da cozinhaUm bloco (if, for): o que está ali só existe ali dentro
O quadro de avisos no corredor, que todo mundo vê e rabiscaUma variável global
A agenda que fica na gaveta da cozinha e não é jogada foraUma 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)
"vendidos não declarada (primeiro uso nesta função)". Para o main, esse nome não existe: ele pertence a vender.
note: each undeclared identifier is reported only once…
"Cada identificador não declarado é informado uma vez só por função". Se vendidos aparecesse mais vezes no main, 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
Uma variável no cabeçalho do 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
  1. O main começa o laço com i = 0 e chama estrelas.
  2. estrelas usa o mesmo i: conta de 0 a 5 e termina com i valendo 5.
  3. De volta ao main, o i++ faz i valer 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 total faz 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: vezes continua com o valor que tinha.
vezes = vezes + 1;
Soma 1 ao valor guardado da chamada anterior: 1, 2, 3…
Escopo continua local
O main não enxerga vezes (daria ‘vezes’ undeclared). Só tocar_campainha pode mexer nela: é a vantagem sobre uma global.

Compare as três escolhas para a contagem:

Local comumstatic localGlobal
Quem enxergaSó a funçãoSó a funçãoTodas as funções do arquivo
Quando existeDurante cada chamadaO programa inteiroO programa inteiro
Guarda o valor entre chamadasNãoSimSim
Valor inicial se você não der umLixoZeroZero
Contagem da campainhaSempre 11, 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çãoUse
Um valor usado só dentro de uma função, numa chamadaVariável local (a escolha padrão)
Um valor usado só dentro de um laço ou ifVariável de bloco, declarada ali dentro
Um valor que vem de quem chamaParâmetro (Parâmetros)
Um resultado que vai para quem chamoureturn (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 mudaGlobal, de preferência const (Constantes)
Dados que várias funções mudamEvite 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.

Mudei a variável, mas o valor "de fora" continua o mesmo

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 contador volta para o começo a cada chamada

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.

Um laço roda menos vezes do que deveria, sem nenhum aviso

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 é declaradaEscopo (onde o nome vale)Tempo de vidaValor inicial padrão
Dentro de uma funçãoDa declaração até o } da funçãoUma chamadaLixo
Dentro de um bloco { } ou no for (int i…)Até o } do blocoUma passagem pelo blocoLixo
ParâmetroA função inteiraUma chamadaO argumento
static dentro de uma funçãoAté o } da funçãoO programa inteiroZero
Fora de todas as funções (global)Da declaração até o fim do arquivoO programa inteiroZero

Exercícios

1. A palavra que faz lembrar

Complete para que contagem guarde o valor entre as chamadas da função:

int contagem = 0;

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. O i de for (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.
  • static dentro da função: escopo local, vida longa. É inicializada uma vez (com constante) e guarda o valor entre chamadas: ideal para contadores. O static fora das funções fica para static e extern.