C static e extern

Quando um programa tem vários arquivos .c, surge uma pergunta: quem enxerga o quê? Em C, toda função e toda variável global que você escreve fora das funções é, por padrão, pública: o ligador a mostra para todos os outros arquivos. Isso é bom para as funções que você quer oferecer, e ruim para as funções auxiliares que só um arquivo usa. Nesta aula você vai conhecer as duas palavras que controlam essa visibilidade: static, que esconde um nome dentro do seu arquivo, e extern, que avisa que um nome existe em outro arquivo.

Você vai aprender
  • Por que duas funções com o mesmo nome, em arquivos diferentes, dão múltiplas definições de.
  • A usar static antes de uma função para deixá-la particular do arquivo.
  • A usar static numa variável global para guardar o estado de um módulo, protegido dos outros arquivos.
  • A compartilhar uma variável global entre arquivos com extern: a declaração no .h e a definição num .c só.
  • A ler no nm a diferença entre um nome público (T, D, B) e um particular (t, d, b).
  • Os erros reais: múltiplas definições de, referência não definida para, initialized and declared ‘extern’ e conflicting types.

Esta aula continua Arquivos .h e .c e Vários Arquivos: lá você aprendeu a dividir o programa, a compilar com gcc -c e a ler os erros do ligador. Todos os comandos rodaram na máquina fictícia da Ana, na pasta ~/c/static, com o gcc 15.2. O sistema dela está em português, e por isso as mensagens do ligador saem em português (o inglês vai citado ao lado).


A ideia: a vitrine e o depósito

Pense na lojinha da Ana. Na vitrine ficam os produtos que qualquer cliente pode ver e pedir. No depósito, nos fundos, ficam as caixas, as ferramentas e o caderninho de contas: coisas que a loja usa, mas que nenhum cliente deve mexer. Se a loja do lado também tem um depósito com uma escada, não há confusão: cada escada fica no seu depósito.

Na lojinhaEm C
Cada loja da ruaCada arquivo .c
A vitrine (todos veem)Funções e variáveis globais sem static: o ligador as mostra para todos os arquivos
O depósito (só a loja vê)Funções e variáveis globais com static: só o próprio arquivo as enxerga
A placa "o caixa fica na loja 12"Uma declaração com extern: "esta variável existe, mas mora em outro arquivo"

O nome técnico disso é ligação (linkage): um nome com ligação externa é visto por todos os arquivos do programa; um nome com ligação interna, só pelo seu arquivo. O static fora das funções troca a ligação de externa para interna.


1. O problema: dois arquivos, a mesma função auxiliar

O programa da lojinha tem um módulo de relatório e um de estoque. Cada um desenha as suas linhas de separação com uma pequena função auxiliar, e a Ana deu o mesmo nome às duas: linha. Os cabeçalhos só prometem a função principal de cada módulo:

#ifndef RELATORIO_H
#define RELATORIO_H

void relatorio_vendas(void);

#endif
#ifndef ESTOQUE_H
#define ESTOQUE_H

void estoque_mostrar(void);

#endif

Os dois .c têm a sua linha, cada uma com um desenho:

#include <stdio.h>
#include "relatorio.h"

void linha(void) {
    printf("====================\n");
}

void relatorio_vendas(void) {
    linha();
    printf("Vendas de hoje: R$ 150\n");
    linha();
}
#include <stdio.h>
#include "estoque.h"

void linha(void) {
    printf("--------------------\n");
}

void estoque_mostrar(void) {
    linha();
    printf("Cadernos: 12\n");
    printf("Lápis:    40\n");
    linha();
}
#include "relatorio.h"
#include "estoque.h"

int main(void) {
    relatorio_vendas();
    estoque_mostrar();
    return 0;
}

Cada arquivo, sozinho, está certo. Juntos, não ligam:

$ gcc -std=c23 -Wall -Wextra main.c relatorio.c estoque.c -o loja
/usr/bin/x86_64-linux-gnu-ld.bfd: /tmp/ccKuvut9.o: na função "linha":
estoque.c:(.text+0x0): múltiplas definições de "linha"; /tmp/ccwUIgEv.o:relatorio.c:(.text+0x0): definido primeiro aqui
collect2: error: ld returned 1 exit status

É o ligador falando (em inglês: multiple definition of `linha'; … first defined here): o estoque.c tem uma linha, e o relatorio.c já tinha uma. O nm (aula Vários Arquivos) mostra por quê:

$ gcc -std=c23 -Wall -Wextra -c relatorio.c estoque.c
$ nm relatorio.o
0000000000000000 T linha
                 U puts
000000000000001a T relatorio_vendas
$ nm estoque.o
000000000000001a T estoque_mostrar
0000000000000000 T linha
                 U puts

Os dois .o oferecem um T linha: as duas estão na "vitrine". Ninguém de fora precisa delas (o main.c nem sabe que existem), mas o C as publicou mesmo assim, porque esse é o padrão.


2. static numa função: só este arquivo enxerga

O conserto é uma palavra. Escreva static antes de cada linha:

#include <stdio.h>
#include "relatorio.h"

static void linha(void) {
    printf("====================\n");
}

void relatorio_vendas(void) {
    linha();
    printf("Vendas de hoje: R$ 150\n");
    linha();
}
static
Em inglês, "estático". Fora das funções, ele quer dizer: este nome é particular deste arquivo. O ligador não o mostra para os outros .o.
void linha(void)
O resto não muda. Dentro do relatorio.c, as chamadas linha() funcionam como antes.

Com o mesmo static na linha do estoque.c, o programa liga, e cada módulo usa a sua:

$ gcc -std=c23 -Wall -Wextra main.c relatorio.c estoque.c -o loja
$ ./loja
====================
Vendas de hoje: R$ 150
====================
--------------------
Cadernos: 12
Lápis:    40
--------------------

O nm agora mostra a letra minúscula:

$ gcc -std=c23 -Wall -Wextra -c relatorio.c estoque.c
$ nm relatorio.o
0000000000000000 t linha
                 U puts
000000000000001a T relatorio_vendas
$ nm estoque.o
000000000000001a T estoque_mostrar
0000000000000000 t linha
                 U puts
T relatorio_vendas
Maiúscula: pública. O ligador a oferece para os outros arquivos (é a que o main.c chama).
t linha
Minúscula: particular (local). Ela existe dentro do .o, mas o ligador não a usa para casar com o U de outro arquivo. Duas t linha em arquivos diferentes não brigam.

Missão: rode e leia o erro do ligador do editor (duplicate symbol: linha, "símbolo duplicado"). Depois ponha static antes da void linha(void) nas abas relatorio.c e estoque.c, e rode de novo.

#include "relatorio.h"
#include "estoque.h"

int main(void) {
    relatorio_vendas();
    estoque_mostrar();
    return 0;
}
#include <stdio.h>
#include "relatorio.h"

void linha(void) {
    printf("====================\n");
}

void relatorio_vendas(void) {
    linha();
    printf("Vendas de hoje: R$ 150\n");
    linha();
}
#include <stdio.h>
#include "estoque.h"

void linha(void) {
    printf("--------------------\n");
}

void estoque_mostrar(void) {
    linha();
    printf("Cadernos: 12\n");
    printf("Lápis:    40\n");
    linha();
}
#ifndef RELATORIO_H
#define RELATORIO_H

void relatorio_vendas(void);

#endif
#ifndef ESTOQUE_H
#define ESTOQUE_H

void estoque_mostrar(void);

#endif
#include <stdio.h>
#include "relatorio.h"

static void linha(void) {
    printf("====================\n");
}

void relatorio_vendas(void) {
    linha();
    printf("Vendas de hoje: R$ 150\n");
    linha();
}
#include <stdio.h>
#include "estoque.h"

static void linha(void) {
    printf("--------------------\n");
}

void estoque_mostrar(void) {
    linha();
    printf("Cadernos: 12\n");
    printf("Lápis:    40\n");
    linha();
}

(Com static em uma só das duas já liga: sobra uma linha pública e uma particular, e o ligador não vê conflito. Mas o certo é marcar as duas: nenhuma delas é para os outros arquivos.)

De fora, a função static não existe

E se o main.c quiser usar a linha mesmo assim? Ele pode até escrever o protótipo, e o compilador acredita. Mas o ligador não acha nenhuma linha pública:

#include "relatorio.h"
#include "estoque.h"

void linha(void);

int main(void) {
    relatorio_vendas();
    estoque_mostrar();
    linha();
    return 0;
}
$ gcc -std=c23 -Wall -Wextra main.c relatorio.c estoque.c -o loja
/usr/bin/x86_64-linux-gnu-ld.bfd: /tmp/ccEpcKxu.o: na função "main":
main.c:(.text+0x13): referência não definida para "linha"
collect2: error: ld returned 1 exit status

É exatamente o que a Ana queria: a linha é um detalhe do depósito, e ninguém de fora consegue depender dela. Se um dia ela mudar o desenho ou o nome, só o próprio arquivo é afetado.

Função static que ninguém usa: o aviso

Como uma função static só pode ser chamada no próprio arquivo, o compilador sabe dizer quando ela sobrou. No ajuda.c, a Ana escreveu uma titulo e esqueceu de chamá-la:

#include <stdio.h>

static void linha(void) {
    printf("--------------------\n");
}

static void titulo(void) {
    printf("Lojinha da Ana\n");
}

void cabecalho(void) {
    linha();
    printf("Bem-vindo!\n");
}
$ gcc -std=c23 -Wall -Wextra -c ajuda.c
ajuda.c:7:13: warning: ‘titulo’ defined but not used [-Wunused-function]
    7 | static void titulo(void) {
      |             ^~~~~~

"‘titulo’ definida mas não usada". Sem o static, não haveria aviso: uma função pública pode ser chamada por qualquer outro arquivo, e o compilador, que vê um arquivo por vez, não tem como saber. É mais um motivo para marcar as auxiliares com static: o compilador passa a ajudar a manter o arquivo limpo. (No editor, o Clang diz unused function 'titulo'.)


3. static numa variável global: o estado do módulo

O static também vale para variáveis globais, e aí ele é ainda mais útil. A Ana quer um módulo de caixa que soma as vendas do dia. O total tem de durar o programa inteiro (por isso é global), mas ninguém deve poder alterá-lo direto: só pela função caixa_vender, que recusa valores negativos.

#ifndef CAIXA_H
#define CAIXA_H

void caixa_vender(int valor);
int caixa_total(void);

#endif
#include "caixa.h"

static int total = 0;

void caixa_vender(int valor) {
    if (valor > 0) {
        total += valor;
    }
}

int caixa_total(void) {
    return total;
}
#include <stdio.h>
#include "caixa.h"

int main(void) {
    caixa_vender(36);
    caixa_vender(10);
    caixa_vender(-500);
    printf("Total do dia: R$ %d\n", caixa_total());
    return 0;
}
static int total = 0;
Uma variável global, fora das funções: existe do começo ao fim do programa e é vista por todas as funções deste arquivo. O static a esconde dos outros arquivos.
caixa_vender
A única porta de entrada. Ela confere o valor antes de somar: a venda de -500 é recusada.
caixa_total
A janela para ler o total, sem poder mudá-lo.
$ gcc -std=c23 -Wall -Wextra main.c caixa.c -o caixa
$ ./caixa
Total do dia: R$ 46
$ gcc -std=c23 -Wall -Wextra -c caixa.c
$ nm caixa.o
0000000000000025 T caixa_total
0000000000000000 T caixa_vender
0000000000000000 b total

O b minúsculo diz: total é particular e mora na parte do programa chamada .bss, a das variáveis globais que começam em zero. (Uma global com outro valor inicial apareceria como d, de data; as letras maiúsculas B e D são as públicas.)

Agora alguém tenta "trapacear" no trapaca.c, declarando o total por conta própria para mudá-lo direto:

#include <stdio.h>
#include "caixa.h"

extern int total;

int main(void) {
    total = 1000000;
    printf("Total do dia: R$ %d\n", caixa_total());
    return 0;
}
$ gcc -std=c23 -Wall -Wextra trapaca.c caixa.c -o trapaca
/usr/bin/x86_64-linux-gnu-ld.bfd: /tmp/cctcye9R.o: aviso: relocalização contra "total" em secção só de leitura ".text"
/usr/bin/x86_64-linux-gnu-ld.bfd: /tmp/cctcye9R.o: na função "main":
trapaca.c:(.text+0xa): referência não definida para "total"
/usr/bin/x86_64-linux-gnu-ld.bfd: aviso: a criar DT_TEXTREL num PIE.
collect2: error: ld returned 1 exit status

A linha que importa é a do meio: referência não definida para "total" (undefined reference to `total'). As duas linhas com aviso: são efeito colateral do mesmo problema (o ligador não sabe onde colocar uma variável que não existe) e somem quando o erro é consertado. O static funcionou: o total está protegido, e o único jeito de mexer nele é pelas funções do caixa.h.

Isso é encapsulamento. Um par caixa.h + caixa.c com variáveis static e funções públicas funciona como uma "caixa-preta": quem usa só vê as funções da vitrine. É a forma de o C fazer o que outras linguagens fazem com classes e private.

Experimente: rode. Depois, na aba main.c, acrescente a linha extern int total; antes do main e um total = 1000000; dentro dele, depois das vendas: o ligador do editor responde undefined symbol: total. Por fim, tire o static da aba caixa.c e rode de novo: a trapaça passa, e o total vira um milhão.

#include <stdio.h>
#include "caixa.h"

int main(void) {
    caixa_vender(36);
    caixa_vender(10);
    caixa_vender(-500);
    printf("Total do dia: R$ %d\n", caixa_total());
    return 0;
}
#include "caixa.h"

static int total = 0;

void caixa_vender(int valor) {
    if (valor > 0) {
        total += valor;
    }
}

int caixa_total(void) {
    return total;
}
#ifndef CAIXA_H
#define CAIXA_H

void caixa_vender(int valor);
int caixa_total(void);

#endif

Os dois sentidos do static

Na aula Escopo você viu o static dentro de uma função. É a mesma palavra com outro efeito, e isso confunde muita gente:

Onde está o staticO que mudaExemplo
Dentro de uma funçãoO tempo de vida: a variável não morre quando a função termina e guarda o valor entre as chamadas. Quem a enxerga continua sendo só a função.static int chamadas = 0; num contador de chamadas
Fora das funções (variável global ou função)A visibilidade: o nome fica particular do arquivo. O tempo de vida já era o do programa inteiro e não muda.static int total = 0; e static void linha(void)

4. extern: compartilhar uma variável entre arquivos

Às vezes você quer que vários arquivos usem a mesma variável global: uma configuração do programa, por exemplo. A Ana guarda o desconto da loja e o nome dela num módulo config, e o módulo preco e o main usam os dois.

A regra é a mesma das funções (aula Arquivos .h e .c): declarar no .h, quantas vezes for preciso; definir num .c, uma vez só. Para funções, o protótipo já é uma declaração. Para variáveis, int desconto; seria uma definição (reserva memória); para só declarar, é preciso o extern:

#ifndef CONFIG_H
#define CONFIG_H

extern int desconto;
extern const char nome_loja[];

#endif
#include "config.h"

int desconto = 10;
const char nome_loja[] = "Lojinha da Ana";
extern int desconto;
Em inglês, "externo". Quer dizer: "existe uma variável int chamada desconto, definida em algum arquivo". Não reserva memória e não tem valor. Pode aparecer em quantos arquivos você quiser.
extern const char nome_loja[];
O mesmo para um texto. O tamanho pode ficar de fora na declaração: quem define é o config.c.
int desconto = 10;
A definição, no config.c: aqui a variável nasce, com o seu valor inicial. Uma vez só, no programa inteiro.
#include "config.h"
O config.c inclui o próprio .h. Assim, se a declaração e a definição não combinarem, o compilador avisa (você vai ver na seção 5).

Quem usa só inclui o config.h:

#include "config.h"
#include "preco.h"

int preco_final(int preco) {
    return preco - preco * desconto / 100;
}
#include <stdio.h>
#include "config.h"
#include "preco.h"

int main(void) {
    printf("%s (desconto de %d%%)\n", nome_loja, desconto);
    printf("Caderno: R$ %d\n", preco_final(20));
    desconto = 50;
    printf("Liquidação! Caderno: R$ %d\n", preco_final(20));
    return 0;
}

(O preco.h tem só o protótipo int preco_final(int preco); com o include guard.)

$ gcc -std=c23 -Wall -Wextra main.c config.c preco.c -o loja
$ ./loja
Lojinha da Ana (desconto de 10%)
Caderno: R$ 18
Liquidação! Caderno: R$ 10

O main mudou o desconto para 50, e o preco_final, em outro arquivo, viu a mudança: é a mesma variável. O nm confirma quem define e quem só usa:

$ gcc -std=c23 -Wall -Wextra -c main.c config.c preco.c
$ nm config.o
0000000000000000 D desconto
0000000000000000 R nome_loja
$ nm main.o
                 U desconto
0000000000000000 T main
                 U nome_loja
                 U preco_final
                 U printf
D desconto
Definida aqui, pública, na parte data (variáveis globais com valor inicial).
R nome_loja
Definida aqui, pública, na parte read-only ("só leitura"), porque é const.
U desconto
No main.o, o extern virou um "preciso disto": o ligador vai buscar o desconto no config.o.

Esqueceu a definição?

O extern é só uma promessa. Se nenhum arquivo define a variável (aqui, a Ana esqueceu o config.c no comando), o compilador aceita e o ligador reclama:

$ gcc -std=c23 -Wall -Wextra main.c preco.c -o loja
/usr/bin/x86_64-linux-gnu-ld.bfd: /tmp/ccWDl0aG.o: aviso: relocalização contra "desconto" em secção só de leitura ".text"
/usr/bin/x86_64-linux-gnu-ld.bfd: /tmp/ccLIU07M.o: na função "main":
main.c:(.text+0xa): referência não definida para "desconto"
/usr/bin/x86_64-linux-gnu-ld.bfd: main.c:(.text+0x11): referência não definida para "nome_loja"
/usr/bin/x86_64-linux-gnu-ld.bfd: main.c:(.text+0x4f): referência não definida para "desconto"
/usr/bin/x86_64-linux-gnu-ld.bfd: /tmp/ccWDl0aG.o: na função "preco_final":
preco.c:(.text+0xd): referência não definida para "desconto"
/usr/bin/x86_64-linux-gnu-ld.bfd: aviso: a criar DT_TEXTREL num PIE.
collect2: error: ld returned 1 exit status

Uma linha para cada uso: três no main, uma no preco_final. Ignore os dois aviso:, que são consequência; o conserto é passar o config.c ao gcc.

E as funções?

As funções já têm ligação externa sem que você escreva nada: int preco_final(int preco); e extern int preco_final(int preco); são a mesma coisa. Por isso quase ninguém escreve extern em protótipos. Você vai encontrá-lo nos cabeçalhos do sistema (o <stdio.h> está cheio de extern int …, veja Pré-processador), mas no seu código use o extern só para variáveis.

Global compartilhada continua sendo global. Tudo o que a aula Escopo disse sobre os perigos das globais vale em dobro aqui: agora qualquer arquivo pode mudar o desconto. Prefira o jeito da seção 3 (variável static e funções para ler e mudar). Use extern para poucas coisas, de preferência constantes (extern const …) ou configurações lidas uma vez no começo.

5. Os enganos mais comuns com extern

int x; em dois arquivos

Na pasta comum, a Ana escreveu int visitas; (sem extern, sem valor) no main.c e no contar.c, achando que a segunda só "avisava" que a variável existe:

int visitas;
void contar(void) {
    visitas++;
}
$ gcc -std=c23 -Wall -Wextra main.c contar.c -o visitas
/usr/bin/x86_64-linux-gnu-ld.bfd: /tmp/cczvhoAP.o:(.bss+0x0): múltiplas definições de "visitas"; /tmp/ccLHdFGp.o:(.bss+0x0): definido primeiro aqui
collect2: error: ld returned 1 exit status

int visitas; fora das funções é uma definição (vale zero, como toda global), e duas definições não podem. Você vai achar na internet programas antigos que fazem isso e "funcionam": até o gcc 9, o padrão era juntar essas variáveis numa só (a opção -fcommon). Desde o gcc 10, o padrão é -fno-common, e o erro aparece. Com -fcommon, o programa da Ana liga e mostra Visitas: 1, mas o conserto certo é extern int visitas; no contar.c (ou, melhor, num .h).

extern com valor

extern int desconto = 10;
int main(void) {
    return desconto;
}
$ gcc -std=c23 -Wall -Wextra x.c -o x
x.c:1:12: warning: ‘desconto’ initialized and declared ‘extern’
    1 | extern int desconto = 10;
      |            ^~~~~~~~

"‘desconto’ inicializada e declarada extern". Com valor, a linha deixa de ser uma declaração e vira uma definição; o extern fica sem sentido, e o gcc avisa. Num .h, isso daria múltiplas definições. Declaração (extern, sem valor) no .h; definição (sem extern, com valor) no .c.

Tipos que não combinam

Na pasta tipo, o config.c define int desconto = 10;, e o main.c, por conta própria (sem incluir .h nenhum), declara a variável com o tipo errado:

#include <stdio.h>
extern double desconto;
int main(void) {
    printf("Desconto: %g\n", desconto);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra main.c config.c -o loja
$ ./loja
Desconto: 4.94066e-323

Nenhum erro, nenhum aviso, e um número absurdo. O compilador viu cada arquivo sozinho, e o ligador só casa nomes, não tipos: o main leu os bytes de um int como se fossem um double. (No editor do guia sai outro número absurdo, porque o lixo lido é outro.) O remédio é o que a seção 4 já fazia: a declaração mora num .h, e o .c que define inclui esse .h. Aí o compilador vê as duas no mesmo arquivo e compara:

$ gcc -std=c23 -Wall -Wextra -c config.c
config.c:2:5: error: conflicting types for ‘desconto’; have ‘int’
    2 | int desconto = 10;
      |     ^~~~~~~~
In file included from config.c:1:
config.h:1:15: note: previous declaration of ‘desconto’ with type ‘double’
    1 | extern double desconto;
      |               ^~~~~~~~

"Tipos em conflito para ‘desconto’: aqui é ‘int’", e a nota aponta a declaração anterior, no config.h, com ‘double’. Agora o erro aparece na compilação, com arquivo e linha, em vez de virar um número maluco na tela.

static num .h: uma cópia para cada arquivo

Na pasta copia, a Ana pôs o contador de pedidos no cabeçalho:

#ifndef CONTADOR_H
#define CONTADOR_H

static int pedidos = 0;

void novo_pedido(void);

#endif
#include "contador.h"

void novo_pedido(void) {
    pedidos++;
}
#include <stdio.h>
#include "contador.h"

int main(void) {
    novo_pedido();
    novo_pedido();
    novo_pedido();
    printf("Pedidos: %d\n", pedidos);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra main.c contador.c -o pedidos
$ ./pedidos
Pedidos: 0

Três pedidos, e o programa mostra zero, sem nenhum aviso. O .h foi colado nos dois .c, e o static deu a cada um a sua pedidos particular: o novo_pedido soma na cópia do contador.c, e o main lê a cópia dele, que nunca mudou. O conserto é o da seção 3: static int pedidos = 0; no contador.c e uma função int total_pedidos(void) no .h.

Uma exceção que você vai ver: funções pequenas marcadas static inline num .h. Lá, a cópia em cada arquivo é de propósito: é uma função sem estado, e cada .c recebe a sua para o compilador poder colá-la no lugar da chamada. É o assunto de Funções inline.

Qual usar

Você quer…EscrevaOnde
Uma função que outros arquivos chamamint preco_final(int preco); + a definiçãoProtótipo no .h, corpo no .c
Uma função auxiliar, só deste arquivostatic void linha(void) { … }Só no .c (nunca no .h)
Um estado do módulo, protegidostatic int total = 0; + funções para ler e mudarA variável no .c; as funções no .h
Uma variável que vários arquivos leem e mudamextern int desconto; + int desconto = 10;O extern no .h; a definição num .c só, que inclui o .h
Uma constante para vários arquivosextern const char nome_loja[]; + a definição; ou um #define/enum no .hComo a linha de cima

Regra prática: comece tudo como static no .c. Só tire o static (e ponha o protótipo no .h) do que outro arquivo realmente precisa. Quanto menos coisas na vitrine, menos colisões de nome e menos lugares onde um erro pode nascer.


Quando dá errado

múltiplas definições de "linha" (multiple definition of `linha')

O que significa: dois arquivos definem uma função (ou uma variável global) pública com o mesmo nome. No editor: duplicate symbol: linha.

Como consertar: se é uma auxiliar que só o próprio arquivo usa, ponha static antes dela (nos dois arquivos). Se é uma variável que deve ser a mesma em todos, deixe a definição num .c e use extern nos outros (pelo .h).

referência não definida para "total", com o static no outro arquivo

O que significa: o nome existe, mas é particular do arquivo onde está. Para o ligador, é como se não existisse. No editor: undefined symbol: total.

Como consertar: se outro arquivo precisa mesmo daquilo, ofereça uma função pública no .h (como caixa_total). Tirar o static também resolve, mas abre o depósito para todo mundo.

referência não definida para "desconto", com aviso: relocalização contra "desconto"

O que significa: há um extern int desconto;, mas nenhum arquivo do comando tem a definição int desconto = …;. Os avisos de relocalização e de DT_TEXTREL vêm junto e são consequência.

Como consertar: escreva a definição num .c (uma vez só) e confira se esse .c está no comando do gcc.

warning: ‘desconto’ initialized and declared ‘extern’

O que significa: extern int desconto = 10;: um extern com valor é uma definição disfarçada.

Como consertar: no .h, extern int desconto; (sem valor); no .c, int desconto = 10; (sem extern).

error: conflicting types for ‘desconto’

O que significa: a declaração (extern double desconto;) e a definição (int desconto) dizem tipos diferentes. É bom que o gcc tenha visto: sem o #include do .h, o programa ligaria e leria lixo.

Como consertar: escolha um tipo e use o mesmo nos dois lugares. E mantenha o #include "config.h" no config.c, que é o que permite essa conferência.

O valor "não muda" entre arquivos (Pedidos: 0)

O que significa: há um static int … num .h, e cada .c que o inclui ganhou a sua cópia. Não há erro nem aviso.

Como consertar: variável static fica no .c; para compartilhar, use funções ou extern. No .h, nunca defina variáveis.


Referência

Escrito fora das funçõesO que éQuem enxergaNo nm
void f(void) { … }Definição de funçãoTodos os arquivosT f
static void f(void) { … }Definição de função particularSó este arquivot f
void f(void); ou extern void f(void);Declaração (protótipo)Usa a f de outro arquivoU f (se for chamada)
int x = 5;Definição de variável globalTodos os arquivosD x
int x;Definição de variável global, valendo zeroTodos os arquivosB x
static int x = 0;Definição de variável particularSó este arquivob x (ou d x, com valor diferente de zero)
extern int x;Declaração: a variável está em outro arquivoUsa a x de outro arquivoU x (se for usada)
extern const int x; + const int x = 5;Constante compartilhadaTodos os arquivosR x

Exercícios

1. Particular do arquivo

Complete para que a função arredondar só possa ser usada dentro do próprio .c:

int arredondar(int valor) {

2. A declaração no .h

O config.c tem int limite_estoque = 100;. Complete a linha do config.h que deixa os outros arquivos usarem essa variável:

3. O nm loja.o mostra t formatar. O que isso quer dizer?

4. Qual destas linhas pode ir num .h incluído por cinco .c sem dar múltiplas definições?

5. A Ana pôs static int pedidos = 0; num .h incluído pelo main.c e pelo contador.c. O que acontece?

Missão final: o desconto da lojinha

Missão: o projeto não liga: o ligador do editor aponta dois nomes duplicados (duplicate symbol), o linha e o desconto. Conserte os dois sem apagar nada que aparece na tela:

  1. o config.h define o desconto, e ele é colado no main.c e no preco.c: troque por uma declaração extern e ponha a definição int desconto = 10; no config.c;
  2. o main.c e o preco.c têm cada um a sua linha (com desenhos diferentes): deixe as duas particulares.
====================
Caderno: R$ 18
====================
--------------------
Liquidação! Caderno: R$ 10
--------------------
#include <stdio.h>
#include "config.h"
#include "preco.h"

void linha(void) {
    printf("====================\n");
}

int main(void) {
    linha();
    printf("Caderno: R$ %d\n", preco_final(20));
    linha();
    desconto = 50;
    mostrar_preco("Liquidação! Caderno", 20);
    return 0;
}
#include <stdio.h>
#include "config.h"
#include "preco.h"

void linha(void) {
    printf("--------------------\n");
}

int preco_final(int preco) {
    return preco - preco * desconto / 100;
}

void mostrar_preco(const char *nome, int preco) {
    linha();
    printf("%s: R$ %d\n", nome, preco_final(preco));
    linha();
}
#ifndef CONFIG_H
#define CONFIG_H

int desconto = 10;

#endif
#include "config.h"

// a definição do desconto vem aqui
#ifndef PRECO_H
#define PRECO_H

int preco_final(int preco);
void mostrar_preco(const char *nome, int preco);

#endif
#include <stdio.h>
#include "config.h"
#include "preco.h"

static void linha(void) {
    printf("====================\n");
}

int main(void) {
    linha();
    printf("Caderno: R$ %d\n", preco_final(20));
    linha();
    desconto = 50;
    mostrar_preco("Liquidação! Caderno", 20);
    return 0;
}
#include <stdio.h>
#include "config.h"
#include "preco.h"

static void linha(void) {
    printf("--------------------\n");
}

int preco_final(int preco) {
    return preco - preco * desconto / 100;
}

void mostrar_preco(const char *nome, int preco) {
    linha();
    printf("%s: R$ %d\n", nome, preco_final(preco));
    linha();
}
#ifndef CONFIG_H
#define CONFIG_H

extern int desconto;

#endif
#include "config.h"

int desconto = 10;

Resumo

  • Fora das funções, toda função e toda variável global é pública (ligação externa): o ligador a mostra para todos os arquivos. Duas com o mesmo nome dão múltiplas definições de (multiple definition of).
  • static fora das funções deixa o nome particular do arquivo (ligação interna). Use-o em toda função auxiliar e em todo estado do módulo. No nm, a letra fica minúscula (t, b, d).
  • static dentro de uma função é outra coisa: muda o tempo de vida, não a visibilidade (aula Escopo).
  • extern int x; declara uma variável que mora em outro arquivo: vai no .h. A definição, int x = 5;, vai num .c só, que inclui o próprio .h (assim o gcc confere os tipos).
  • int x; fora das funções já é definição: em dois arquivos, dá erro desde o gcc 10. extern com valor vira definição e dá aviso.
  • Nada de static int num .h: cada .c ganha uma cópia, sem aviso.
  • Comece tudo como static; publique (no .h) só o que outros arquivos precisam. O próximo passo para projetos com muitos arquivos é o Makefile.