Каково влияние extern "C" в C++?
Что именно делает сдача extern "C"
в код C++ делать?
Например:
extern "C" {
void foo();
}
18 ответов
extern "C" делает имя функции в C++ связанным с C (компилятор не искажает имя), так что клиентский код C может ссылаться (т.е. использовать) вашу функцию, используя совместимый с C заголовочный файл, который содержит только декларация вашей функции. Ваше определение функции содержится в двоичном формате (который был скомпилирован вашим компилятором C++), на который клиентский компоновщик 'C' затем будет ссылаться, используя имя 'C'.
Поскольку C++ перегружает имена функций, а C - нет, компилятор C++ не может просто использовать имя функции в качестве уникального идентификатора для ссылки, поэтому он искажает имя, добавляя информацию об аргументах. Компилятору A 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" игнорируется для членов класса
- не более одной функции с конкретным именем может иметь связь "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"
}
который я представляю, выполняет то же самое.
Не уверен, какой путь лучше, но я видел оба.
Декомпилировать 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()
Вывод: оба следующих типа символов не были искажены:
- определенный
- объявлено, но не определено (
Ndx = UND
), предоставляется по ссылке или во время выполнения из другого объектного файла
Так вам понадобится extern "C"
оба при звонке:
- C из C++: расскажите
g++
ожидать непогашенных символов, производимыхgcc
- C++ от C: расскажите
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 имеет только один возможный не искаженный символ, поэтому никакой дополнительной работы не требуется.
main.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; }
Бежать:
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.
main.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
#endif
cpp.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);
}
Бежать:
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++ допускает перегрузку и имеет много функций, которых нет в C - таких как классы, функции-члены, спецификации исключений - невозможно просто использовать имя функции в качестве имени символа. Чтобы решить эту проблему, C++ использует так называемое искажение имени, которое преобразует имя функции и всю необходимую информацию (например, число и размер аргументов) в некоторую странную строку, обрабатываемую только компилятором и компоновщиком.
Поэтому, если вы укажете функцию, которая будет extern C, компилятор не будет выполнять манипулирование именами с ней, и к ней можно получить прямой доступ, используя ее имя символа в качестве имени функции.
Это удобно при использовании dlsym()
а также dlopen()
для вызова таких функций.
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;
}
Компилятор A C не скомпилирует приведенный выше пример, потому что та же функция printMe
определяется дважды (хотя они имеют разные параметры int a
против char a
).
gcc -o printMe printMe.c &&./printMe;
1 ошибкаPrintMe определяется более одного раза.
Компилятор C++ скомпилирует приведенный выше пример. Все равно чтоprintMe
определяется дважды.
g ++ -o printMe printMe.c &&./printMe;
Это связано с тем, что компилятор C++ неявно переименовывает ( искажает) функции на основе их параметров. В C эта функция не была поддержана. Однако, когда C++ был построен над C, язык был разработан, чтобы быть объектно-ориентированным, и должен был поддерживать возможность создавать разные классы с методами (функциями) с одинаковыми именами и переопределять методы (переопределение методов) на основе различных параметры.
Экстерн говорит: "Не портите имена функций"
Однако представьте, что у нас есть устаревший C-файл с именем "parent.c", который include
s имена функций из других устаревших файлов C, "parent.h", "child.h" и т. д. Если устаревший файл "parent.c" запускается через компилятор C++, то имена функций будут искажены, и они будут больше не соответствуют именам функций, указанным в "parent.h", "child.h" и т. д., поэтому имена функций в этих внешних файлах также должны быть искажены. Упорядочивание имен функций в сложной программе на C, имеющих много зависимостей, может привести к повреждению кода; поэтому может быть удобно предоставить ключевое слово, которое может сказать компилятору C++ не искажать имя функции.
extern
Ключевое слово указывает компилятору C++ не искажать (переименовывать) имена функций. Пример использования: extern void printMe(int a);
Не любой C-заголовок будет компилироваться с внешним "C". Когда идентификаторы в заголовке C конфликтуют с ключевыми словами C++, компилятор C++ будет жаловаться на это.
Например, я видел следующий сбой кода в g ++:
extern "C" {
struct method {
int virtual;
};
}
Kinda имеет смысл, но есть что-то, что следует иметь в виду при переносе C-кода на C++.
Он изменяет связь функции таким образом, что функция вызывается из C. На практике это означает, что имя функции не искажено.
Он информирует компилятор C++ для поиска имен этих функций в стиле C при компоновке, потому что имена функций, скомпилированных в C и C++, отличаются на этапе компоновки.
extern "C" предназначен для распознавания компилятором C++ и для уведомления компилятора о том, что указанная функция (или должна быть) скомпилирована в стиле C. Так что при ссылке, это ссылка на правильную версию функции из C.
extern "C"
является спецификацией связи, которая используется для вызова функций C в исходных файлах Cpp. Мы можем вызывать функции C, писать переменные и включать заголовки. Функция объявлена во внешней сущности и определена снаружи. Синтаксис
Тип 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)
{
//
//
}
Ранее я использовал extern "C" для файлов dll(библиотеки динамической компоновки), чтобы сделать функцию main() и т. Д. "Экспортируемой", чтобы позже ее можно было использовать в другом исполняемом файле из 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);
}
Функция void f(), скомпилированная компилятором C, и функция с тем же именем void f(), скомпилированная компилятором C++, не являются одной и той же функцией. Если вы написали эту функцию на C, а затем попытались вызвать ее из C++, то компоновщик будет искать функцию C++ и не найдет функцию C.
extern "C" сообщает компилятору C++, что у вас есть функция, которая была скомпилирована компилятором C. Как только вы скажете ему, что он был скомпилирован компилятором C, компилятор C++ узнает, как правильно его вызвать.
Это также позволяет компилятору C++ скомпилировать функцию C++ таким образом, чтобы компилятор C мог ее вызвать. Эта функция официально будет функцией C, но поскольку она скомпилирована компилятором C++, она может использовать все функции C++ и имеет все ключевые слова C++.
Этот ответ для нетерпеливых / имевших сроки, только часть / простое объяснение ниже:
- в C++ вы можете иметь одно и то же имя в классе через перегрузку (например, поскольку все они с одинаковым именем не могут быть экспортированы как есть из dll и т. д.), решение этих проблем заключается в том, что они преобразуются в разные строки (называемые символами) символы учитывают имя функции, а также аргументы, поэтому каждая из этих функций, даже с одним и тем же именем, может быть однозначно идентифицирована (также называется, искажение имени)
- в C у вас нет перегрузки, имя функции уникально (поэтому отдельная строка для уникального определения имени функции не требуется, поэтому символ является самим именем функции)
Так
в C++, с изменением имени уникально идентифицирует каждую функцию
в C, даже без названия искажения уникальности каждой функции
Чтобы изменить поведение C++, то есть указать, что искажение имени не должно происходить для конкретной функции, вы можете использовать extern "C" перед именем функции по любой причине, например, экспортируя функцию с определенным именем из dll, для использования его клиентами.
Читайте другие ответы, для более подробных / более правильных ответов.
Интересно, что в этом потоке (до сих пор) не встречается ни Visual Studio, ни dumpbin. Поэтому я хочу добавить некоторую информацию об этом.
( Если это имеет смысл, мы могли бы объединить это с любым из приведенных выше ответов. Я не уверен насчет философии SO здесь. )
При компиляции с помощью компилятора Visual Studio каждому символу предшествует подчеркивание .
пример: компиляция
extern "C"
void cf () {}
void cppf () {}
и при работе с полученным объектом символы выглядят следующим образом:
01A 00000000 SECT7 notype () External | _cf
01B 00000000 SECT5 notype () External | ?cppf@@YAXXZ (void __cdecl cppf(void))
То же самое для переменных.
typedef struct
{ int a; int b; } CppStruct;
extern "C"
{
typedef struct
{ int a; int b; int c; } CStruct;
CStruct cCStruct;
CppStruct cCppStruct;
}
CStruct cppCStruct;
CppStruct cppCppStruct;
009 00000000 SECT3 notype External | _cCStruct
00A 0000000C SECT3 notype External | _cCppStruct
00B 00000014 SECT3 notype External | ?cppCStruct@@3UCStruct@@A (struct CStruct cppCStruct)
00C 00000020 SECT3 notype External | ?cppCppStruct@@3UCppStruct@@A (struct CppStruct cppCppStruct)
примечание: для символов C++dumpbin
также показывает неповрежденное имя символа в скобках.
Примечание 2: Как видите, это не влияет на определения типов.
Высматривать! Если переменная где-то ранее была объявлена (например, в заголовочном файле), то она будет скомпилирована с использованием связи C++ без дальнейшего уведомления:
extern CppStruct ifcStruct;
extern int ifcVar;
/* ... */
extern "C"
{
CppStruct ifcStruct;
int ifcVar = 0;
}
dumpbin /symbols
00C 00000000 SECT4 notype External | ?ifcStruct@@3UCppStruct@@A (struct CppStruct ifcStruct)
00D 00000008 SECT4 notype External | ?ifcVar@@3HA (int ifcVar)
Однако, когда функция объявлена где-то раньше без , компилятор (Microsoft) выдает отчетливое сообщение об ошибке :
test.cpp(20): error C2732: linkage specification contradicts earlier specification for 'ifcf'
test.cpp(20): note: see declaration of 'ifcf'
(Причины такого различия обсуждаются здесь.)
Насколько я знаю, также сообщает компилятору использовать соглашения о вызовах C.
см. также Экспорт классов C++ из DLL — веб-сайт Эли Бендерского (Google все еще находит его, но сайт кажется мертвым ):
extern "C" __declspec(dllexport) IKlass* __cdecl create_klass()
Давайте посмотрим, что означает каждая часть, по порядку:
extern "C"
- сообщает компилятору C++, что компоновщик должен использовать соглашение о вызовах C и искажение имен для этой функции. Само имя экспортируется из DLL unmangled()__declspec(dllexport)
— сообщает компоновщику экспортировать символ из DLL. Альтернативно, имяcreate_klass
может быть помещен в файл .def, переданный компоновщику.__cdecl
- повторяет, что соглашение о вызовах C (в отличие от__stdcall
соглашение о вызовах). Здесь это не является строго необходимым, но я включил это для полноты картины (в typedef дляiklass_factory
в коде приложения).
см. также этот FAQ: Как смешивать C и C++.
При смешивании C и C++ (т. Е. A. Вызова функции C из C++; и b. Вызова функции C++ из C) искажение имени C++ вызывает проблемы с линковкой. Технически говоря, эта проблема возникает, только когда функции вызываемого абонента уже скомпилированы в двоичный файл (скорее всего, файл библиотеки *.a) с использованием соответствующего компилятора.
Поэтому нам нужно использовать extern "C", чтобы отключить искажение имени в C++.
gcc, кажется, недавно также поддерживает изменение имени. даже внутриextern "c"
, если вы используете класс или перегрузку, он автоматически искажается.
#include <stdio.h>
extern "C"{
struct myint{
int i;
};
struct myint2
{
int a;
myint2(int a): a(a) {};
operator myint() const {return myint{a};}
};
}
void f1(myint i){
printf("%d", i.i);
}
int main(){
myint2 a(1);
f1(a);
}
Я даже использовал многие функции cpp. но код компилируется и работает нормально. если выnm
, ты можешь видетьmain
не искажено, но myint есть.
Не вступая в противоречие с другими хорошими ответами, я добавлю немного своего примера.
Что именно делает компилятор C++: он изменяет имена в процессе компиляции, поэтому мы требуем, чтобы компилятор обрабатывал C
реализация специально.
Когда мы делаем классы C++ и добавляем extern "C"
, мы сообщаем нашему компилятору C++, что используем соглашение о вызовах C.
Причина (мы вызываем реализацию C из C++): либо мы хотим вызвать функцию C из C++, либо вызвать функцию C++ из C (классы C++... и т. Д. Не работают в C).
Обратитесь к приведенной ниже ссылке, которая предназначена для компьютерных фанатов для компьютерных фанатов, объясняющих использование extern "C". Добавление информации об импорте со страницы ниже.
Рассмотрим следующие объявления функции f ()
int f (void) { return 1; }
int f (int) { return 0; }
void g (void) { int i = f(), j = f(0); }
Компилятор C ++ может искажать приведенные выше имена следующим образом (Источник: Wiki)
int __f_v (void) { return 1; }
int __f_i (int) { return 0; }
void __g_v (void) { int i = __f_v(), j = __f_i(0); }