Como usar corretamente a palavra-chave extern em C

236

Minha pergunta é sobre quando uma função deve ser referenciada com a externpalavra - chave em C.

Não estou conseguindo ver quando isso deve ser usado na prática. Enquanto escrevo um programa, todas as funções que utilizo são disponibilizadas através dos arquivos de cabeçalho que incluí. Então, por que seria útil externobter acesso a algo que não foi exposto no arquivo de cabeçalho?

Eu poderia estar pensando em como externfunciona incorretamente e, se sim, por favor me corrija.

Edit: Você deve externalgo quando é a declaração padrão sem a palavra-chave em um arquivo de cabeçalho?

lillq
fonte
relacionado para funções: stackoverflow.com/questions/856636/... para varables: stackoverflow.com/questions/1433204
Ciro Santilli郝海东冠状病六四事件法轮功

Respostas:

290

" extern" altera a ligação. Com a palavra-chave, supõe-se que a função / variável esteja disponível em outro lugar e a resolução seja adiada para o vinculador.

Há uma diferença entre "extern" nas funções e nas variáveis: nas variáveis, ela não instancia a própria variável, ou seja, não aloca nenhuma memória. Isso precisa ser feito em outro lugar. Portanto, é importante se você deseja importar a variável de outro lugar. Para funções, isso apenas informa ao compilador que a ligação é externa. Como esse é o padrão (você usa a palavra-chave "estática" para indicar que uma função não está vinculada usando ligação externa), não é necessário usá-la explicitamente.

bluebrother
fonte
1
então por que a mesma coisa externa é lá em Git: um software muito popular e moderna verificá-lo: github.com/git/git/blob/master/strbuf.h
rsjethani
K&R não notam que é padrão declarar a função como "externa", mas essa resposta resolve minha confusão!
acgtyrant
@rsjethani Acho que é para tornar o documento mais rigoroso e formatado.
acgtyrant
Talvez seja uma pergunta idiota, mas como isso se compara à declaração direta?
weberc2
197

extern informa ao compilador que esses dados estão definidos em algum lugar e serão conectados ao vinculador.

Com a ajuda das respostas aqui e conversando com alguns amigos, aqui está o exemplo prático de um uso externo .

Exemplo 1 - para mostrar uma armadilha:

File stdio.h:

int errno;
/* other stuff...*/

myCFile1.c:
#include <stdio.h>

Code...

myCFile2.c:
#include <stdio.h>

Code...

Se myCFile1.o e myCFile2.o estiverem vinculados, cada um dos arquivos c terá cópias separadas de errno . Esse é um problema, pois o mesmo erro está disponível em todos os arquivos vinculados.

Exemplo 2 - A correção.

File stdio.h:

extern int errno;
/* other stuff...*/

File stdio.c

int errno;

myCFile1.c:
#include <stdio.h>

Code...

myCFile2.c:
#include <stdio.h>

Code...

Agora, se myCFile1.o e MyCFile2.o estiverem vinculados pelo vinculador, ambos apontarão para o mesmo erro . Assim, resolvendo a implementação com extern .

lillq
fonte
71
O problema não é que os módulos myCFile1 e myCFile2 tenham uma cópia separada do errno, é que ambos estão expondo um símbolo chamado "errno". Quando o vinculador vê isso, ele não sabe qual "errno" escolher; portanto, ele sai com uma mensagem de erro.
cwick
2
o que significa "vinculado pelo vinculador" realmente significa? todo mundo usa este termo, eu não encontrar qualquer definição :(
Marcel Falliere
7
O compilador do Wiki ~ MarcelFalliere ~ compila cada arquivo de origem sozinho e cria um arquivo de objeto para cada arquivo de origem. O vinculador vincula esses arquivos de objeto a 1 executável.
Bitterblue
1
O @cwick gcc não está dando erro ou aviso mesmo depois de usar -Walle -pedantic. Por quê ? e como ?
b-ak
6
Um guarda de inclusão não protege contra exatamente isso?
obskyr
32

Já foi declarado que a externpalavra-chave é redundante para funções.

Quanto às variáveis ​​compartilhadas entre as unidades de compilação, declare-as em um arquivo de cabeçalho com a palavra-chave extern e, em seguida, defina-as em um único arquivo de origem, sem a palavra-chave extern. O arquivo de origem único deve ser o único que compartilha o nome do arquivo de cabeçalho, para práticas recomendadas.

aib
fonte
@aib "redundante para funções", verifique meu comentário na resposta do bluebrother.
rsjethani
E se você não quiser expor nenhuma das funções no arquivo de cabeçalho? Não seria melhor declarar a variável em um arquivo C e acessá-la externamente em outro; deixe o vinculador resolver o problema e oculte o restante do cabeçalho.
ste3e
16

Muitos anos depois, descubro essa pergunta. Depois de ler todas as respostas e comentários, pensei em esclarecer alguns detalhes ... Isso pode ser útil para as pessoas que chegam aqui através da pesquisa no Google.

A questão é especificamente sobre o uso de funções "externas", portanto ignorarei o uso de "externas" com variáveis ​​globais.

Vamos definir 3 protótipos de função:

//--------------------------------------
//Filename: "my_project.H"
extern int function_1(void);
static int function_2(void);
       int function_3(void);

O arquivo de cabeçalho pode ser usado pelo código fonte principal da seguinte maneira:

//--------------------------------------
//Filename: "my_project.C"
#include "my_project.H"

void main(void){
    int v1 = function_1();
    int v2 = function_2();
    int v3 = function_3();
}

int function_2(void) return 1234;

Para compilar e vincular, devemos definir "function_2" no mesmo arquivo de código-fonte em que chamamos essa função. As duas outras funções podem ser definidas em código-fonte diferente " .C" ou podem estar localizadas em qualquer arquivo binário ( .OBJ, * .LIB, * .DLL), para o qual talvez não tenhamos o código-fonte.

Vamos incluir novamente o cabeçalho "my_project.H" em um arquivo "* .C" diferente para entender melhor a diferença. No mesmo projeto, adicionamos o seguinte arquivo:

//--------------------------------------
//Filename: "my_big_project_splitted.C"
#include "my_project.H"

void old_main_test(void){
    int v1 = function_1();
    int v2 = function_2();
    int v3 = function_3();
}

int function_2(void) return 5678;

int function_1(void) return 12;
int function_3(void) return 34;

Recursos importantes a serem observados:

  • Quando uma função é definida como "estática" em um arquivo de cabeçalho, o compilador / vinculador deve encontrar uma instância de uma função com esse nome em cada módulo que usa esse arquivo de inclusão.

  • Uma função que faz parte da biblioteca C pode ser substituída em apenas um módulo, redefinindo um protótipo com "estático" apenas nesse módulo. Por exemplo, substitua qualquer chamada para "malloc" e "free" para adicionar o recurso de detecção de vazamento de memória.

  • O especificador "extern" não é realmente necessário para funções. Quando "estático" não é encontrado, sempre se assume que uma função é "externa".

  • No entanto, "extern" não é o padrão para variáveis. Normalmente, qualquer arquivo de cabeçalho que define variáveis ​​a serem visíveis em muitos módulos precisa usar "extern". A única exceção seria se um arquivo de cabeçalho fosse garantido para ser incluído em um e apenas um módulo.

    Muitos gerentes de projeto exigiriam que essa variável fosse colocada no início do módulo, não dentro de nenhum arquivo de cabeçalho. Alguns projetos grandes, como o emulador de videogame "Mame", exigem que essa variável apareça apenas acima da primeira função que os utiliza.

Christian Gingras
fonte
Então, por que exatamente uma função estática precisa de uma definição versus a externa? (Eu sei que isto é de 2 anos de atraso, mas isso é realmente muito útil para a compreensão)
SubLock69
2
A definição é necessária se você chamar a função na linha 100 e instancia-la na linha 500. A linha 100 declararia protótipo indefinido. Então, você adiciona o protótipo próximo ao topo.
Christian Gingras
15

Em C, 'extern' é implícito para protótipos de funções, pois um protótipo declara uma função que é definida em outro lugar. Em outras palavras, um protótipo de função possui ligação externa por padrão; usar 'extern' é bom, mas é redundante.

(Se a vinculação estática for necessária, a função deve ser declarada como 'estática', tanto no protótipo quanto no cabeçalho da função, e esses normalmente devem estar no mesmo arquivo.

Steve Melnikoff
fonte
8

Um artigo muito bom sobre a externpalavra - chave, juntamente com os exemplos: http://www.geeksforgeeks.org/understanding-extern-keyword-in-c/

Embora eu não concorde que o uso de externdeclarações de função seja redundante. Supõe-se que seja uma configuração do compilador. Portanto, recomendo usar as externdeclarações de função quando necessário.

tozak
fonte
3
Eu li o artigo geeksforgeeks.org antes de vir para cá, mas achei muito mal escrito. Além das deficiências gramaticais e de sintaxe, ele usa muitas palavras para expressar o mesmo argumento várias vezes e depois esquadrinha as informações críticas. Por exemplo, no Exemplo 4, subitamente 'somefile.h' é incluído, mas nada é dito sobre ele além de: "Supondo que somefile.h tenha a definição de var". Bem, as informações que estamos "supondo" são as que eu estou procurando. Infelizmente, nenhuma das respostas nesta página é muito melhor.
Elise van Looij
6

Se cada arquivo em seu programa for compilado primeiro em um arquivo de objeto, então os arquivos de objeto serão vinculados extern. Diz ao compilador "Esta função existe, mas o código para ela está em outro lugar. Não entre em pânico."

Chris Lutz
fonte
Hum, é assim que a tradução é normalmente feita: os arquivos de origem são compilados nos arquivos de objetos e depois são vinculados. Quando você não precisaria extern nesse caso? Você também não usaria #include para obter funções, mas protótipos de funções. Não entendo do que você está falando.
David Thornley
Ultimamente, parece que estou tendo esse problema de interpretar mal as coisas. Me desculpe por isso. Quando eu era novo no C, #include "file.c" para incluir apenas as funções em um arquivo diretamente no outro arquivo. Então eu descobri como usar 'extern'. Eu pensei que ele estava cometendo o mesmo erro que eu.
31730 Chris Lutz
4

Todas as declarações de funções e variáveis ​​nos arquivos de cabeçalho devem ser extern.

Exceções a esta regra são funções embutidas definidas no cabeçalho e variáveis ​​que - embora definidas no cabeçalho - terão que ser locais para a unidade de tradução (o arquivo de origem no qual o cabeçalho é incluído): essas devem ser static.

Nos arquivos de origem, externnão deve ser usado para funções e variáveis ​​definidas no arquivo. Prefixe as definições locais com statice não faça nada para as definições compartilhadas - elas serão símbolos externos por padrão.

O único motivo para usar externem um arquivo de origem é declarar funções e variáveis ​​definidas em outros arquivos de origem e para as quais nenhum arquivo de cabeçalho é fornecido.


Declarar protótipos de funções externé realmente desnecessário. Algumas pessoas não gostam disso porque apenas desperdiçam espaço e as declarações de função já tendem a ultrapassar os limites de linha. Outros gostam porque, dessa maneira, funções e variáveis ​​podem ser tratadas da mesma maneira.

Christoph
fonte
Você pode explicar por que "Todas as declarações de funções e variáveis ​​nos arquivos de cabeçalho devem ser externas"? Parece-me pelas outras respostas que elas são externas por padrão.
Lillq 30/01/09
@Lane: externé opcional para declarações de função, mas eu gosto de tratar variáveis ​​e funções da mesma maneira - pelo menos, é a coisa mais razoável que eu poderia ter, pois não me lembro exatamente por que comecei a fazer isso;)
Christoph
Não é uma idéia melhor sempre incluir variáveis ​​globais no arquivo C para que elas não sejam vistas por outros arquivos C aleatórios que incluem o cabeçalho. E sempre usar extern em todos os globais, exceto no verdadeiro sumidouro inicial, por uma questão de clareza; se for prefixado extern, será definido em outro lugar.
ste3e
3

As funções realmente definidas em outros arquivos de origem devem ser declaradas apenas nos cabeçalhos. Nesse caso, você deve usar extern ao declarar o protótipo em um cabeçalho.

Na maioria das vezes, suas funções serão uma das seguintes (mais como uma prática recomendada):

  • estático (funções normais que não são visíveis fora desse arquivo .c)
  • inline estático (inline de arquivos .c ou .h)
  • extern (declaração em cabeçalhos do próximo tipo (veja abaixo))
  • [nenhuma palavra-chave] (funções normais destinadas a serem acessadas usando declarações externas)
Eduard - Gabriel Munteanu
fonte
Por que você externaria ao declarar o protótipo se esse é o padrão?
Lillq 30/01/09
@ Lane: Pode ser um pouco tendencioso, mas todo projeto em que trabalhei usa a seguinte convenção: nos cabeçalhos, declare protótipos apenas para funções externas (portanto externas). Nos arquivos .c, protótipos simples podem ser usados ​​para evitar a necessidade de pedidos específicos, mas não devem ser colocados em cabeçalhos.
Eduard - Gabriel Munteanu
1

Quando você tem essa função definida em uma DLL ou lib diferente, para que o compilador adie ao vinculador para encontrá-lo. Caso típico é quando você está chamando funções da API do SO.

Otávio Décio
fonte