Какое влияние оказывает extern «C» в C ++?

1262

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

Например:

extern "C" {
   void foo();
}
  • 68
    Я хотел бы представить вам эту статью: http://www.agner.org/optimize/calling_conventions.pdf. Она расскажет вам гораздо больше о соглашении о вызовах и разнице между компиляторами.
  • 0
    @Litherum В верхней части моей головы, это говорит компилятору компилировать эту область кода с использованием C, учитывая, что у вас есть кросс-компилятор. Кроме того, это означает, что у вас есть Cpp-файл, в котором у вас есть эта функция foo() .
Теги:
linkage
name-mangling
extern-c

13 ответов

1247
Лучший ответ

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" ' действителен; объявленная сущность имеет внутреннюю связь и, следовательно, не имеет языковой привязки
  • Связывание с С++ с объектами, определенными на других языках, и объектами, определенными на С++ с других языков, определяется реализацией и зависит от языка. Только там, где стратегии компоновки объектов двух языковых реализаций достаточно схожи, такая связь может быть достигнута.
  • 0
    Похоже, это скрыто ... есть ли функциональная причина, по которой я бы поставил 'extern' C '' перед функцией (кроме вызова ее из отдельной программы на C)?
  • 17
    Компилятор C не использует искажения, которые использует c ++. Поэтому, если вы хотите вызвать интерфейс ac из программы на c ++, вы должны четко объявить, что интерфейс c является «extern c».
Показать ещё 17 комментариев
279

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

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

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

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

  • 10
    Есть четкая разница. В первом случае, если вы скомпилируете этот файл с помощью обычного компилятора gcc, он сгенерирует объект, имя которого не искажено. Если затем связать объекты C и C ++ с компоновщиком, он НЕ найдет функции. Вам нужно будет включить эти файлы «устаревшего заголовка» с ключевым словом extern, как во втором блоке кода.
  • 7
    @Anne: Компилятор C ++ также будет искать не исправленные имена, потому что он видел в заголовке extern "C" ). Отлично работает, использовал эту технику много раз.
Показать ещё 7 комментариев
185

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

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

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

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

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

  • 0
    что вы имеете в виду под рукой? символ name = имя функции сделает имя символа переданным в dlsym или другое?
  • 1
    @ Ошибка: да. В общем случае в общем случае невозможно dlopen () совместно используемой библиотеки C ++, имеющей только заголовочный файл, и выбрать правильную функцию для загрузки. (На x86 есть опубликованная спецификация распределения имен в форме Itanium ABI, которую все известные мне компиляторы x86 используют для манипулирования именами функций C ++, но ничего в языке не требует этого.)
143

Декомпилируйте сгенерированный двоичный файл 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++ чтобы генерировать не исправленные символы для использования 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) { }
}

Минимальный работоспособный 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(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++ from немного сложнее: нам нужно вручную создавать не искаженные версии каждой функции, которую мы хотим представить.

Здесь мы иллюстрируем, как выставить перегрузки функции 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.

  • 5
    Лучший ответ, поскольку вы 1) явно упоминаете, что extern "C" { помогает вам вызывать не исправленные функции C изнутри программ C ++ , а также неуправляемые функции C ++ изнутри программ C , что в других ответах не так очевидно, и 2) потому что Вы показываете отличные примеры каждого. Спасибо!
  • 1
    Это лучший, хорошо объясненный ответ здесь. Спасибо
33

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

Большинство языков программирования не построены поверх существующих языков программирования. C++ построен поверх C, и, кроме того, он является объектным -o языком программирования, построенным из процедурного языка программирования, и по этой причине есть 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;
}

Компилятор 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 и должен был поддерживать возможность создавать разные классы с методами (функциями) с одинаковыми именами и переопределять методы (переопределение методов ).) на основе разных параметров.

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

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

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

  • 0
    мы не можем использовать extern "C" если у нас есть только файл dll ? Я имею в виду, если у нас нет заголовочного файла, а есть только исходный файл (только реализации) и использование его функции через указатель на функцию. в этом состоянии мы просто использовали функции (независимо от их имени).
  • 0
    @tfmontague, для меня ты прибил это правильно! прямо в голову.
24

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

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

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

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

  • 11
    extern "C" означает использование связи C, как описано в других ответах. Это не значит «компилировать содержимое как C» или что-то еще. int virtual; недопустимо в C ++, и указание другой связи не меняет этого.
  • 1
    ... или режим вообще, любой код, содержащий синтаксическую ошибку, не будет компилироваться.
Показать ещё 1 комментарий
24

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

  • 0
    искаженный или украшенный, каков правильный термин?
  • 3
    Mangled - это термин, который обычно используется ... Не верьте, что я когда-либо видел "украшенный", использованный с этим значением.
Показать ещё 1 комментарий
17

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

12

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

5

Я использовал '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);
}
  • 2
    Богус. extern "C" и __declspec(dllexport) не связаны. Первый контролирует оформление символов, последний отвечает за создание экспортной записи. Вы также можете экспортировать символ, используя оформление имени C ++. Помимо полного упущения из этого вопроса, есть и другие ошибки в примере кода. Например, main экспортируемый из вашей DLL, не объявляет возвращаемое значение. Или созыв соглашения, в этом отношении. При импорте вы приписываете соглашение о случайных вызовах ( WINAPI ) и используете неправильный символ для 32-битных сборок (должно быть _main или _main@0 ). Извините, -1.
  • 0
    На самом деле, это всего лишь рабочий пример, где я использовал его прежде, чтобы экспортировать функцию, и его следует использовать просто для того, чтобы понять, как его можно использовать. Я не сказал, что это нужно использовать таким образом. Кроме того, DLL main (), объявленная в dll, имеет тип DLL, который имеет тип -> (extern "C" __declspec (dllexport)), а не int или string, определенный выше, и он не возвращает ничего похожего на void, я назвал его "main" , но его можно назвать как угодно, и его не следует путать с «main» типа int в самом исполняемом файле. Однако это компилируется и прекрасно работает со многими другими функциями в dll с параметрами и возвращает также.
Показать ещё 10 комментариев
4

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)
{
    //
    //
}
1

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

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

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

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

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

1

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню