Como os compiladores c ++ encontram uma variável externa?

15

Eu compilo este programa por g ++ e clang ++. Há uma diferença:
g ++ imprime 1, mas clang ++ imprime 2.
Parece que
g ++: a variável externa é definida no escopo mais curto.
clang ++: a variável externa é definida no menor escopo global.

A especificação C ++ tem alguma especificação sobre isso?

main.cpp

#include <iostream>
static int i;
static int *p = &i;

int main() {
  int i;
  {
    extern int i;
    i = 1;
    *p = 2;
    std::cout << i << std::endl;
  }
}

other.cpp

int i;

versão: g ++: 7.4.0 / clang ++: 10.0.0
compilação: $ (CXX) main.cpp other.cpp -o extern.exe

eddie kuo
fonte
4
O compilador não faz nada com o extern, exceto marcá-los como variáveis ​​que possuem referências externas; o vinculador é o que tenta resolver os links entre todos os arquivos de objetos compilados.
SPlatten
Uma excelente (se estranha) pergunta! Brincando com seu código MSVCe clang-cl(ambos dão 2), parece que ele extern int ié completamente ignorado por ambos: mesmo que eu não vincule o other.cpparquivo, o programa cria e executa.
Adrian Mole
11
@SPlatten Presumivelmente, como o vinculador não precisa 'resolver' a referência i, ele não tenta.
Adrian Mole
3
O bug do GCC suspenso antigo relacionado pode ser encontrado aqui e o bug do Clang aberto correspondente aqui
walnut

Respostas:

11

[basic.link/7] deve ser a parte relevante da Norma. No rascunho atual, ele diz:

O nome de uma função declarada no escopo do bloco e o nome de uma variável declarada por uma externdeclaração de escopo do bloco têm ligação. Se uma declaração desse tipo estiver anexada a um módulo nomeado, o programa está incorreto. Se houver uma declaração visível de uma entidade com ligação, ignorando as entidades declaradas fora do escopo de espaço para nome mais interno, de modo que a declaração de escopo do bloco seja uma redeclaração (possivelmente mal formada) se as duas declarações aparecerem na mesma região declarativa, o A declaração de escopo do bloco declara a mesma entidade e recebe o vínculo da declaração anterior. Se houver mais de uma entidade correspondente, o programa está mal formado. Caso contrário, se nenhuma entidade correspondente for encontrada, a entidade do escopo do bloco receberá ligação externa.Se, dentro de uma unidade de tradução, a mesma entidade for declarada com vínculo interno e externo, o programa será mal formado.

Observe que o exemplo subsequente corresponde quase exatamente ao seu caso:

static void f();
extern "C" void h();
static int i = 0;               // #1
void g() {
  extern void f();              // internal linkage
  extern void h();              // C language linkage
  int i;                        // #2: i has no linkage
  {
    extern void f();            // internal linkage
    extern int i;               // #3: external linkage, ill-formed
  }
}

Portanto, o programa deve estar mal formado. A explicação está abaixo do exemplo:

Sem a declaração na linha 2, a declaração na linha 3 seria vinculada à declaração na linha 1. Como a declaração com ligação interna está oculta, no entanto, o nº 3 recebe ligação externa, tornando o programa mal formado.

Daniel Langr
fonte
O programa no exemplo está mal formado porque não há i com ligação externa definida em qualquer lugar. Este não é o caso do exemplo do OP.
n. 'pronomes' m.
3
@ n.'pronouns'm. Mas a regra se aplica a uma unidade de tradução: se, dentro de uma unidade de tradução, a mesma entidade for declarada com vínculo interno e externo, o programa será mal formado. .
Daniel Langr 7/01
2
A resposta se aplica somente ao C ++ 17 e posterior, consulte a resolução do problema 426 do CWG . Parece-me que o GCC estava correto antes dessa mudança.
walnut
OK, parece que eu estava lendo a edição anterior do padrão.
n. 'pronomes' m.