Jak kompilatory c ++ znajdują zmienną zewnętrzną?

15

Kompiluję ten program przez g ++ i clang ++. Jest różnica:
g ++ drukuje 1, ale clang ++ drukuje 2.
Wygląda na to, że
g ++: zmienna zewnętrzna jest zdefiniowana w najkrótszym zakresie.
clang ++: zmienna zewnętrzna jest zdefiniowana w najkrótszym globalnym zakresie.

Czy specyfikacja C ++ ma jakieś specyfikacje na ten temat?

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;

wersja: g ++: 7.4.0 / clang ++: 10.0.0
kompilacja: $ (CXX) main.cpp other.cpp -o extern.exe

Eddie Kuo
źródło
4
Kompilator nie robi nic z zewnętrznym, poza oznaczeniem ich jako zmiennych, które mają odwołania zewnętrzne, linker próbuje rozwiązać łącza między wszystkimi skompilowanymi plikami obiektowymi.
SPlatten
Doskonałe (jeśli dziwne) pytanie! Bawiąc się z twoim kodem MSVCi clang-cl(oba dają 2), wydaje się, że extern int ijest on całkowicie ignorowany przez oba: nawet jeśli nie połączę other.cpppliku, program się buduje i uruchamia.
Adrian Mole
1
@SPlatten Prawdopodobnie, ponieważ linker nie musi „rozpoznawać” odniesienia i, nie próbuje.
Adrian Mole
3
Powiązany stary zawieszony błąd GCC można znaleźć tutaj i odpowiadający mu otwarty błąd Clanga tutaj
orzech

Odpowiedzi:

11

[basic.link/7] powinien być odpowiednią częścią standardu. W bieżącym projekcie mówi:

Nazwa funkcji zadeklarowanej w zakresie bloków i nazwa zmiennej zadeklarowanej przez externdeklarację zakresu blokowego mają powiązanie. Jeśli taka deklaracja jest dołączona do nazwanego modułu, program jest źle sformułowany. Jeśli widoczna jest deklaracja encji powiązanej, ignorowanie encji zadeklarowanych poza najbardziej wewnętrznym obejmującym obszarem przestrzeni nazw, tak że deklaracja zakresu bloków byłaby (prawdopodobnie źle sformułowana) deklaracją, gdyby dwie deklaracje pojawiły się w tym samym regionie deklaratywnym, blokowa deklaracja zakresu deklaruje ten sam podmiot i otrzymuje powiązanie z poprzednią deklaracją. Jeśli istnieje więcej niż jedna taka pasująca jednostka, program jest źle sformułowany. W przeciwnym razie, jeśli nie zostanie znaleziony pasujący byt, encja o zasięgu blokowym otrzymuje połączenie zewnętrzne.Jeśli w jednostce tłumaczeniowej ten sam podmiot zostanie zadeklarowany z połączeniem wewnętrznym i zewnętrznym, program jest źle sformułowany.

Zauważ, że kolejny przykład prawie dokładnie pasuje do twojego przypadku:

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
  }
}

Tak więc program powinien być źle sformułowany. Wyjaśnienie znajduje się poniżej przykładu:

Bez deklaracji w linii nr 2 deklaracja w linii nr 3 łączyłaby się z deklaracją w linii nr 1. Ponieważ deklaracja z wewnętrznym powiązaniem jest ukryta, # 3 otrzymuje zewnętrzne powiązanie, co powoduje, że program jest źle sformułowany.

Daniel Langr
źródło
Program w tym przykładzie jest źle ukształtowany, ponieważ nie ma żadnego powiązania z zewnętrzną i zdefiniowany w dowolnym miejscu. Nie jest tak w przypadku przykładu OP.
n. „zaimki” m.
3
@ n.'Pronouns'm. Ale reguła dotyczy jednostki tłumaczeniowej: jeśli w jednostce tłumaczeniowej zadeklarowany jest ten sam podmiot z połączeniem wewnętrznym i zewnętrznym, program jest źle sformułowany. .
Daniel Langr
2
Odpowiedź dotyczy tylko C ++ 17 i nowszych, patrz rozwiązanie problemu CWG 426 . Wydaje mi się, że GCC miał rację przed tą zmianą.
orzech
OK, wygląda na to, że czytałem poprzednią edycję standardu.
n. „zaimki” m.