Каково влияние extern "C" в C ++? - PullRequest
1420 голосов
/ 25 июня 2009

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

Например:

extern "C" {
   void foo();
}

Ответы [ 13 ]

1397 голосов
/ 25 июня 2009

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" является спецификацией связи
  • Каждый компилятор требуется для обеспечения связи "C"
  • спецификация связи должна встречаться только в области имен
  • все типы функций, имена функций и имена переменных имеют языковую связь См. Комментарий Ричарда: Язык имеют только имена функций и имена переменных с внешней связью связь
  • два типа функций с разными языковыми связями - это разные типы, даже если в противном случае они идентичны
  • гнездо спецификаций связи, внутренняя определяет окончательную связь
  • extern "C" игнорируется для учеников
  • не более одной функции с конкретным именем может иметь связь "C" (независимо от пространства имен)
  • extern "C" заставляет функцию иметь внешнюю связь (не может сделать ее статичной) См. Комментарий Ричарда: 'static' внутри 'extern' C "' является действительным; заявленная сущность имеет внутреннюю связь и поэтому не имеет языковой связи
  • Связь с C ++ с объектами, определенными в других языках, и с объектами, определенными в C ++ из других языков, определяется реализацией и зависит от языка. Только там, где стратегии размещения объектов двух языковых реализаций достаточно похожи, такая связь может быть достигнута
283 голосов
/ 21 октября 2012

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

Вы очень часто будете видеть код в заголовках C, например:

#ifdef __cplusplus
extern "C" {
#endif

// all of your legacy C code here

#ifdef __cplusplus
}
#endif

Это позволяет вам использовать этот заголовочный файл C вместе с вашим кодом C ++, потому что будет определен макрос "__cplusplus". Но вы можете также по-прежнему использовать его с вашим устаревшим кодом C, где макрос определен NOT , поэтому он не увидит уникальную конструкцию C ++.

Хотя я также видел код C ++, такой как:

extern "C" {
#include "legacy_C_header.h"
}

что я представляю, выполняет то же самое.

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

196 голосов
/ 25 июня 2009

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

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

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

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

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

188 голосов

Декомпилируйте 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(); }

Компиляция с GCC 4.8 Linux ELF Вывод:

g++ -c main.cpp

Декомпилировать таблицу символов:

readelf -s main.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++, чтобы сгенерировать не исправленные символы для 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; }

Пробег:

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.

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 .

Проверено в Ubuntu 18.04.

39 голосов
/ 12 февраля 2017

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

Большинство языков программирования не построены поверх существующих языков программирования. 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;
}

Компилятор C не будет компилировать приведенный выше пример, поскольку одна и та же функция printMe определяется дважды (даже если они имеют разные параметры int a против char a).

gcc -o printMe printMe.c && ./printMe;
1 ошибка. PrintMe определяется более одного раза.

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

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

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

extern "C" говорит "не искажать имена функций C"

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

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

27 голосов
/ 25 июня 2009

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

25 голосов
/ 10 января 2013

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

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

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

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

19 голосов
/ 25 июня 2009

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

12 голосов
/ 10 апреля 2012

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

6 голосов
/ 01 июля 2014

Ранее я использовал '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);
}
...