Форум » C/C++ » Спецификации связывания ( Linkage specifications or Language linkage): баги MS VC++2010 и GCC 4.7.1 » Ответить

Спецификации связывания ( Linkage specifications or Language linkage): баги MS VC++2010 и GCC 4.7.1

Сыроежка: Рассмотрим простой пример программы, состоящей из двух модулей. Первый модуль - основной, то есть содержащий функцию main. Назовем его first.cpp [pre2] #include <cstdio> int x = 10; void f(); int main() { f(); return 0; }[/pre2] Второй модуль, вспомогательный, содержит определение функции f. Назовем его second.c [pre2] #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 [quote]1>second.obj : error LNK2001: неразрешенный внешний символ "_x" 1>first.obj : error LNK2001: неразрешенный внешний символ ""void __cdecl f(void)" (?f@@YAXXZ)"[/quote] Посмотрите внимательно на эти сообщения об ошибках редактора связей. Первое сообщение об ошибке говорит о том, что в модуле 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++: [quote]1 All function types, function names with external linkage, and variable names with external linkage have a language linkage.[/quote] В этом же параграфе далее написано, что [quote]Two function types with different language linkages are distinct types even if they are otherwise identical.[/quote] Из чего следует, что в нашем исходном примере имеются объявления двух различных функций f, хотя они во всем совпадают кроме языкового связывания. Как сделать теперь так, чтобы исходная программа примера успешно компилировалась и выполнялась? Для этого достаточно указать в модуле first.cpp для объявления переменной x и фукнции f спецификацию связывания для языка C. Сделать это можно следующим образом. как показано в коде ниже [pre2] #include <cstdio> extern "C" { int x = 10; void f(); } int main() { f(); return 0; }[/pre2] Теперь программа успешно скомпилируется, скомпонуется и выполнится. В этой программе объявления для переменной x и функции f соответственно объявляют одни и те же сущности в обоих модулях. На этом корректная работа компилятора MS VC++ 2010 в отношении языкового связывания заканчивается, и компилятор начинает полностью игнорировать другие положения стандарта, описанные в разделе 7.5 Linkage specifications. Рассмотрим, какие именно положения этого раздела стандарта С++ компилятор MS VC++ 2010 не соблюдает, а заодно и сами ознакомимся с этими положениями. Согласно параграфу №6 [quote]"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."[/quote] Из этого параграфа следует, что нельзя объявлять в каком-нибудь пространстве имен сущность с языковым связыванием C, если ее имя совпадает с именем в объявление некоторой сущности в глобальном пространстве имен, если только данное объявление в глобальном пространстве имен не означает ту же самую сущность, то есть имеет то же самое языковое связывание. При этом компилятор не обязан выдавать диагностическое сообщение только в том случае, если эти два объявления находятся в различных единицах трансляции. Однако компилятор MS VC++ 2010 не выдает никакого диагностического сообщения, даже если эти два объявления находятся в одной единице трансляции. [pre2]#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; }[/pre2] В этом новом модуле first.cpp имя функции f, объявленной в пространстве имен N1, с языковым связыванием C совпадает с таким же именем f, объявленнном в глобальном пространстве имен, с языковым связыванием C++. Эти два имени f объявляют разные сущности, так как одно объявление имеет спецификацию связывания "C++" (по умолчанию для объявления в глобальном пространстве имен), а другое - спецификацию связывания "C" (для объявления в пространстве имен N1). А следовательно согласно процитированному параграфу стандарта компилятор должен был выдать диагностическое соообщеение, так как оба объявления нахоодятся в одной единице трансляции. Однако компилятор MS VC++ 2010 этого не делает. Код успешно компилирруется и выполгяется при условии, что вы не забыли включить в проект модуль second.c. В этом же параграфе №6 стандарта C++ далее написано: [quote]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.[/quote] То есть нельзя объявлять переменную со спецификацией связывания "C" с тем же самым именем, что и имя функции с такой же спецификацией связывания "C", даже если эти объявления находятся в различных пространствах имен. Однако компилятор MS VC++ 2010 игнорирует это положение стандарта. Вот соответствующий демонстрационный пример. [pre2] #include "stdafx.h" namespace N1 { extern "C" int i; } namespace N2 { extern "C" int i(); } int _tmain(int argc, _TCHAR* argv[]) { return 0; }[/pre2] Этот код успешно компилируется компилятором MS VC++ 2010, хотя в пространстве имен N! имеется объявление переменной i со спецификацией связывания "C", совпадающее с именем функции, объявленной в пространстве имен N2, и также имеющей спецификацию связывания "C". Следующий пример некорректной работы компилятора MS VC++ 2010. Параграф №7 гласит: [quote]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.[/quote] И в этом же параграфе приведен пример нерпавильного объявления, когда спецификатор класса памяти указывается одновременно с со спецификацией связывания: [quote]extern "C" static void g(); // error[/quote] Однако компилятор MS VC++ 2010 никаких диагностических сообщений для этой некорректной конструкции не выдает. [pre2] #include "stdafx.h" extern "C" static void g(); int _tmain(int argc, _TCHAR* argv[]) { return 0; }[/pre2] Если вы попробуете этот же код скомпилировать компилятором GCC 4.7.1, то сразу же получите сообщение об ошибке. И, наконец, последний пример, когда оба компилятора, и MS VC++ 2010, и GCC 4.7.1, ведут себя некорректно, то есть имеют баг. Как уже говорилось выше согласно параграфу №1 [quote]Two function types with different language linkages are distinct types even if they are otherwise identical.[/quote] В параграфе №8 есть уточняющее примечание: [quote]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 ][/quote] Тем не менее оба указанных компилятора это положение стандарта не соблюдают. Следующий некорректный код они успешно компилируют: [pre2] #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; }[/pre2] В этом примере тип функции h имеет спецификацию связывания "C", в то время как указатель pf является указателем на функцию, имеющую спецификацию связывания "C++". Так как типы функций различны так как у них различны спецификации связывания (смотрите выдержку из параграфа №1), то нельзя указатель на функцию с одним спецификатором связывания присвоить указатель на функцию с другим спецификатором связывания, даже если во всем остальном они совпадают. P.S. Если у вас есть собственные, вами обнаруженные баги компиляторов относительно языкового связывания, то вы можете дополнить эту тему своими примерами.

Ответов - 0



полная версия страницы