Что именно делает вставка extern "C"
в код C ++?
Например:
extern "C" {
void foo();
}
foo()
функция.
Что именно делает вставка extern "C"
в код C ++?
Например:
extern "C" {
void foo();
}
foo()
функция.
Ответы:
extern "C" делает имя функции в C ++ связанным с C (компилятор не искажает имя), так что клиентский код C может ссылаться (т.е. использовать) вашу функцию, используя совместимый с C заголовочный файл, который содержит только декларация вашей функции. Определение вашей функции содержится в двоичном формате (который был скомпилирован вашим компилятором C ++), на который клиентский компоновщик 'C' затем будет ссылаться, используя имя 'C'.
Поскольку C ++ перегружает имена функций, а C - нет, компилятор C ++ не может просто использовать имя функции в качестве уникального идентификатора для ссылки, поэтому он искажает имя, добавляя информацию об аргументах. Компилятору AC не нужно искажать имя, так как вы не можете перегрузить имена функций в 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" { int i; }
это определение. Это может быть не то, что вы хотели, рядом с не определением void g(char);
. Чтобы сделать это без определения, вам нужно extern "C" { extern int i; }
. С другой стороны, синтаксис одного объявления без фигурных скобок делает объявление не определением: extern "C" int i;
это то же самое, чтоextern "C" { extern int i; }
Просто хотел добавить немного информации, так как я еще не видел ее опубликованной.
Вы очень часто будете видеть код в заголовках 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"
}
который я представляю, выполняет то же самое.
Не уверен, какой путь лучше, но я видел оба.
extern "C"
в заголовке). Он прекрасно работает, использовал эту технику много раз.
extern "C"
до или после включения заголовка. К тому времени, когда он достигает компилятора, это всего лишь один длинный поток предварительно обработанного текста.
g++
не ошиблась ни по одной цели, ни за последние 17 лет, по крайней мере. Суть первого примера в том, что не имеет значения, используете ли вы компилятор C или C ++, для имен в extern "C"
блоке не будет производиться искажение имен .
Декомпилируйте g++
сгенерированный двоичный файл, чтобы увидеть, что происходит
main.cpp
void f() {}
void g();
extern "C" {
void ef() {}
void eg();
}
/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }
Скомпилируйте и разберите сгенерированный вывод ELF :
g++ -c -std=c++11 -Wall -Wextra -pedantic -o main.o main.cpp
readelf -s main.o
Вывод содержит:
8: 0000000000000000 7 FUNC GLOBAL DEFAULT 1 _Z1fv
9: 0000000000000007 7 FUNC GLOBAL DEFAULT 1 ef
10: 000000000000000e 17 FUNC GLOBAL DEFAULT 1 _Z1hv
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Z1gv
13: 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 из 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++
* because C does not know what this extern "C" thing is. */
#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 ++ для 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 имя символа совпадает с именем функции. Это возможно, потому что в C нет двух нестатических функций, которые могут иметь одинаковое имя.
Поскольку C ++ допускает перегрузку и имеет много функций, которых нет в C - например, классы, функции-члены, спецификации исключений - невозможно просто использовать имя функции в качестве имени символа. Чтобы решить эту проблему, C ++ использует так называемое искажение имени, которое преобразует имя функции и всю необходимую информацию (например, число и размер аргументов) в некоторую странную строку, обрабатываемую только компилятором и компоновщиком.
Поэтому, если вы укажете функцию, которая будет extern C, компилятор не будет выполнять манипулирование именами с ней, и к ней можно получить прямой доступ, используя ее имя символа в качестве имени функции.
Это удобно при использовании dlsym()
и dlopen()
для вызова таких функций.
Большинство языков программирования не построены поверх существующих языков программирования. C ++ построен поверх C, и, кроме того, это объектно-ориентированный язык программирования, построенный из процедурного языка программирования, и по этой причине существуют такие выражения C ++, какextern "C"
которые обеспечивают обратную совместимость с 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
определяется дважды (даже если они имеют разные параметры int a
против char a
).
gcc -o printMe printMe.c && ./printMe;
1 ошибка PrintMe определяется более одного раза.
Компилятор C ++ скомпилирует приведенный выше пример. Ему все равно, что printMe
определяется дважды.
g ++ -o printMe printMe.c && ./printMe;
Это связано с тем, что компилятор C ++ неявно переименовывает ( искажает ) функции на основе их параметров. В C эта функция не поддерживалась. Однако, когда C ++ был построен на C, язык был разработан , чтобы быть объектно-ориентированным, и необходимо поддерживать возможность создавать различные классы с методами (функции) одного и того же имени, и переопределить методы ( метод переопределение ) , основанные на различных параметры.
extern "C"
говорит "не калечить имена функций C"Однако представьте, что у нас есть устаревший C-файл с именем «parent.c», который содержит include
имена функций из других унаследованных C-файлов, «parent.h», «child.h» и т. Д. Если запускается унаследованный файл «parent.c» через компилятор C ++ имена функций будут искажены, и они больше не будут совпадать с именами функций, указанными в "parent.h", "child.h" и т. д., поэтому имена функций в этих внешних файлах также должны будут быть искалеченным. Переназначение имен функций в сложной программе на C, которые имеют много зависимостей, может привести к повреждению кода; поэтому может быть удобно предоставить ключевое слово, которое может сказать компилятору C ++ не искажать имя функции.
extern "C"
Ключевое слово указывает на ++ компилятор C не калечить (переименования) C имена функций.
Например:
extern "C" void printMe(int a);
extern "C"
если у нас есть только dll
файл? Я имею в виду, если у нас нет заголовочного файла, а есть только исходный файл (только реализации) и использование его функции через указатель на функцию. в этом состоянии мы просто использовали функции (независимо от их имени).
Ни один C-заголовок не может быть сделан совместимым с C ++, просто заключая во внешнюю "C". Когда идентификаторы в заголовке C конфликтуют с ключевыми словами C ++, компилятор C ++ будет жаловаться на это.
Например, я видел следующий сбой кода в g ++:
extern "C" {
struct method {
int virtual;
};
}
Kinda имеет смысл, но есть что-то, что следует иметь в виду при переносе C-кода на C ++.
extern "C"
означает использовать связь C, как описано в других ответах. Это не значит «компилировать содержимое как C» или что-то еще. int virtual;
недопустимо в C ++, и указание другой связи не меняет этого.
Он изменяет связь функции таким образом, что функция вызывается из C. На практике это означает, что имя функции не искажено .
undname
.
Он информирует компилятор C ++ для поиска имен этих функций в стиле C при компоновке, потому что имена функций, скомпилированных в C и C ++, различаются на этапе компоновки.
Ранее я использовал 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);
}
extern "C"
и __declspec(dllexport)
не связаны. Первый контролирует оформление символов, последний отвечает за создание экспортной записи. Вы также можете экспортировать символ, используя оформление имени C ++. Помимо полного отсутствия смысла в этом вопросе, в примере кода есть и другие ошибки. Например, при main
экспорте из вашей DLL возвращаемое значение не объявляется. Или созыв соглашения, в этом отношении. При импорте вы приписываете соглашение о случайном вызове ( WINAPI
) и используете неправильный символ для 32-битных сборок (должно быть _main
или _main@0
). Извините, -1.
void*
, но ваша реализация ничего не возвращает. Это будет очень хорошо летать ...
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)
{
//
//
}
Этот ответ для нетерпеливых / у которых есть сроки, чтобы встретиться, только часть / простое объяснение ниже:
Таким образом,
в C ++, с именем искажения уникально идентифицирует каждую функцию
в C, даже без указания имени однозначно идентифицирует каждую функцию
Чтобы изменить поведение C ++, то есть указать, что искажение имени не должно происходить для конкретной функции, вы можете использовать extern "C" перед именем функции по любой причине, например, экспортируя функцию с определенным именем из dll , для использования его клиентами.
Прочитайте другие ответы, для более подробных / более правильных ответов.
При смешивании C и C ++ (то есть a. Вызова функции C из C ++ и b. Вызова функции C ++ из C) искажение имени C ++ вызывает проблемы с линковкой. Технически говоря, эта проблема возникает, только когда функции вызываемого абонента уже скомпилированы в двоичный файл (скорее всего, файл библиотеки * .a) с использованием соответствующего компилятора.
Поэтому нам нужно использовать extern "C", чтобы отключить искажение имени в C ++.
Не противореча другим хорошим ответам, я добавлю немного своего примера.
Что именно делает C ++ Compiler : он искажает имена в процессе компиляции, поэтому мы требуем, чтобы компилятор специально относился к C
реализации.
Когда мы делаем классы C ++ и добавляем их extern "C"
, мы говорим нашему компилятору C ++, что мы используем соглашение о вызовах C.
Причина (мы вызываем реализацию C из C ++): либо мы хотим вызвать функцию C из C ++, либо вызвать функцию C ++ из C (классы C ++ ... и т. Д. Не работают в C).
Функция void f (), скомпилированная компилятором C, и функция с тем же именем void f (), скомпилированная компилятором C ++, не являются одной и той же функцией. Если вы написали эту функцию на C, а затем попытались вызвать ее из C ++, то компоновщик будет искать функцию C ++ и не найдет функцию C.
extern "C" сообщает компилятору C ++, что у вас есть функция, скомпилированная компилятором C. Как только вы скажете, что он был скомпилирован компилятором C, компилятор C ++ будет знать, как правильно его вызвать.
Это также позволяет компилятору C ++ компилировать функцию C ++ таким образом, чтобы компилятор C мог ее вызывать. Эта функция официально будет функцией C, но, поскольку она компилируется компилятором C ++, она может использовать все функции C ++ и имеет все ключевые слова C ++.
extern "C"
функцию - и (с некоторыми ограничениями) она будет вызываться кодом, скомпилированным компилятором C.