Внешняя связь для имени в безымянном пространстве имен

Согласно пункту 3.5/4 стандарта C++:

Пространство имен без имени или пространство имен, объявленное прямо или косвенно в пространстве имен без имени, имеет внутреннюю связь.

Одновременно в пункте 7.3.1.1 мы имеем примечание 96):

Хотя сущности в безымянном пространстве имен могут иметь внешнюю связь, они фактически квалифицируются по имени, уникальному для их единицы перевода, и, следовательно, никогда не будут видны из любой другой единицы перевода.

Как явно сделать внешнюю связь для имени внутри безымянного пространства имен и как проверить, что эта связь на самом деле является внешней, если Стандарт гарантировал, что нет способа получить доступ к имени, определенному внутри безымянного пространства имен, из другого модуля перевода?

В каких случаях полезно делать явную внешнюю связь для имени в безымянном пространстве имен?

3 ответа

Решение

Как явно сделать внешнюю связь для имени в безымянном пространстве имен

Единственный способ, которым я могу придумать, это дать ему связь на языке C, чтобы его имя связи игнорировало квалификацию пространства имен:

namespace {
  extern void f() { }      // has internal linkage despite 'extern'
  extern "C" void g() { }  // ignores linkage of namespace
}
void (*p)() = f;  // ensure 'f' won't be optimized away

(Строгое чтение стандарта предполагает, что g должна иметь внутреннюю связь, но компиляторы, похоже, этого не делают.)

и как проверить, что эта связь на самом деле внешняя, если Стандарт гарантировал, что нет способа получить доступ к имени, определенному внутри безымянного пространства имен, из другого модуля перевода?

Обычно компиляторы ELF реализуют внутреннюю связь с неглобальными символами, поэтому вы можете скомпилировать код и проверить объектный файл:

$ g++ -c linkage.cc
$ nm linkage.o
0000000000000000 t _ZN12_GLOBAL__N_11fEv
0000000000000007 T g
0000000000000000 D p

Искаженное имя безымянного пространства имен может варьироваться в зависимости от компилятора, но его деформирование покажет:

$ nm -C  linkage.o
0000000000000008 t (anonymous namespace)::f()
0000000000000000 T g
0000000000000000 D p

Строчные буквы t показывает, что f имеет локальную видимость, что означает, что он не может быть связан с другими объектными файлами. Прописные буквы T показывает, что g имеет внешнюю связь.

Это не гарантируется стандартом, поскольку видимость ELF не является частью стандарта C++, и некоторые компиляторы реализуют связывание без использования видимости даже на платформах ELF, например, компилятор EDG создает глобальный символ для того же кода:

$ nm linkage.o
0000000000000008 T _ZN23_GLOBAL__N__7_link_cc_p1fEv
0000000000000004 C __EDGCPFE__4_9
0000000000000000 T g
0000000000000000 D p
$ nm -C linkage.o
0000000000000008 T (anonymous namespace)::f()
0000000000000004 C __EDGCPFE__4_9
0000000000000000 T g
0000000000000000 D p

Итак, используя extern "C" позволяет вам дать внешнюю связь имени, даже если она появляется в безымянном пространстве имен, но это не делает примечание правильным, потому что вы можете ссылаться на это имя из других единиц перевода, потому что оно не использует безымянную область пространства имен. Это наводит меня на мысль, что заметка является просто пережитком C++03, когда сущности в безымянных пространствах имен не имеют автоматически внутренней связи, и заметка должна быть исправлена ​​или удалена (и действительно, TC указывает, что она уже была удалена DR 1603).

В каких случаях полезно делать явную внешнюю связь для имени в безымянном пространстве имен?

Я не могу вспомнить ни одного случая, когда это полезно.

Ре

В каких случаях полезно делать явную внешнюю связь для имени в безымянном пространстве имен?

Необходимость внешней связи была важна для шаблонов C++03. Например, указатель на функцию в качестве аргумента шаблона должен быть указателем на функцию внешней связи. Например, следующее не будет компилироваться с компилятором C++03:

template< void(*f)() >
void tfunc() { f(); }

#include <stdio.h>
static void g() { printf( "Hello!\n" ); }

int main()
{
    tfunc<g>();
}

Он прекрасно компилируется с помощью компилятора C++11.

Таким образом, в C++11 механизм анонимного пространства имен для внешней связи, но без конфликтов имен между единицами перевода, технически необходим только для классов. У класса есть внешняя связь. Не хотелось бы выбирать имена классов, которые гарантированно не встречались в других единицах перевода.

В C++11 правила изменились не только для параметров шаблона, но и для того, имеют ли вещи в анонимном пространстве имен внешнюю связь или нет. В C++03 анонимное пространство имен имело формально внешнюю связь, возможно, если только оно не находится внутри анонимного пространства имен (C++03 §3.5/4 last dash + C++03 §7.3.1.1/1). В C++11 анонимное пространство имен имеет формально внутреннюю связь.

Это не имеет значения для компоновщика, потому что нет связи пространств имен, но это важно как формальное устройство для описания взаимосвязи вещей:

C++11 §3.5 / 4:

" Безымянное пространство имен или пространство имен, объявленное прямо или косвенно в безымянном пространстве имен, имеет внутреннюю связь. Все остальные пространства имен имеют внешнюю связь. Имя, имеющее область пространства имен, которой не была предоставлена ​​внутренняя связь выше, имеет ту же связь, что и окружающее пространство имен, если это имя
- Переменная; или же
- функция; или же
- именованный класс (раздел 9) или безымянный класс, определенный в typedef объявление, в котором класс имеет typedef имя для целей связи (7.1.3); или же
- именованное перечисление (7.2) или неназванное перечисление, определенное в typedef объявление, в котором перечисление имеет имя typedef для целей связывания (7.1.3); или же
- перечислитель, принадлежащий перечислению со связью; или же
Шаблон.


Прежде чем перейти к другим вашим вопросам, стоит отметить, что эта цитата из стандарта,

" Хотя сущности в безымянном пространстве имен могут иметь внешнюю связь, они фактически квалифицируются по имени, уникальному для их единицы перевода, и, следовательно, никогда не будут видны из любой другой единицы перевода.

просто неправильно, потому что extern "C" сущность видна из других единиц перевода независимо от того, в каком пространстве имен она объявлена.

К счастью, насколько я помню, заметки не являются нормативными, то есть они не определяют язык.


ре

Как явно сделать внешнюю связь для имени в безымянном пространстве имен

просто объявить не const переменная или функция, как extern, Вы можете объявить не const переменная или функция, как extern "C" делая связь внешней, но в то же время делая пространства имен неактуальными для связи: в языке C их нет.

namespace {
    extern "C" void foo() {}    // Extern linkage
}  // namespace <anon>

void bar() {}  // Also extern linkage, but visible to other TUs.

ре

Как проверить, что связь на самом деле внешняя

ну, связь влияет на такие вещи, как возможные конфликты с правилом единого определения, часто сокращаемым как " ODR ", которое в C++11 - §3.2.

Итак, один из способов увидеть связь в действии - это связать два объектных файла, сгенерированных из вышеуказанного источника, как если бы у вас было два модуля перевода с одним и тем же исходным кодом:

C: \ my \ forums \ so \ 088> g ++ -c anon.cpp -o xo & g++ -c anon.cpp -o yo

C: \ my \ forums \ so \ 088> g ++ main.cpp xo yo
yo:anon.cpp:(.text+0x0): множественное определение `foo'
xo:anon.cpp:(.text+0x0): сначала определено здесь
yo:anon.cpp:(.text+0x7): множественное определение `bar()'
xo:anon.cpp:(.text+0x7): сначала определено здесь
collect2.exe: ошибка: ld вернул 1 состояние выхода

C:\my\forums\so\088> _

Компоновщик жалуется на множественные определения foo потому что, как и в случае с привязкой к языку C, для линкера это выглядит как inline член внешней связи глобального пространства имен с двумя (возможно, конфликтующими) определениями.

Согласно стандарту [3.5/2]:

Когда имя имеет внешнюю связь, обозначаемая им сущность может именоваться именами из областей действия других единиц перевода или из других областей той же единицы перевода

а также

Когда имя имеет внутреннюю связь, обозначаемая им сущность может именоваться именами из других областей в том же модуле перевода.

Таким образом, в основном, если вы можете ссылаться на что-то в единице перевода, отличной от той, в которой это что-то было определено, тогда это имеет внешнюю связь, в противном случае она имеет внутреннюю связь. Итак, учитывая примечание от вопроса:

Хотя сущности в безымянном пространстве имен могут иметь внешнюю связь, они фактически квалифицируются по имени, уникальному для их единицы перевода, и, следовательно, никогда не будут видны из любой другой единицы перевода.

У нас практически есть ситуация, в которой у нас есть имя, но мы его не знаем, поэтому, как бы мы ни старались, мы не можем ссылаться на него из другого блока перевода. И это делает его неотличимым от того, что имеет внутреннюю связь. Так что, на мой взгляд, это всего лишь жонглирование словами - если вы не можете отличить одну ситуацию от другой, тогда они одинаковы.

Другие вопросы по тегам