13 ответов:
extern " C "делает имя функции в C++ с привязкой" C "(компилятор не искажает имя), чтобы код клиента C мог ссылаться на (т. е. использовать) вашу функцию, используя совместимый с " C " файл заголовка, который содержит только объявление вашей функции. Ваше определение функции содержится в двоичном формате (который был скомпилирован вашим компилятором C++), что клиентский компоновщик " C "будет ссылаться на использование имени "C".
поскольку C++ имеет перегрузку имен функций, а C - нет, C++ компилятор не может просто использовать имя функции в качестве уникального идентификатора для ссылки, поэтому он искажает имя, добавляя информацию о аргументах. Компилятору C не нужно искажать имя, так как вы не можете перегружать имена функций в C. Когда вы заявляете, что функция имеет внешнюю связь "C" в C++, компилятор C++ не добавляет информацию о типе аргумента/параметра к имени, используемому для связи.
просто чтобы вы знали, вы можете указать связь "C" с каждым отдельным объявлением / определением явно или используйте блок для группировки последовательности объявлений / определений, чтобы иметь определенную связь:
extern "C" void foo(int); extern "C" { void g(char); int i; }Если вы заботитесь о технических особенностях, они перечислены в разделе 7.5 стандарта C++03, вот краткое резюме (с акцентом на extern "C"):
- extern " C " -это спецификация связи
- каждый компилятор требуются для обеспечения связи "C"
- спецификация связи должна выполняться только в пространстве имен объем
все типы функций, имена функций и имена переменных имеют языковую связьсмотрите комментарий Ричарда: только имена функций и имена переменных с внешней связью имеют языковую связь- два типа функций с различными языковыми связями являются различными типами, даже если в остальном идентичны
- гнездо спецификаций рычага, внутреннее одно определяет окончательный рычаг
- extern "C" is игнорируется для членов класса
- не более одной функции с определенным именем может иметь связь "C" (независимо от пространства имен)
extern "C" заставляет функцию иметь внешнюю связь (не может сделать ее статической)смотрите комментарий Ричарда: 'static' внутри 'extern "C"' является допустимым; сущность, объявленная таким образом, имеет внутреннюю связь и поэтому не имеет языковой связи- связь из C++ с объектами, определенными на других языках и для объектов, определенных в C++ из других языков, определяется реализация и зависит от языка. Только там, где стратегии компоновки объектов двух языковых реализаций достаточно похожи, такая связь может быть достигнута
просто хотел добавить немного информации, так как я не видел еще в курсе.
вы очень часто будете видеть код в заголовках C, например:
#ifdef __cplusplus extern "C" { #endif // all of your legacy C code here #ifdef __cplusplus } #endifэто позволяет вам использовать этот файл заголовка C с вашим кодом C++, потому что будет определен макрос "__cplusplus". Но вы можете и все еще используйте его с вашим устаревшим кодом C, где макрос не определено, поэтому он не будет видеть однозначно C++ строить.
хотя, я также видел C++ код, такой как:
extern "C" { #include "legacy_C_header.h" }, Я думаю, выполняет почти то же самое.
Не уверен, какой путь лучше, но я видел обоих.
в каждой программе на C++ все нестатические функции представлены в двоичном файле в виде символов. Эти символы представляют собой специальные текстовые строки, которые однозначно идентифицируют функцию в программе.
В C, имя символа совпадает с именем функции. Это возможно, потому что в C никакие две нестатические функции не могут иметь одно и то же имя.
потому что C++ допускает перегрузку и имеет много функций, которые C не любит-классы, функции-члены, спецификации исключений-это невозможно просто использовать имя функции в качестве имени символа. Чтобы решить эту проблему, C++ использует так называемое искажение имени, которое преобразует имя функции и всю необходимую информацию (например, количество и размер аргументов) в какую-то странную строку, обрабатываемую только компилятором и компоновщиком.
поэтому, если вы укажете функцию, которая будет extern C, компилятор не выполняет искажение имени с ней, и это может быть напрямую доступ с использованием его имени символа в качестве функции имя.
это удобно при использовании
dlsym()иdlopen()для вызова таких функций.
декомпилировать a
g++двоичный файл, чтобы увидеть, что происходитвход:
void f() {} void g(); extern "C" { void ef() {} void eg(); } /* Prevent g and eg from being optimized away. */ void h() { g(); eg(); }компиляция с выходом GCC 4.8 Linux ELF:
g++ -c a.cppдекомпилировать таблицу символов:
readelf -s a.oвывод содержит:
Num: Value Size Type Bind Vis Ndx Name 8: 0000000000000000 6 FUNC GLOBAL DEFAULT 1 _Z1fv 9: 0000000000000006 6 FUNC GLOBAL DEFAULT 1 ef 10: 000000000000000c 16 FUNC GLOBAL DEFAULT 1 _Z1hv 11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Z1gv 12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND egтолкование
мы видим, что:
efиegхранились в символах с тем же именем, что и в кодостальные символы были искажены. Давайте распутаем их:
$ c++filt _Z1fv f() $ c++filt _Z1hv h() $ c++filt _Z1gv g()вывод: оба из следующих типов символов не mangled:
- определена
- объявлен, но не определен (
Ndx = UND), которые будут предоставлены по ссылке или во время выполнения из другого объектного файлатак что вам понадобится
extern "C"как при вызове:
- C от C++: скажи
g++ожидать незамутненные символы, созданныеgcc- C++ от C: tell
g++для создания незамутненных символов дляgccиспользоватьвещи, которые не работают в extern C
становится очевидным, что любая функция C++, которая требует искажения имени, не проснется внутри
extern C:extern "C" { // Overloading. // error: declaration of C function ‘void f(int)’ conflicts with void f(); void f(int i); // Templates. // error: template with C linkage template <class C> void f(C i) { } }минимальный запускаемый C из примера C++
для полноты картины и для новичков там.
вызов C из C++ довольно прост: каждая функция C имеет только один возможный неискаженный символ, поэтому никакой дополнительной работы не требуется.
главная.cpp:
#include <cassert> #include "c.h" int main() { assert(f() == 1); }Си.ч:
#ifndef C_H #define C_H /* This ifdef allows the header to be used from both C and C++. */ #ifdef __cplusplus extern "C" { #endif int f(); #ifdef __cplusplus } #endif #endifСи.с:
#include "c.h" int f(void) { return 1; }Run:
g++ -c -o main.o -std=c++98 main.cpp gcc -c -o c.o -std=c89 c.c g++ -o main.out main.o c.o ./main.outбез
extern "C"ссылка не работает с:main.cpp:6: undefined reference to `f()', потому что
g++ожидает найти искореженныйf, которыйgccне производить.минимальный запускаемый C++ из примера C
вызов C++ из немного сложнее: мы должны вручную создавать не искаженные версии каждой функции, которую мы хотим выставить.
здесь мы проиллюстрируем, как предоставить перегрузку функций C++ С.
главная.c:
#include <assert.h> #include "cpp.h" int main(void) { assert(f_int(1) == 2); assert(f_float(1.0) == 3); return 0; }cpp.h:
#ifndef CPP_H #define CPP_H #ifdef __cplusplus // C cannot see these overloaded prototypes, or else it would get confused. int f(int i); int f(float i); extern "C" { #endif int f_int(int i); int f_float(float i); #ifdef __cplusplus } #endif #endifcpp.cpp:
#include "cpp.h" int f(int i) { return i + 1; } int f(float i) { return i + 2; } int f_int(int i) { return f(i); } int f_float(float i) { return f(i); }Run:
gcc -c -o main.o -std=c89 -Wextra main.c g++ -c -o cpp.o -std=c++98 cpp.cpp g++ -o main.out main.o cpp.o ./main.outбез
extern "C"выдает:main.c:6: undefined reference to `f_int' main.c:7: undefined reference to `f_float', потому что
g++генерируются искаженные символы, которыеgccне удается найти.
C++ искажает имена функций для создания объектно-ориентированного языка из процедурного языка
большинство языков программирования не построены поверх существующих языков программирования. C++ построен поверх C, и, кроме того, это объектно-ориентированный язык программирования, построенный из процедурного языка программирования, и по этой причине есть ключевые слова C++, такие как
extern, которые обеспечивают обратную совместимость с C.давайте посмотрим на следующие пример:
#include <stdio.h> // Two functions are defined with the same name // but have different parameters void printMe(int a) { printf("int: %i\n", a); } void printMe(char a) { printf("char: %c\n", a); } int main() { printMe("a"); printMe(1); return 0; }компилятор C не будет компилировать приведенный выше пример, потому что та же функция
printMeопределяется дважды (хотя они имеют разные параметрыint avschar a).gcc-o printMe printMe.с. && /printMe;
Ошибка 1. PrintMe определяется несколько раз.компилятор C++ скомпилирует приведенный выше пример. Ему все равно, что
printMeопределен дважды.g++ - o printMe printMe.с. && /printMe;
это потому, что компилятор C++ неявно переименовывает (mangles) функции, основанные на их параметры. В C, эта функция не поддерживается. Однако, когда C++ был построен на C, язык был разработан, чтобы быть объектно-ориентированным, и должен был поддерживать возможность создавать различные классы с методами (функциями) того же имени и переопределять методы (способ переопределения) основан на разные параметры.
Extern говорит "не калечить имена функций"
однако, представьте, что у нас есть унаследованный C-файл с именем "parent.c " это
includeимена функций s из других устаревших файлов C, " parent.ч", "ребенок.h " и др. Если наследство " родитель.файл c " запускается через компилятор C++, затем имена функций будут искажены, и они больше не будут соответствовать именам функций, указанным в "parent.ч", "ребенок.h " и т. д.-Поэтому имена функций в этих внешних файлах также понадобятся быть искалеченным. Искажение имен функций в сложной программе на C, имеющей множество зависимостей, может привести к нарушению кода; поэтому может быть удобно предоставить ключевое слово, которое может сказать компилятору C++ не искажать имя функции.The
externключевое слово говорит компилятору C++ не калечить (переименовывать) имена функций. Пример использования:extern void printMe(int a);
оно меняет взаимосвязи функции таким образом, что функция вызывается из C. На практике это означает, что имя функции не mangled.
Не любой c-заголовок будет компилироваться с extern "C". Когда идентификаторы в C-заголовке конфликтуют с ключевыми словами C++, компилятор C++ будет жаловаться на это.
например, я видел следующий код сбой в g++:
extern "C" { struct method { int virtual; }; }вроде имеет смысл, но это то, что нужно иметь в виду при портировании C-кода на C++.
Он сообщает компилятору C++, чтобы искать имена этих функций в стиле C при связывании, потому что имена функций, скомпилированных в C и C++, отличаются на этапе связывания.
extern "C" предназначен для распознавания компилятором C++ и уведомления компилятора о том, что указанная функция (или должна быть) скомпилирована в стиле C. Так что при связывании, это ссылка на правильную версию функции из C.
я использовал модификатор extern "с", Перед для DLL(динамически подключаемая библиотека) файлы и т. д. функция main ()" exportable", поэтому ее можно использовать позже в другом исполняемом файле из dll. Может быть, пример того, где я использовал его, может быть полезен.
DLL
#include <string.h> #include <windows.h> using namespace std; #define DLL extern "C" __declspec(dllexport) //I defined DLL for dllexport function DLL main () { MessageBox(NULL,"Hi from DLL","DLL",MB_OK); }EXE
#include <string.h> #include <windows.h> using namespace std; typedef LPVOID (WINAPI*Function)();//make a placeholder for function from dll Function mainDLLFunc;//make a variable for function placeholder int main() { char winDir[MAX_PATH];//will hold path of above dll GetCurrentDirectory(sizeof(winDir),winDir);//dll is in same dir as exe strcat(winDir,"\exmple.dll");//concentrate dll name with path HINSTANCE DLL = LoadLibrary(winDir);//load example dll if(DLL==NULL) { FreeLibrary((HMODULE)DLL);//if load fails exit return 0; } mainDLLFunc=(Function)GetProcAddress((HMODULE)DLL, "main"); //defined variable is used to assign a function from dll //GetProcAddress is used to locate function with pre defined extern name "DLL" //and matcing function name if(mainDLLFunc==NULL) { FreeLibrary((HMODULE)DLL);//if it fails exit return 0; } mainDLLFunc();//run exported function FreeLibrary((HMODULE)DLL); }
extern "C"- это спецификация связи, которая используется для вызов функций C на Cpp исходные файлы. Мы можем вызовите функции C, напишите переменные и включите заголовки. Функция объявлена в сущности extern и определена снаружи. СинтаксисТип 1:
extern "language" function-prototypeТип 2:
extern "language" { function-prototype };например:
#include<iostream> using namespace std; extern "C" { #include<stdio.h> // Include C Header int n; // Declare a Variable void func(int,int); // Declare a function (function prototype) } int main() { func(int a, int b); // Calling function . . . return 0; } // Function definition . . . void func(int m, int n) { // // }
при смешивании C и C++ (т. е. a. вызов функции C из C++; и b. вызов функции C++ из C), искажение имени C++ вызывает проблемы связывания. Технически говоря, эта проблема возникает только тогда, когда вызываемые функции уже скомпилированы в двоичный код (скорее всего, a *.файл библиотеки) с использованием соответствующего компилятора.
поэтому нам нужно использовать extern "C", чтобы отключить искажение имени в C++.
этот ответ для нетерпеливых / есть сроки, чтобы соответствовать, только часть / простое объяснение ниже:
- В C++ вы можете иметь одно и то же имя в классе через перегрузку (например, поскольку все они имеют одно и то же имя, не могут быть экспортированы как-есть из dll и т. д.) решение этих проблем заключается в том, что они преобразуются в разные строки (называемые символами), символы составляют имя функции, а также аргументы, поэтому каждая из этих функций даже с одинаковым именем может быть однозначно идентифицирована (также называется, имя калечить)
- В C у вас нет перегрузки, имя функции уникально (поэтому отдельная строка для идентификации имени функции однозначно не требуется, поэтому символ-это само имя функции)
Так
в C++ с именем mangling уникально идентифицирует каждую функцию
в C даже без искажения имени однозначно идентифицирует каждую функциючтобы изменить поведение C++, то есть указать это имя mangling не должен случается для определенной функции, вы можете использовать extern "C" перед именем функции, по какой-либо причине, например, экспорт функции с определенным именем из dll, для использования ее клиентами.
прочитайте другие ответы, для более подробных / более правильных ответов.
Comments