Рассмотрим простой пример программы, состоящей из двух модулей.
Первый модуль - основной, то есть содержащий функцию
main. Назовем его
first.cpp #include <cstdio>
int x = 10;
void f();
int main()
{
f();
return 0;
}
Второй модуль, вспомогательный, содержит определение функции
f. Назовем его
second.c #include <stdio.h>
extern int x;
void f()
{
printf( "Inside f() x = %d\n", x );
}[/pre2
Итак, в этой программе имеется всего лишь одна функция f, объявленная как void f() и переменная с внешним связыванием x.
Казалось бы ничего сложного в этой программе нет, но, увы, этот код не будет компилироваться.
Точнее сказать, он скомпилируется, но редактор связей выдаст сообщения об ошибках,
что он не может найти определения переменной x и функции void f()
Вот как, например, будут выглядеть сообщения об ошибках в MS VC++ 2010
цитата: |
1>second.obj : error LNK2001: неразрешенный внешний символ "_x" 1>first.obj : error LNK2001: неразрешенный внешний символ ""void __cdecl f(void)" (?f@@YAXXZ)" |
|
Посмотрите внимательно на эти сообщения об ошибках редактора связей.
Первое сообщение об ошибке говорит о том, что в модуле
second.obj имеется
неразрешенный внешний символ
_x. Это означает, что редактор связей не нашел
определение для этого символа, объявленного в модуле
second.c.
Второе сообщение об ошибке уже ссылается на основной модуль,
то есть на модуль
first.obj,
и говорит о том, что в этом модуле встретилось объявление функции
void f( void ),
для которой не было найдено определение.
На первый взгляд эти сообщения выглядят странными, так как имеется определение переменной
x в первом модуле
first.cpp,
а также определение функции
f во втором модуле
second.c. В чем же проблема?
Проблема состоит в том, что первый модуль компилировался как программа,
написанная на C++, так как имя модуля имеет расширение
.cpp,
а второй модуль компилировался как программа, написанная на C, так как имя второго модуля имеет расширение
.c.
И чтобы связать эти два модуля воедино, мы должны были бы сказать,
что в обоих модулях объект
x и функция
f соответственно означают одинаковые сущности.
Однако пока мы этого не сделаем, редактор связей будет считать их различными сущностями.
То есть переменная
x, определенная в модуле
first.cpp как int x = 10; является отдельным объектом,
никак не связанным с объевлением переменной
x в модуле
second.c. То же самое справедливо и для объявлений функции
f.
По умолчанию в программах, написанных на C++, все функции, их типы,
а также переменные с внешним связыванием имеют C++ языковое связывание.
Внешние символы программы, написанной на C компилятор C++ рассматривает как внешние символы,
имеющие C языковое связывание.
Согласно параграфу №1 раздела 7.5 Linkage specifications стандарта C++:
цитата: |
1 All function types, function names with external linkage, and variable names with external linkage have a language linkage. |
|
В этом же параграфе далее написано, что
цитата: |
Two function types with different language linkages are distinct types even if they are otherwise identical. |
|
Из чего следует, что в нашем исходном примере имеются объявления двух различных функций
f,
хотя они во всем совпадают кроме языкового связывания.
Как сделать теперь так, чтобы исходная программа примера успешно компилировалась и выполнялась?
Для этого достаточно указать в модуле
first.cpp для объявления переменной
x и фукнции
f спецификацию связывания для языка C.
Сделать это можно следующим образом. как показано в коде ниже
[pre2] #include <cstdio>
extern "C"
{
int x = 10;
void f();
}
int main()
{
f();
return 0;
}
Теперь программа успешно скомпилируется, скомпонуется и выполнится.
В этой программе объявления для переменной
x и функции
f соответственно объявляют одни и те же сущности в обоих модулях.
На этом корректная работа компилятора
MS VC++ 2010 в отношении языкового связывания заканчивается,
и компилятор начинает полностью игнорировать другие положения стандарта, описанные в разделе 7.5 Linkage specifications.
Рассмотрим, какие именно положения этого раздела стандарта С++ компилятор
MS VC++ 2010 не соблюдает,
а заодно и сами ознакомимся с этими положениями.
Согласно параграфу №6
цитата: |
"An entity with C language linkage shall not be declared with the same name as an entity in global scope, unless both declarations denote the same entity; no diagnostic is required if the declarations appear in different translation units." |
|
Из этого параграфа следует, что нельзя объявлять в каком-нибудь пространстве имен сущность с языковым связыванием C,
если ее имя совпадает с именем в объявление некоторой сущности в глобальном пространстве имен,
если только данное объявление в глобальном пространстве имен не означает ту же самую сущность,
то есть имеет то же самое языковое связывание.
При этом компилятор не обязан выдавать диагностическое сообщение только в том случае, если эти два объявления находятся в различных единицах трансляции.
Однако компилятор
MS VC++ 2010 не выдает никакого диагностического сообщения, даже если эти два объявления находятся в одной единице трансляции.
#include "stdafx.h"
#include <cstdio>
void f();
extern "C"
{
int x = 10;
namespace N1
{
void f();
}
}
int _tmain(int argc, _TCHAR* argv[])
{
N1::f();;
return 0;
}
В этом новом модуле
first.cpp имя функции
f,
объявленной в пространстве имен
N1, с языковым связыванием C совпадает с таким же именем
f,
объявленнном в глобальном пространстве имен, с языковым связыванием C++.
Эти два имени
f объявляют разные сущности, так как одно объявление имеет спецификацию связывания "C++" (по умолчанию для объявления в глобальном пространстве имен),
а другое - спецификацию связывания "C" (для объявления в пространстве имен
N1).
А следовательно согласно процитированному параграфу стандарта компилятор должен был выдать диагностическое соообщеение, так как оба объявления нахоодятся в одной единице трансляции.
Однако компилятор
MS VC++ 2010 этого не делает.
Код успешно компилирруется и выполгяется при условии, что вы не забыли включить в проект модуль
second.c.
В этом же параграфе №6 стандарта C++ далее написано:
цитата: |
A variable with C language linkage shall not be declared with the same name as a function with C language linkage (ignoring the namespace names that qualify the respective names); no diagnostic is required if the declarations appear in different translation units. |
|
То есть нельзя объявлять переменную со спецификацией связывания "C" с тем же самым именем, что и имя функции с такой же спецификацией связывания "C", даже если эти объявления находятся в различных пространствах имен.
Однако компилятор
MS VC++ 2010 игнорирует это положение стандарта.
Вот соответствующий демонстрационный пример.
#include "stdafx.h"
namespace N1
{
extern "C" int i;
}
namespace N2
{
extern "C" int i();
}
int _tmain(int argc, _TCHAR* argv[])
{
return 0;
}
Этот код успешно компилируется компилятором
MS VC++ 2010, хотя в пространстве имен
N! имеется объявление переменной
i со спецификацией связывания "C",
совпадающее с именем функции, объявленной в пространстве имен
N2, и также имеющей спецификацию связывания "C".
Следующий пример некорректной работы компилятора
MS VC++ 2010.
Параграф №7 гласит:
цитата: |
7 A declaration directly contained in a linkage-specification is treated as if it contains the extern specifier (7.1.1) for the purpose of determining the linkage of the declared name and whether it is a definition. Such a declaration shall not specify a storage class. |
|
И в этом же параграфе приведен пример нерпавильного объявления, когда спецификатор класса памяти указывается одновременно с со спецификацией связывания:
цитата: |
extern "C" static void g(); // error |
|
Однако компилятор
MS VC++ 2010 никаких диагностических сообщений для этой некорректной конструкции не выдает.
#include "stdafx.h"
extern "C" static void g();
int _tmain(int argc, _TCHAR* argv[])
{
return 0;
}
Если вы попробуете этот же код скомпилировать компилятором
GCC 4.7.1, то сразу же получите сообщение об ошибке.
И, наконец, последний пример, когда оба компилятора, и
MS VC++ 2010, и
GCC 4.7.1, ведут себя некорректно, то есть имеют баг.
Как уже говорилось выше согласно параграфу №1
цитата: |
Two function types with different language linkages are distinct types even if they are otherwise identical. |
|
В параграфе №8 есть уточняющее примечание:
цитата: |
8 [ Note: Because the language linkage is part of a function type, when a pointer to C function (for example) is dereferenced, the function to which it refers is considered a C function. —end note ] |
|
Тем не менее оба указанных компилятора это положение стандарта не соблюдают. Следующий некорректный код они успешно компилируют:
#include "stdafx.h"
#include <cstdio>
extern "C"
{
static void h() { std::printf( "void h()\n" ); }
}
void ( *pf )() = h;
int _tmain(int argc, _TCHAR* argv[])
{
pf();
return 0;
}
В этом примере тип функции
h имеет спецификацию связывания "C", в то время как указатель
pf является указателем на функцию, имеющую спецификацию связывания "C++". Так как типы функций различны так как у них различны спецификации связывания (смотрите выдержку из параграфа №1), то нельзя указатель на функцию с одним спецификатором связывания присвоить указатель на функцию с другим спецификатором связывания, даже если во всем остальном они совпадают.
P.S. Если у вас есть собственные, вами обнаруженные баги компиляторов относительно языкового связывания, то вы можете дополнить эту тему своими примерами.