Efeitos da palavra-chave extern nas funções C

171

Em C, não notei nenhum efeito da externpalavra - chave usada antes da declaração da função. No começo, pensei que, ao definir extern int f();um único arquivo , você o implementa fora do escopo do arquivo. No entanto, descobri que ambos:

extern int f();
int f() {return 0;}

e

extern int f() {return 0;}

compile muito bem, sem avisos do gcc. Eu usei gcc -Wall -ansi; nem aceitaria //comentários.

Há algum efeito no uso extern antes das definições de função ? Ou é apenas uma palavra-chave opcional, sem efeitos colaterais para funções.

No último caso, não entendo por que os designers padrão escolheram desarrumar a gramática com palavras-chave supérfluas.

EDIT: Para esclarecer, eu sei que não há uso para externem variáveis, mas eu só estou perguntando sobre externem funções .

Elazar Leibovich
fonte
De acordo com algumas pesquisas que fiz ao tentar usar isso para alguns propósitos malucos de modelagem, o extern não é suportado no formato pretendido pela maioria dos compiladores e, portanto, realmente não faz nada.
Ed James
4
Nem sempre é supérfluo, veja minha resposta. Sempre que você precisar compartilhar algo entre os módulos que NÃO deseja em um cabeçalho público, é muito útil. No entanto, 'externalizar' cada função em um cabeçalho público (com compiladores modernos) tem muito pouco ou nenhum benefício, pois eles podem descobrir por si próprios.
Tim Post
@ Ed .. se volátil int foo for global em foo.c e o bar.c precisar, o bar.c deve declará-lo como externo. Tem suas vantagens. Além disso, pode ser necessário compartilhar alguma função que você NÃO deseja que seja exposta em um cabeçalho público.
Tim Post
Veja também: stackoverflow.com/questions/496448/…
Steve Melnikoff
2
@ Barry Se houver, a outra pergunta é uma duplicata desta. 2009 vs 2012
Elazar Leibovich 06/06

Respostas:

138

Temos dois arquivos, foo.c e bar.c.

Aqui está foo.c

#include <stdio.h>

volatile unsigned int stop_now = 0;
extern void bar_function(void);

int main(void)
{
  while (1) {
     bar_function();
     stop_now = 1;
  }
  return 0;
}

Agora, aqui está bar.c

#include <stdio.h>

extern volatile unsigned int stop_now;

void bar_function(void)
{
   while (! stop_now) {
      printf("Hello, world!\n");
      sleep(30);
   }
}

Como você pode ver, não temos um cabeçalho compartilhado entre foo.c e bar.c, no entanto bar.c precisa de algo declarado em foo.c quando está vinculado, e foo.c precisa de uma função de bar.c quando está vinculado.

Ao usar 'extern', você está dizendo ao compilador que o que se segue será encontrado (não estático) no momento do link; não reserve nada para ele no passe atual, pois ele será encontrado posteriormente. Funções e variáveis ​​são tratadas igualmente a esse respeito.

É muito útil se você precisar compartilhar algo global entre os módulos e não quiser colocá-lo / inicializá-lo em um cabeçalho.

Tecnicamente, todas as funções em um cabeçalho público da biblioteca são 'externas', no entanto, rotulá-las como tal tem muito pouco ou nenhum benefício, dependendo do compilador. A maioria dos compiladores pode descobrir isso por conta própria. Como você vê, essas funções são realmente definidas em outro lugar.

No exemplo acima, main () imprimiria olá mundo apenas uma vez, mas continuaria inserindo bar_function (). Observe também que bar_function () não retornará neste exemplo (já que é apenas um exemplo simples). Imagine o stop_now sendo modificado quando um sinal for atendido (portanto, volátil) se isso não parecer prático o suficiente.

Os externos são muito úteis para coisas como manipuladores de sinal, um mutex que você não deseja colocar em um cabeçalho ou estrutura, etc. A maioria dos compiladores será otimizada para garantir que eles não reservem memória para objetos externos, pois sabem que eles o reservará no módulo em que o objeto está definido. No entanto, novamente, há pouco sentido em especificá-lo com compiladores modernos ao prototipar funções públicas.

Espero que ajude :)

Tim Post
fonte
56
Seu código será compilado sem a externa antes da função bar_.
Elazar Leibovich
2
@ Tim: Então você não teve o privilégio duvidoso de trabalhar com o código com o qual trabalho. Pode acontecer. Às vezes, o cabeçalho também contém a definição de função estática. É feio e desnecessário em 99,99% do tempo (eu poderia estar desligado por uma ordem ou duas ou magnitude, exagerando quantas vezes é necessário). Geralmente ocorre quando as pessoas entendem mal que um cabeçalho é necessário apenas quando outros arquivos de origem usarão as informações; o cabeçalho é (ab) usado para armazenar informações de declaração para um arquivo de origem e nenhum outro arquivo deve incluí-lo. Ocasionalmente, ocorre por razões mais distorcidas.
Jonathan Leffler
2
@ Jonathan Leffler - Duvidoso mesmo! Eu herdei um código bastante superficial antes, mas posso dizer honestamente que nunca vi alguém colocar uma declaração estática em um cabeçalho. Parece que você tem um trabalho bastante divertido e interessante :)
Tim Post
1
A desvantagem do 'protótipo de função que não está no cabeçalho' é que você não obtém a verificação independente automática da consistência entre a definição de função bar.ce a declaração em foo.c. Se a função for declarada foo.h e os dois arquivos incluírem foo.h, o cabeçalho aplicará consistência entre os dois arquivos de origem. Sem ele, se a definição de bar_functionin for bar.calterada, mas a declaração in foo.cnão for alterada, as coisas darão errado no tempo de execução; o compilador não consegue identificar o problema. Com um cabeçalho usado corretamente, o compilador identifica o problema.
31812 Jonathan Leffler
1
declarações externas em funções são supérfluas como 'int' em 'unsigned int'. É uma boa prática usar 'extern' quando o protótipo NÃO é uma declaração de encaminhamento ... Mas ele realmente deve estar em um cabeçalho sem 'extern', a menos que a tarefa seja um caso delicado. stackoverflow.com/questions/10137037/…
82

Tanto quanto me lembro do padrão, todas as declarações de função são consideradas "externas" por padrão, portanto não há necessidade de especificá-lo explicitamente.

Isso não torna essa palavra-chave inútil, pois também pode ser usada com variáveis ​​(e nesse caso - é a única solução para resolver problemas de ligação). Mas com as funções - sim, é opcional.


fonte
21
Então, como designer padrão, não permitirei o uso externo com funções, pois apenas adiciona ruído à gramática.
Elazar Leibovich
3
A compatibilidade com versões anteriores pode ser uma dor.
MathuSum Mut
1
@ElazarLeibovich Na verdade, nesse caso em particular, não é o que acrescentaria ruído à gramática.
Lightness Races em órbita
1
Como limitar uma palavra-chave adiciona ruído está além de mim, mas acho que é uma questão de gosto.
Elazar Leibovich
É útil permitir o uso de "externo" para funções, pois indica para outros programadores que a função está definida em outro arquivo, não no arquivo atual e também não é declarada em um dos cabeçalhos incluídos.
DimP 25/06
23

Você precisa distinguir entre dois conceitos separados: definição de função e declaração de símbolo. "extern" é um modificador de ligação, uma dica para o compilador sobre onde o símbolo referido posteriormente é definido (a dica é "não aqui").

Se eu escrever

extern int i;

no escopo do arquivo (fora de um bloco de função) em um arquivo C, você está dizendo "a variável pode ser definida em outro lugar".

extern int f() {return 0;}

é uma declaração da função f e uma definição da função f. A definição neste caso substitui o externo.

extern int f();
int f() {return 0;}

é primeiro uma declaração, seguida pela definição.

O uso de externestá errado se você deseja declarar e definir simultaneamente uma variável de escopo do arquivo. Por exemplo,

extern int i = 4;

dará um erro ou aviso, dependendo do compilador.

O uso de externé útil se você desejar explicitamente evitar a definição de uma variável.

Deixe-me explicar:

Digamos que o arquivo ac contenha:

#include "a.h"

int i = 2;

int f() { i++; return i;}

O arquivo ah inclui:

extern int i;
int f(void);

e o arquivo bc contém:

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

int main(void){
    printf("%d\n", f());
    return 0;
}

O externo no cabeçalho é útil, porque informa ao compilador durante a fase do link "esta é uma declaração e não uma definição". Se eu remover a linha em ac que define i, alocar espaço para ela e atribuir um valor a ela, o programa falhará ao compilar com uma referência indefinida. Isso informa ao desenvolvedor que ele se referiu a uma variável, mas ainda não a definiu. Se, por outro lado, eu omito a palavra-chave "extern" e removo a int i = 2linha, o programa ainda compila - eu será definido com o valor padrão 0.

As variáveis ​​de escopo do arquivo são definidas implicitamente com um valor padrão de 0 ou NULL se você não atribuir explicitamente um valor a elas - diferentemente das variáveis ​​de escopo de bloco que você declara na parte superior de uma função. A palavra-chave extern evita essa definição implícita e, assim, ajuda a evitar erros.

Para funções, nas declarações de função, a palavra-chave é realmente redundante. As declarações de função não têm uma definição implícita.

Dave Neary
fonte
Você quis remover a int i = 2linha no terceiro parágrafo? E é correto afirmar, vendo int i;, o compilador alocará memória para essa variável, mas vendo extern int i;, o compilador NÃO alocará memória, mas procurará a variável em outro lugar?
Frozen Flame
Na verdade, se você omitir a palavra-chave "extern", o programa não será compilado devido à redefinição de i em ac e bc (devido a ah).
Nixt 28/04
15

A externpalavra-chave assume diferentes formas, dependendo do ambiente. Se uma declaração estiver disponível, a externpalavra-chave utilizará a ligação conforme especificado anteriormente na unidade de tradução. Na ausência de qualquer declaração, externespecifica a ligação externa.

static int g();
extern int g(); /* g has internal linkage */

extern int j(); /* j has tentative external linkage */

extern int h();
static int h(); /* error */

Aqui estão os parágrafos relevantes do projeto C99 (n1256):

6.2.2 Ligações de identificadores

[...]

4 Para um identificador declarado com o especificador de classe de armazenamento externo em um escopo em que uma declaração anterior desse identificador seja visível, 23) se a declaração anterior especificar ligação interna ou externa, a ligação do identificador na declaração posterior será a mesma como a ligação especificada na declaração anterior. Se nenhuma declaração anterior estiver visível ou se a declaração anterior não especificar nenhuma ligação, o identificador terá uma ligação externa.

5 Se a declaração de um identificador para uma função não tiver um especificador de classe de armazenamento, sua ligação será determinada exatamente como se tivesse sido declarada com o especificador de classe de armazenamento externo. Se a declaração de um identificador para um objeto tiver escopo de arquivo e nenhum especificador de classe de armazenamento, sua ligação será externa.

dirkgently
fonte
É o padrão, ou você está apenas me dizendo o comportamento de um compilador típico? No caso do padrão, ficarei feliz em ter um link para o padrão. Mas obrigada!
Elazar Leibovich
Esse é o comportamento padrão. O rascunho do C99 está disponível aqui: < open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf >. O padrão atual não é gratuito (o rascunho é bom o suficiente para a maioria dos propósitos).
dirkgently
1
Acabei de testar no gcc e são "extern int h (); static int h () {return 0;}" e "int h (); static int h () {return 0;}" são aceitos com o mesmo Aviso. É apenas C99 e não ANSI? Você pode me indicar a seção exata no rascunho, pois isso não parece ser verdade para o gcc.
Elazar Leibovich
Verifique novamente. Tentei o mesmo com o gcc 4.0.1 e recebo um erro exatamente onde deveria estar. Tente o compilador online da comeau ou o codepad.org também se você não tiver acesso a outros compiladores. Leia o padrão.
dirkgently
2
@ dirkgently, minha verdadeira pergunta é: existe algum efeito no uso do exetrn com declaração de função e, se não existe, por que é possível adicionar extern a uma declaração de função? E a resposta é não, não há efeito, e houve um efeito em compiladores não tão padrão.
Elazar Leibovich
11

As funções embutidas têm regras especiais sobre o que externsignifica. (Observe que as funções embutidas são uma extensão C99 ou GNU; elas não estavam no C. original

Para funções não embutidas, externnão é necessário, pois está ativado por padrão.

Observe que as regras para C ++ são diferentes. Por exemplo, extern "C"é necessário na declaração C ++ das funções C que você chamará do C ++, e existem regras diferentes inline.

user9876
fonte
Esta é a única resposta aqui que está correta e realmente responde à pergunta.
precisa saber é
4

IOW, extern é redundante e não faz nada.

Por isso, 10 anos depois:

Veja commit ad6dad0 , commit b199d71 , commit 5545442 (29 Abr 2019) por Denton Liu ( Denton-L) .
(Incorporado por Junio ​​C Hamano - gitster- in commit 4aeeef3 , 13 de maio de 2019)

*.[ch]: remover externdas declarações de função usandospatch

Houve um empurrão para remover externdas declarações de função.

Remova algumas instâncias de " extern" para declarações de função capturadas pelo Coccinelle.
Observe que o Coccinelle tem alguma dificuldade em processar funções com __attribute__ou varargs, portanto, algumas externdeclarações são deixadas para trás para serem tratadas em um patch futuro.

Este foi o adesivo Coccinelle usado:

  @@
    type T;
    identifier f;
    @@
    - extern
    T f(...);

e foi executado com:

  $ git ls-files \*.{c,h} |
    grep -v ^compat/ |
    xargs spatch --sp-file contrib/coccinelle/noextern.cocci --in-place

Isso nem sempre é simples:

Veja commit 7027f50 (04 set 2019) por Denton Liu ( Denton-L) .
(Mesclado por Denton Liu - Denton-L- in commit 7027f50 , 05 set 2019)

compat/*.[ch]: remover externdas declarações de função usando spatch

Em 5545442 ( *.[ch]: remove externdas declarações de função usando spatch, 2019-04-29, Git v2.22.0-rc0), removemos externos das declarações de função usando, spatchmas excluímos intencionalmente os arquivos, uma compat/vez que alguns são copiados diretamente de um upstream e devemos evitar agitando-os para que a fusão manual de futuras atualizações seja mais simples.

No último commit, determinamos os arquivos que foram retirados de um upstream para que possamos excluí-los e executar spatcho restante.

Este foi o adesivo Coccinelle usado:

@@
type T;
identifier f;
@@
- extern
  T f(...);

e foi executado com:

$ git ls-files compat/\*\*.{c,h} |
    xargs spatch --sp-file contrib/coccinelle/noextern.cocci --in-place
$ git checkout -- \
    compat/regex/ \
    compat/inet_ntop.c \
    compat/inet_pton.c \
    compat/nedmalloc/ \
    compat/obstack.{c,h} \
    compat/poll/

Coccinelle tem alguns problemas para lidar com __attribute__e varargs, portanto, executamos o seguinte para garantir que nenhuma alteração restante seja deixada para trás:

$ git ls-files compat/\*\*.{c,h} |
    xargs sed -i'' -e 's/^\(\s*\)extern \([^(]*([^*]\)/\1\2/'
$ git checkout -- \
    compat/regex/ \
    compat/inet_ntop.c \
    compat/inet_pton.c \
    compat/nedmalloc/ \
    compat/obstack.{c,h} \
    compat/poll/

Observe que, com o Git 2.24 (quarto trimestre de 2019), qualquer espúria externé descartada.

Veja commit 65904b8 (30 set 2019) por Emily Shaffer ( nasamuffin) .
Ajudado por: Jeff King ( peff) .
Veja commit 8464f94 (21 de setembro de 2019) de Denton Liu ( Denton-L) .
Ajudado por: Jeff King ( peff) .
(Incorporado por Junio ​​C Hamano - gitster- in commit 59b19bc , 07 out 2019)

promisor-remote.h: soltar externda declaração de função

Durante a criação deste arquivo, sempre que uma nova declaração de função foi introduzida, ela incluía um extern.
No entanto, a partir de 5545442 ( *.[ch]: remove externfrom declarações de função usando spatch, 2019-04-29, Git v2.22.0-rc0), tentamos ativamente impedir que externos sejam usados ​​em declarações de função porque são desnecessários.

Remova esses espúrios extern.

VonC
fonte
3

A externpalavra-chave informa ao compilador que a função ou variável possui ligação externa - em outras palavras, que é visível em arquivos diferentes daquele em que está definida. Nesse sentido, tem o significado oposto à staticpalavra - chave. É um pouco estranho colocar externno momento da definição, pois nenhum outro arquivo teria visibilidade da definição (ou isso resultaria em várias definições). Normalmente, você coloca externuma declaração em algum momento com visibilidade externa (como um arquivo de cabeçalho) e coloca a definição em outro lugar.

1800 INFORMAÇÃO
fonte
2

declarar uma função extern significa que sua definição será resolvida no momento da vinculação, não durante a compilação.

Diferentemente das funções regulares, que não são declaradas externas, ele pode ser definido em qualquer um dos arquivos de origem (mas não em vários arquivos de origem), caso contrário, você receberá um erro de vinculador dizendo que forneceu várias definições da função) incluindo a declarado extern.So, no caso de o vinculador resolver a definição da função no mesmo arquivo.

Eu não acho que fazer isso seria muito útil, mas fazer esse tipo de experimento fornece uma melhor visão sobre como o compilador e o vinculador da linguagem funcionam.

Rampal Chaudhary
fonte
2
IOW, extern é redundante e não faz nada. Seria muito mais claro se você colocasse dessa maneira.
Elazar Leibovich
@ElazarLeibovich Acabei de encontrar um caso semelhante em nossa base de código e tive a mesma conclusão. Todas as respostas aqui podem ser resumidas em uma única lista. Não tem efeito prático, mas pode ser bom para facilitar a leitura. Prazer em vê-lo on-line e não apenas em meetups :)
Aviv
1

A razão pela qual não tem efeito é que, no momento do link, o vinculador tenta resolver a definição externa (no seu caso extern int f()). Não importa se o encontra no mesmo arquivo ou em um arquivo diferente, desde que seja encontrado.

espero que isso responda sua pergunta.

Umer
fonte
1
Então, por que permitir adicionar externa qualquer função?
Elazar Leibovich
2
Evite colocar spam não relacionado em suas postagens. Obrigado!
Mac