Как работает внешняя декларация "C"?
Я беру курс языков программирования, и мы говорим о extern "C"
декларация.
Как это объявление работает на более глубоком уровне, кроме "оно взаимодействует с C и C++"? Как это влияет на привязки, которые также имеют место в программе?
9 ответов
extern "C"
используется для того, чтобы следующие символы не были искажены (украшены).
Пример:
Допустим, у нас есть следующий код в файле с именем test.cpp
:
extern "C" {
int foo() {
return 1;
}
}
int bar() {
return 1;
}
Если вы бежите gcc -c test.cpp -o test.o
Посмотрите на названия символов:
00000010 T _Z3barv
00000000 т фу
foo()
держит свое имя.
Давайте рассмотрим типичную функцию, которая может компилироваться как в C, так и в C++:
int Add (int a, int b)
{
return a+b;
}
Теперь в C функция называется "_Add" внутри. Принимая во внимание, что функция C++ внутренне называется чем-то совершенно другим, используя систему, называемую mangling. Это в основном способ назвать функцию так, чтобы одна и та же функция с разными параметрами имела другое внутреннее имя.
Поэтому, если Add() определен в add.c, а у вас есть прототип в add.h, у вас возникнет проблема, если вы попытаетесь включить add.h в файл C++. Поскольку код C++ ищет функцию с именем, отличным от имени в add.c, вы получите ошибку компоновщика. Чтобы обойти эту проблему, вы должны добавить add.c этим методом:
extern "C"
{
#include "add.h"
}
Теперь код C++ будет связываться с _Add вместо искаженной версии имени C++.
Это одно из применений выражения. В итоге, если вам нужно скомпилировать код, который строго является C, в программе на C++ (с помощью оператора include или каким-либо другим способом), вам нужно обернуть его объявлением extern "C" { ... }.
Когда вы помечаете блок кода с помощью extern "C", вы говорите системе использовать связь в стиле C.
Это, в основном, влияет на то, как компоновщик искажает имена. Вместо использования искажения имен в стиле C++ (которое более сложно для поддержки перегрузок операторов), вы получаете стандартное именование в стиле C из компоновщика.
Следует отметить, что extern "C"
также изменяет типы функций. Он не только изменяет вещи на более низких уровнях:
extern "C" typedef void (*function_ptr_t)();
void foo();
int main() { function_ptr_t fptr = &foo; } // error!
Тип &foo
не совпадает с типом, который определяет typedef (хотя код принят некоторыми, но не всеми компиляторами).
В C++ имя / символ функций фактически переименовывается во что-то другое, так что разные классы / пространства имен могут иметь функции с одинаковыми сигнатурами. В C все функции определены глобально, и такой настраиваемый процесс переименования не требуется.
Чтобы заставить C++ и C общаться друг с другом, "extern C" указывает компилятору не использовать соглашение C.
extern C влияет на искажение имен компилятором C++. Это способ заставить компилятор C++ не искажать имена, или, скорее, манипулировать ими так же, как это делает компилятор C. Так он взаимодействует с C и C++.
В качестве примера:
extern "C" void foo(int i);
позволит функции быть реализованной в модуле C, но позволит вызывать ее из модуля C++.
Проблема возникает при попытке заставить модуль C вызывать функцию C++ (очевидно, C не может использовать классы C++), определенные в модуле C++. Компилятор C не любит extern "C"
,
Так что вам нужно использовать это:
#ifdef __cplusplus
extern "C" {
#endif
void foo(int i);
#ifdef __cplusplus
}
#endif
Теперь, когда это появляется в заголовочном файле, компиляторы C и C++ будут довольны объявлением, и теперь оно может быть определено в модуле C или C++ и может вызываться как кодом C, так и C++.
extern "C" обозначает, что в прилагаемом коде используются ссылки в стиле C и искажение имен. C++ использует более сложный формат искажения имен. Вот пример:
http://en.wikipedia.org/wiki/Name_mangling
int example(int alpha, char beta);
в С: _example
в C++: __Z7exampleic
Обновление: как GManNickG отмечает в комментариях, шаблон искажения имени зависит от компилятора.
extern "C" - это ключевое слово для объявления функции с привязками C, потому что компилятор C и компилятор C++ преобразуют исходный код в объектный файл в другую форму:
Например, фрагмент кода выглядит следующим образом:
int _cdecl func1(void) {return 0}
int _stdcall func2(int) {return 0}
int _fastcall func3(void) {return 1}
32-битные C-компиляторы переведут код в форму следующим образом:
_func1
_func2@4
@func3@4
в cdecl func1 будет переводиться как "_name"
в stdcall func2 будет переводиться как '_name @ X'
в fastcall func2 будет переводиться как '@ name @ X'
"X" означает количество байтов параметров в списке параметров.
64-битное соглашение на Windows не имеет лидирующего подчеркивания
В C++ представлены классы, шаблоны, пространства имен и перегрузка операторов, поскольку не допускается две функции с одинаковым именем, компилятор C++ предоставляет информацию о типе в имени символа,
например, фрагмент кода выглядит следующим образом:
int func(void) {return 1;}
int func(int) {return 0;}
int func_call(void) {int m=func(), n=func(0);}
Компилятор C++ переведет код следующим образом:
int func_v(void) {return 1;}
int func_i(int) {return 0;}
int func_call(void) {int m=_func_v(), n=_func_i(0);}
"_v" и "_i" - это информация о типах "void" и "int"
Вот цитата из MSDN
"Ключевое слово extern объявляет переменную или функцию и указывает, что она имеет внешнюю связь (ее имя видно из файлов, отличных от того, в котором она определена). При изменении переменной extern указывает, что переменная имеет статическую длительность (она выделяется когда программа начинается и освобождается, когда программа заканчивается). Переменная или функция могут быть определены в другом исходном файле или позже в том же файле. По умолчанию объявления переменных и функций в области видимости файла являются внешними. "
http://msdn.microsoft.com/en-us/library/0603949d%28VS.80%29.aspx