Що саме робить введення 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 ++ не може просто використовувати ім'я функції як унікальний ідентифікатор для посилання, тому він керує ім'ям, додаючи інформацію про аргументи. Компілятору змінного струму не потрібно маніпулювати назвою, оскільки ви не можете перевантажувати імена функцій у C. Коли ви заявляєте, що функція має зовнішню зв'язок "C" у C ++, компілятор C ++ не додає інформацію про аргументи / параметри до імені, використовуваного для зв’язок.
Просто ви знаєте, що ви можете вказати посилання "C" на кожне окреме оголошення / визначення явно або використовувати блок для групування послідовності декларацій / визначень, щоб мати певний зв'язок:
extern "C" void foo(int);
extern "C"
{
void g(char);
int i;
}
Якщо ви дбаєте про технічні характеристики, вони перераховані в розділі 7.5 стандарту C ++ 03, ось короткий підсумок (з акцентом на зовнішнє "C"):
extern "C" { int i; }
це визначення. Це може бути не тим, що ви задумали, поруч із невизначенням void g(char);
. Щоб це не було визначення, вам знадобиться extern "C" { extern int i; }
. З іншого боку, синтаксис однієї декларації без дужок робить декларацію не визначенням: extern "C" int i;
це те саме, щоextern "C" { extern int i; }
Просто хотілося додати трохи інформації, оскільки я її ще не бачив у публікації.
Ви дуже часто бачите код у заголовках С так:
#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
використанняРечі, які не працюють у зовнішній С
Стає очевидним, що будь-яка функція 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 має лише один можливий некерований символ, тому додаткової роботи не потрібно.
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 ++ використовує так зване ім'я mangling, яке перетворює ім'я функції та всю необхідну інформацію (наприклад, кількість та розмір аргументів) у якусь дивну рядок, оброблений лише компілятором та лінкером.
Отже, якщо ви вказали функцію, яка буде зовнішньою 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;
}
Компілятор змінного струму не буде компілювати вищевказаний приклад, оскільки одна і та ж функція 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 ++ не маніпулювати ім'ям функції.
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;
};
}
Щось має сенс, але це щось, про що слід пам’ятати, коли переносите C-код на C ++.
extern "C"
означає використовувати зв'язок C, як описано в інших відповідях. Це не означає "компілювати вміст як C" або щось інше. int virtual;
недійсний у C ++, і якщо вказати різні зв’язки, це не змінить.
Це змінює зв'язок функції таким чином, що функцію можна викликати від C. На практиці це означає, що ім'я функції не є керованим .
undname
.
Він повідомляє компілятор C ++ шукати назви цих функцій у стилі С під час з'єднання, оскільки назви функцій, складених на C та C ++, відрізняються під час етапу зв’язування.
Я раніше використовував "extern" C "'для файлів dll (динамічна бібліотека посилань), щоб зробити etc. 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 ++, з ім'ям mangling однозначно ідентифікує кожну функцію
в C, навіть без імені mangling однозначно ідентифікує кожну функцію
Щоб змінити поведінку C ++, тобто вказати, що керування іменами не повинно відбуватися для певної функції, ви можете використовувати зовнішнє "C" перед назвою функції з будь-якої причини, наприклад, експортуючи функцію з певним іменем з dll , для використання його клієнтами.
Прочитайте інші відповіді, щоб отримати детальніші / правильніші відповіді.
При змішуванні C і C ++ (тобто, виклику функції C з C ++; і b. Виклику функції C ++ від C), керування іменем C ++ викликає проблеми з пов’язанням. З технічної точки зору ця проблема виникає лише тоді, коли функції виклику вже були зібрані у двійковий (швидше за все, файл * .a бібліотеки) за допомогою відповідного компілятора.
Тому нам потрібно використовувати зовнішній "C", щоб відключити керування іменами в C ++.
Не конфліктуючи з іншими хорошими відповідями, я додам трохи свого прикладу.
Що саме робить компілятор C ++ : він маніпулює іменами в процесі компіляції, отже, нам потрібно сказати компілятору, щоб лікувати 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.