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.
- Por que duas funções com o mesmo nome, em arquivos diferentes, dão
múltiplas definições de. - A usar
staticantes de uma função para deixá-la particular do arquivo. - A usar
staticnuma 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.he a definição num.csó. - A ler no
nma 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’econflicting 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 lojinha | Em C |
|---|---|
| Cada loja da rua | Cada 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 chamadaslinha()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.cchama). - t linha
- Minúscula: particular (local). Ela existe dentro do
.o, mas o ligador não a usa para casar com oUde outro arquivo. Duast linhaem 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
statica 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.
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 static | O que muda | Exemplo |
|---|---|---|
| Dentro de uma função | O 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
intchamadadesconto, 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.cinclui 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, oexternvirou um "preciso disto": o ligador vai buscar odescontonoconfig.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.
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.
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… | Escreva | Onde |
|---|---|---|
| Uma função que outros arquivos chamam | int preco_final(int preco); + a definição | Protótipo no .h, corpo no .c |
| Uma função auxiliar, só deste arquivo | static void linha(void) { … } | Só no .c (nunca no .h) |
| Um estado do módulo, protegido | static int total = 0; + funções para ler e mudar | A variável no .c; as funções no .h |
| Uma variável que vários arquivos leem e mudam | extern int desconto; + int desconto = 10; | O extern no .h; a definição num .c só, que inclui o .h |
| Uma constante para vários arquivos | extern const char nome_loja[]; + a definição; ou um #define/enum no .h | Como 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.
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ções | O que é | Quem enxerga | No nm |
|---|---|---|---|
void f(void) { … } | Definição de função | Todos os arquivos | T f |
static void f(void) { … } | Definição de função particular | Só este arquivo | t f |
void f(void); ou extern void f(void); | Declaração (protótipo) | Usa a f de outro arquivo | U f (se for chamada) |
int x = 5; | Definição de variável global | Todos os arquivos | D x |
int x; | Definição de variável global, valendo zero | Todos os arquivos | B x |
static int x = 0; | Definição de variável particular | Só este arquivo | b x (ou d x, com valor diferente de zero) |
extern int x; | Declaração: a variável está em outro arquivo | Usa a x de outro arquivo | U x (se for usada) |
extern const int x; + const int x = 5; | Constante compartilhada | Todos os arquivos | R x |
Exercícios
1. Particular do arquivo
Complete para que a função arredondar só possa ser usada dentro do próprio .c:
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:
- o
config.hdefine odesconto, e ele é colado nomain.ce nopreco.c: troque por uma declaraçãoexterne ponha a definiçãoint desconto = 10;noconfig.c; - o
main.ce opreco.ctêm cada um a sualinha(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). staticfora 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. Nonm, a letra fica minúscula (t,b,d).staticdentro 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.csó, 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.externcom valor vira definição e dá aviso.- Nada de
static intnum.h: cada.cganha 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.