Каков эффект extern " C " в C++?
что именно делает putting extern "c/" class="blnk">C"
в коде на C++ делать?
например:
extern "C" {
void foo();
}
13 ответов
extern " C "делает имя функции в C++ связью" C " (компилятор не искажает имя), чтобы код клиента C мог ссылаться на (i.e) ваша функция использует совместимый с " 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" игнорируется для членов класса
- не более одной функции с определенным именем может иметь связь " C " (независимо от пространства имен)
-
extern " C " заставляет функцию иметь внешнюю связь (не может сделать ее статической)см. комментарий Ричарда: "статический" внутри "extern" C " действителен; сущность, объявленная таким образом, имеет внутреннюю связь и поэтому не имеет языковой связи - связь с C++ с объектами, определенными на других языках и для объектов, определенных в C++ с других языков, определяется реализация и зависит от языка. Только там, где стратегии компоновки объектов двух языковых реализаций достаточно похожи, такая связь может быть достигнута
просто хотел добавить немного информации, так как я еще не видел его.
вы очень часто увидите код в заголовках C, например:
#ifdef __cplusplus
extern "C" {
#endif
// all of your legacy C code here
#ifdef __cplusplus
}
#endif
это позволяет использовать этот файл заголовка C с кодом C++, потому что будет определен макрос "__cplusplus". Но ты можешь!--7-->и все еще используйте его с вашим устаревшим кодом 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++: скажи!--16--> ожидать unmangled символы, производимые
gcc
- C++ от C: tell
g++
для создания unmangled символы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) { }
}
минимальный runnable C из примера c++
для полноты и для newbs там.
вызов 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; }
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++ С.
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);
}
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 a
vs char 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.ч", "ребенок.ч" и т. д. Если наследие " родитель.файл c " запускается через компилятор C++, затем имена функций будут искажены, и они больше не будут соответствовать именам функций, указанным в "parent.ч", "ребенок.h " и т. д. - Поэтому имена функций в этих внешних файлах также понадобятся быть искалеченным. Искажение имен функций в сложной программе на языке С с большим количеством зависимостей может привести к нарушению кода; поэтому может быть удобно предоставить ключевое слово, которое может сказать компилятору c++ не искажать имя функции.
на extern
ключевое слово говорит компилятору C++ не калечить (переименовывать) имена функций. Пример использования: extern void printMe(int a);
оно меняет взаимосвязи функции таким образом, что функция вызывается из C. На практике это означает, что имя функции не исковеркали.
Не любой 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" 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"
спецификация рычага которая использована к вызовите функции C на исходные файлы Cpp. Мы можем!--5-->вызовите функции C, напишите переменные и включите заголовки. Функция объявлена в extern entity и определена снаружи. Синтаксис
Тип 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 для использования ее клиентами.
прочитайте другие ответы, для более подробных / более правильных ответов.