Что именно помещает extern "C"
в код С++?
Например:
extern "C" {
void foo();
}
extern "C" делает имя функции в С++ имеет ссылку "C" (компилятор не изменяет имя), чтобы клиентский код C мог ссылаться на (т.е. использовать) вашу функцию, используя совместимый заголовочный файл, совместимый с C, который содержит только объявление вашей функции. Определение вашей функции содержится в двоичном формате (который был скомпилирован вашим компилятором на С++), который клиентский C-компоновщик будет ссылаться на имя "C" .
Так как С++ имеет перегрузку имен функций, а C - нет, компилятор С++ не может просто использовать имя функции как уникальный идентификатор для ссылки, поэтому он управляет именем, добавляя информацию о аргументах. Компилятору AC не нужно указывать имя, так как вы не можете перегружать имена функций в C. Когда вы указываете, что функция имеет ссылку extern "C" в С++, компилятор С++ не добавляет информацию о параметрах параметра/параметрах в имя, используемое для связь.
Как вы знаете, вы можете указать ссылку "C" для каждого отдельного объявления/определения явно или использовать блок для группировки последовательности объявлений/определений, имеющих определенную связь:
extern "C" void foo(int);
extern "C"
{
void g(char);
int i;
}
Если вы заботитесь о технических особенностях, они перечислены в разделе 7.5 стандарта С++ 03, вот краткий обзор (с акцентом на extern "C" ):
Просто хотел добавить немного информации, так как я еще не видел его.
Вы очень часто видите код в заголовках C следующим образом:
#ifdef __cplusplus
extern "C" {
#endif
// all of your legacy C code here
#ifdef __cplusplus
}
#endif
Что это значит, так это то, что он позволяет использовать этот заголовочный файл C с кодом С++, потому что будет определен макрос "__cplusplus". Но вы также можете использовать его с вашим старым кодом C, где макрос НЕ определен, поэтому он не увидит уникальную конструкцию С++.
Хотя, я также видел код на С++, например:
extern "C" {
#include "legacy_C_header.h"
}
который, как я полагаю, выполняет то же самое.
Не уверен, какой путь лучше, но я видел оба.
extern "C"
). Отлично работает, использовал эту технику много раз.
В каждой программе на С++ все нестатические функции представлены в двоичном файле как символы. Эти символы представляют собой специальные текстовые строки, которые однозначно идентифицируют функцию в программе.
В C имя символа совпадает с именем функции. Это возможно, потому что в C две нестатические функции могут иметь одно и то же имя.
Поскольку С++ допускает перегрузку и имеет множество функций, которые C не имеет - как классы, функции-члены, спецификации исключений, невозможно просто использовать имя функции в качестве имени символа. Чтобы решить эту проблему, С++ использует так называемое управление именами, которое преобразует имя функции и всю необходимую информацию (например, число и размер аргументов) в какую-то странную строку, обрабатываемую только компилятором и компоновщиком.
Итак, если вы укажете функцию extern C, компилятор не будет выполнять с ним имя, и он может быть напрямую доступ к нему с использованием имени символа в качестве имени функции.
Это удобно при использовании dlsym()
и dlopen()
для вызова таких функций.
Декомпилируйте сгенерированный двоичный файл 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"
при вызове:
g++
чтобы он ожидал не исправленных символов, созданных gcc
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++ from немного сложнее: нам нужно вручную создавать не искаженные версии каждой функции, которую мы хотим представить.
Здесь мы иллюстрируем, как выставить перегрузки функции 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
не может найти.
Проверено в Ubuntu 18.04.
extern "C" {
помогает вам вызывать не исправленные функции C изнутри программ C ++ , а также неуправляемые функции C ++ изнутри программ C , что в других ответах не так очевидно, и 2) потому что Вы показываете отличные примеры каждого. Спасибо!
Большинство языков программирования не построены поверх существующих языков программирования. C++ построен поверх C, и, кроме того, он является объектным -o языком программирования, построенным из процедурного языка программирования, и по этой причине есть 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;
}
Компилятор AC не скомпилирует приведенный выше пример, потому что printMe
та же функция printMe
определяется дважды (даже если они имеют разные параметры int a
vs char a
).
gcc -o printMe printMe.c &&./printMe;
1 ошибка PrintMe определяется более одного раза.
Компилятор C++ скомпилирует приведенный выше пример. Не важно, что printMe
определяется дважды.
g++ -o printMe printMe.c &&./printMe;
Это связано с тем, что компилятор C++ неявно переименовывает (искажает) функции на основе их параметров. В C эта функция не была поддержана. Однако, когда C++ был построен на C, язык был разработан для объекта -o и должен был поддерживать возможность создавать разные классы с методами (функциями) с одинаковыми именами и переопределять методы (переопределение методов ).) на основе разных параметров.
Однако представьте, что у нас есть устаревший C файл с именем "parent.c", который include
имена функций из других унаследованных C файлов, "parent.h", "child.h" и т.д. Если старый файл "parent.c" запущен через компилятор C++ имена функций будут искажены, и они больше не будут совпадать с именами функций, указанными в "parent.h", "child.h" и т.д., так что имена функций в этих внешних файлах также будут нужно быть искалеченным. Упорядочивание имен функций в сложной программе на C, имеющих много зависимостей, может привести к повреждению кода; поэтому может быть удобно предоставить ключевое слово, которое может сказать компилятору C++ не искажать имя функции.
Ключевое слово extern
указывает компилятору C++ не искажать (переименовывать) имена функций. Пример использования: extern void printMe(int a);
extern "C"
если у нас есть только файл dll
? Я имею в виду, если у нас нет заголовочного файла, а есть только исходный файл (только реализации) и использование его функции через указатель на функцию. в этом состоянии мы просто использовали функции (независимо от их имени).
Ни один C-заголовок не может быть сделан совместимым с C++, просто заключив внешнюю букву "C". Когда идентификаторы в C-заголовке конфликтуют с ключевыми словами C++, компилятор C++ будет жаловаться на это.
Например, я видел следующий сбой кода в g++:
extern "C" {
struct method {
int virtual;
};
}
Кинда имеет смысл, но есть кое-что, о чем следует помнить при портировании C-кода на C++.
extern "C"
означает использование связи C, как описано в других ответах. Это не значит «компилировать содержимое как C» или что-то еще. int virtual;
недопустимо в C ++, и указание другой связи не меняет этого.
Он изменяет связь функции таким образом, что функция может быть вызвана из C. На практике это означает, что имя функции не mangled.
Он сообщает компилятору С++ искать имена этих функций в стиле C при связывании, потому что имена функций, скомпилированных в C и С++, различаются на этапе компоновки.
extern "C" предназначен для распознавания компилятором С++ и для уведомления компилятора о том, что отмеченная функция (или должна быть) скомпилирована в стиле C. Так что, связывая, он ссылается на правильную версию функции из C.
Я использовал 'extern' C "'для 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"
и __declspec(dllexport)
не связаны. Первый контролирует оформление символов, последний отвечает за создание экспортной записи. Вы также можете экспортировать символ, используя оформление имени C ++. Помимо полного упущения из этого вопроса, есть и другие ошибки в примере кода. Например, main
экспортируемый из вашей DLL, не объявляет возвращаемое значение. Или созыв соглашения, в этом отношении. При импорте вы приписываете соглашение о случайных вызовах ( WINAPI
) и используете неправильный символ для 32-битных сборок (должно быть _main
или [email protected]
). Извините, -1.
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, даже без имени, определяющего однозначно тождества, каждая функция
Чтобы изменить поведение C++, то есть указать, что для определенной функции не должно выполняться манипуляция имени, вы можете использовать extern "C" перед именем функции по любой причине, например, экспортировать функцию с определенным именем из dll, для использования его клиентами.
Прочтите другие ответы, чтобы получить более подробные/более правильные ответы.
При смешивании C и С++ (т.е. вызов функции C из С++ и b. вызов функции С++ из C), сбой имени С++ вызывает проблемы с связыванием. Технически говоря, эта проблема возникает только тогда, когда функции вызываемого пользователя уже скомпилированы в двоичный файл (скорее всего, файл *.a) с использованием соответствующего компилятора.
Поэтому нам нужно использовать extern "C", чтобы отключить манипуляцию имени на С++.
foo()
.