Каков эффект extern "C" в С++?
Что именно помещает extern "C"
в код С++?
Например:
extern "C" {
void foo();
}
Ответы
Ответ 1
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, вот краткое резюме (с акцентом на внешнюю букву "C"):
- extern "C" является спецификацией связи
- Каждый компилятор должен обеспечивать связь "C"
- спецификация связи должна встречаться только в области имен
-
все типы функций, имена функций и имена переменных имеют языковую связь. См. Комментарий Ричарда: только имена функций и имена переменных с внешней связью имеют языковую связь - два типа функций с разными языковыми связями являются разными типами, даже если в противном случае они идентичны
- гнездо спецификаций связи, внутреннее определяет окончательную связь
- extern "C" игнорируется для членов класса
- не более одной функции с конкретным именем может иметь связь "C" (независимо от пространства имен)
-
extern "C" заставляет функцию иметь внешнюю связь (не может сделать ее статичной). См. комментарий Ричарда: "static" внутри "extern" C "'допустим; заявленная сущность имеет внутреннюю связь и поэтому не имеет языковой связи - Связь с C++ с объектами, определенными на других языках, и с объектами, определенными в C++ с других языков, определяется реализацией и зависит от языка. Только в тех случаях, когда стратегии размещения объектов двух языковых реализаций достаточно похожи, такая связь может быть достигнута
Ответ 2
Просто хотел добавить немного информации, так как я еще не видел его.
Вы очень часто видите код в заголовках 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"
}
который, как я полагаю, выполняет то же самое.
Не уверен, какой путь лучше, но я видел оба.
Ответ 3
Декомпилируйте сгенерированный двоичный файл 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"
при звонке:
- C из C++: скажите
g++
, чтобы он ожидал неправильных символов, созданных gcc
- C++ из C: скажите
g++
сгенерировать не исправленные символы, чтобы gcc
использовал
Вещи, которые не работают во внешнем 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);
}
c.h
#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
не произвел.
Пример на GitHub.
Минимальная работоспособность 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);
}
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
не может найти.
Пример на GitHub.
Проверено в Ubuntu 18.04.
Ответ 4
В каждой программе на С++ все нестатические функции представлены в двоичном файле как символы. Эти символы представляют собой специальные текстовые строки, которые однозначно идентифицируют функцию в программе.
В C имя символа совпадает с именем функции. Это возможно, потому что в C две нестатические функции могут иметь одно и то же имя.
Поскольку С++ допускает перегрузку и имеет множество функций, которые C не имеет - как классы, функции-члены, спецификации исключений, невозможно просто использовать имя функции в качестве имени символа. Чтобы решить эту проблему, С++ использует так называемое управление именами, которое преобразует имя функции и всю необходимую информацию (например, число и размер аргументов) в какую-то странную строку, обрабатываемую только компилятором и компоновщиком.
Итак, если вы укажете функцию extern C, компилятор не будет выполнять с ним имя, и он может быть напрямую
доступ к нему с использованием имени символа в качестве имени функции.
Это удобно при использовании dlsym()
и dlopen()
для вызова таких функций.
Ответ 5
C++ меняет имена функций, чтобы создать объектный язык -o из процедурного языка
Большинство языков программирования не построены поверх существующих языков программирования. C++ построен поверх C, и, кроме того, он является объектным языком программирования -o, построенным из процедурного языка программирования, и по этой причине существуют выражения 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
та же функция 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 и должен был поддерживать возможность создавать разные классы с методами (функциями) с одинаковыми именами и переопределять методы (переопределение методов ).) на основе разных параметров.
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);
Ответ 6
Он изменяет связь функции таким образом, что функция может быть вызвана из C. На практике это означает, что имя функции не mangled.
Ответ 7
Ни один C-заголовок не может быть сделан совместимым с C++, просто заключив внешнюю букву "C". Когда идентификаторы в C-заголовке конфликтуют с ключевыми словами C++, компилятор C++ будет жаловаться на это.
Например, я видел следующий сбой кода в g++:
extern "C" {
struct method {
int virtual;
};
}
Кинда имеет смысл, но есть кое-что, о чем следует помнить при портировании C-кода на C++.
Ответ 8
Он сообщает компилятору С++ искать имена этих функций в стиле C при связывании, потому что имена функций, скомпилированных в C и С++, различаются на этапе компоновки.
Ответ 9
extern "C" предназначен для распознавания компилятором С++ и для уведомления компилятора о том, что отмеченная функция (или должна быть) скомпилирована в стиле C. Так что, связывая, он ссылается на правильную версию функции из C.
Ответ 10
Я использовал '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);
}
Ответ 11
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)
{
//
//
}
Ответ 12
Этот ответ для нетерпеливых/имеет крайние сроки для встречи, только часть/простое объяснение ниже:
- в C++ вы можете иметь одно и то же имя в классе с помощью перегрузки (например, поскольку все они одинаковы с именами не могут быть экспортированы как-из dll и т.д.) решение этих проблем заключается в том, что они преобразуются в разные строки ( символы обозначают имя функции, а также аргументы, поэтому каждая из этих функций, даже с тем же именем, может быть однозначно идентифицирована (также называется,
- в C у вас нет перегрузки, имя функции уникально (поэтому отдельная строка для идентификации имени функции не требуется, поэтому символ - это имя функции)
Так
в C++, с именем, определяющим однозначно тождества каждой функции
в C, даже без имени, определяющего однозначно тождества, каждая функция
Чтобы изменить поведение C++, то есть указать, что для определенной функции не должно выполняться манипуляция имени, вы можете использовать extern "C" перед именем функции по любой причине, например, экспортировать функцию с определенным именем из dll, для использования его клиентами.
Прочтите другие ответы, чтобы получить более подробные/более правильные ответы.
Ответ 13
При смешивании C и С++ (т.е. вызов функции C из С++ и b. вызов функции С++ из C), сбой имени С++ вызывает проблемы с связыванием. Технически говоря, эта проблема возникает только тогда, когда функции вызываемого пользователя уже скомпилированы в двоичный файл (скорее всего, файл *.a) с использованием соответствующего компилятора.
Поэтому нам нужно использовать extern "C", чтобы отключить манипуляцию имени на С++.