Каков эффект extern "C" в С++?

Что именно помещает extern "C" в код С++?

Например:

extern "C" {
   void foo();
}
1228
задан Litherum 25 июня '09 в 5:10
источник поделиться
13 ответов

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" ):

  • extern "C" - спецификация привязки
  • Каждый компилятор должен предоставить ссылку "C"
  • спецификация привязки должна выполняться только в области пространства имен
  • все типы функций, имена функций и имена переменных имеют языковой привязку См. комментарий Ричарда: Только функция имена и имена переменных с внешней связью имеют языковые связи
  • два типа функций с различными языковыми связями - это разные типы, даже если они идентичны друг другу
  • Спецификация связей спецификаций, внутренняя определяет конечную связь
  • extern "C" игнорируется для членов класса
  • не более одной функции с определенным именем может иметь ссылку "C" (независимо от пространства имен).
  • extern "C" заставляет функцию иметь внешнюю привязку (не может сделать ее статической) См. комментарий Ричарда: 'static' внутри 'extern "C" ' действителен; объявленная сущность имеет внутреннюю связь и, следовательно, не имеет языковой привязки
  • Связывание с С++ с объектами, определенными на других языках, и объектами, определенными на С++ с других языков, определяется реализацией и зависит от языка. Только там, где стратегии компоновки объектов двух языковых реализаций достаточно схожи, такая связь может быть достигнута.
1227
ответ дан Faisal Vali 25 июня '09 в 5:12
источник поделиться

Просто хотел добавить немного информации, так как я еще не видел его.

Вы очень часто видите код в заголовках 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"
}

который, как я полагаю, выполняет то же самое.

Не уверен, какой путь лучше, но я видел оба.

245
ответ дан UncaAlby 21 окт. '12 в 4:08
источник поделиться

В каждой программе на С++ все нестатические функции представлены в двоичном файле как символы. Эти символы представляют собой специальные текстовые строки, которые однозначно идентифицируют функцию в программе.

В C имя символа совпадает с именем функции. Это возможно, потому что в C две нестатические функции могут иметь одно и то же имя.

Поскольку С++ допускает перегрузку и имеет множество функций, которые C не имеет - как классы, функции-члены, спецификации исключений, невозможно просто использовать имя функции в качестве имени символа. Чтобы решить эту проблему, С++ использует так называемое управление именами, которое преобразует имя функции и всю необходимую информацию (например, число и размер аргументов) в какую-то странную строку, обрабатываемую только компилятором и компоновщиком.

Итак, если вы укажете функцию extern C, компилятор не будет выполнять с ним имя, и он может быть напрямую доступ к нему с использованием имени символа в качестве имени функции.

Это удобно при использовании dlsym() и dlopen() для вызова таких функций.

171
ответ дан sud03r 25 июня '09 в 8:22
источник поделиться

Декомпилируйте 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" при вызове:

  • C из C++: скажите g++ чтобы ожидать неперепутанных символов, созданных gcc
  • C++ из C: сообщите g++ чтобы генерировать unmangled символы для 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) { }
}

Минимальный runnable 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() { 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 не произвело.

Пример на GitHub.

Минимальный runnable 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 не может найти.

Пример на GitHub.

126
ответ дан Ciro Santilli 新疆改造中心 六四事件 法轮功 29 мая '15 в 13:06
источник поделиться

С++ управляет именами функций для создания объектно-ориентированного языка с процедурного языка

Большинство языков программирования не построены поверх существующих языков программирования. С++ построен поверх 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.c && &./printMe;
1. PrintMe определяется несколько раз.

Компилятор С++ скомпилирует приведенный выше пример. Не имеет значения, что printMe определяется дважды.

g++ -o printMe printMe.c && &./printMe;

Это связано с тем, что компилятор С++ неявно переименовывает (mangles) на основе их параметров. В C эта функция не поддерживалась. Однако, когда С++ был построен поверх C, язык был спроектирован так, чтобы быть объектно-ориентированным, и необходим для поддержки способности создавать разные классы с помощью методов (функций) с тем же именем и переопределять методы (переопределение) на основе разных параметров.

Экстерн говорит: "Не изменяйте имена функций"

Однако представьте, что у нас есть старый файл C с именем "parent.c", который include имена функций из других устаревших файлов C, "parent.h", "child.h" и т.д. Если унаследованный "родительский". c "запускается через компилятор С++, тогда имена функций будут искажены, и они больше не будут соответствовать именам функций, указанным в" parent.h "," child.h "и т.д. - поэтому имена функций в этих внешних файлы также должны быть искалечены. И это может стать довольно грязным. Таким образом, было бы удобно предоставить ключевое слово, которое может сообщить компилятору С++ не изменять имя функции.

Ключевое слово extern указывает компилятору С++ не называть имена функций (переименовывать). Пример использования: extern void printMe(int a);

24
ответ дан tfmontague 12 февр. '17 в 4:50
источник поделиться

Он изменяет связь функции таким образом, что функция может быть вызвана из C. На практике это означает, что имя функции не mangled.

22
ответ дан Employed Russian 25 июня '09 в 5:12
источник поделиться

Ни один C-заголовок не будет компилироваться с помощью extern "C". Когда идентификаторы в C-заголовке конфликтуют с ключевыми словами С++, компилятор С++ будет жаловаться на это.

Например, я видел, как следующий код сбой в g++:

extern "C" {
struct method {
    int virtual;
};
}

Kinda имеет смысл, но нужно помнить при переносе C-кода на С++.

21
ответ дан Sander Mertens 10 янв. '13 в 1:16
источник поделиться

Он сообщает компилятору С++ искать имена этих функций в стиле C при связывании, потому что имена функций, скомпилированных в C и С++, различаются на этапе компоновки.

16
ответ дан Mark Rushakoff 25 июня '09 в 5:12
источник поделиться

extern "C" предназначен для распознавания компилятором С++ и для уведомления компилятора о том, что отмеченная функция (или должна быть) скомпилирована в стиле C. Так что, связывая, он ссылается на правильную версию функции из C.

12
ответ дан Flami 10 апр. '12 в 12:46
источник поделиться

Я использовал '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);
}
4
ответ дан SturmCoder 01 июля '14 в 18:43
источник поделиться

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)
{
    //
    //
}
3
ответ дан Yogeesh H T 17 нояб. '15 в 15:09
источник поделиться

При смешивании C и С++ (т.е. вызов функции C из С++ и b. вызов функции С++ из C), сбой имени С++ вызывает проблемы с связыванием. Технически говоря, эта проблема возникает только тогда, когда функции вызываемого пользователя уже скомпилированы в двоичный файл (скорее всего, файл *.a) с использованием соответствующего компилятора.

Поэтому нам нужно использовать extern "C", чтобы отключить манипуляцию имени на С++.

1
ответ дан Trombe 06 июля '17 в 7:04
источник поделиться

Этот ответ для нетерпеливых/имеет крайние сроки для встречи, только часть/простое объяснение ниже:

  • в C++ вы можете иметь одно и то же имя в классе с помощью перегрузки (например, поскольку все они одинаковы с именами не могут быть экспортированы как-из dll и т.д.) решение этих проблем заключается в том, что они преобразуются в разные строки ( символы обозначают имя функции, а также аргументы, поэтому каждая из этих функций, даже с тем же именем, может быть однозначно идентифицирована (также называется,
  • в C у вас нет перегрузки, имя функции уникально (поэтому отдельная строка для идентификации имени функции не требуется, поэтому символ - это имя функции)

Так
в C++, с именем, определяющим однозначно тождества каждой функции
в C, даже без имени, определяющего однозначно тождества, каждая функция

Чтобы изменить поведение C++, то есть указать, что для определенной функции не должно выполняться манипуляция имени, вы можете использовать extern "C" перед именем функции по любой причине, например, экспортировать функцию с определенным именем из dll, для использования его клиентами.

Прочтите другие ответы, чтобы получить более подробные/более правильные ответы.

0
ответ дан Manohar Reddy Poreddy 24 июля '18 в 16:41
источник поделиться

Другие вопросы по меткам