On-line: гостей 0. Всего: 0 [подробнее..]
Программисты всех стран, объединяйтесь!

АвторСообщение



ссылка на сообщение  Отправлено: 19.09.12 17:19. Заголовок: Спецификации связывания ( Linkage specifications or Language linkage): баги MS VC++2010 и GCC 4.7.1


Рассмотрим простой пример программы, состоящей из двух модулей.

Первый модуль - основной, то есть содержащий функцию 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. Если у вас есть собственные, вами обнаруженные баги компиляторов относительно языкового связывания, то вы можете дополнить эту тему своими примерами.

Спасибо: 0 
ПрофильЦитата Ответить
Новых ответов нет


Ответ:
1 2 3 4 5 6 7 8 9
большой шрифт малый шрифт надстрочный подстрочный заголовок большой заголовок видео с youtube.com картинка из интернета картинка с компьютера ссылка файл с компьютера русская клавиатура транслитератор  цитата  кавычки моноширинный шрифт моноширинный шрифт горизонтальная линия отступ точка LI бегущая строка оффтопик свернутый текст

показывать это сообщение только модераторам
не делать ссылки активными
Имя, пароль:      зарегистрироваться    
Тему читают:
- участник сейчас на форуме
- участник вне форума
Все даты в формате GMT  3 час. Хитов сегодня: 6
Права: смайлы да, картинки да, шрифты да, голосования нет
аватары да, автозамена ссылок вкл, премодерация откл, правка нет