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



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



например:



extern "C" {
void foo();
}
1863   13  

13 ответов:

extern " C "делает имя функции в 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, вот краткое резюме (с акцентом на extern "C"):

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

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

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

#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"
}

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

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

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

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

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

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

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

декомпилировать a 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()
    

вывод: оба из следующих типов символов не mangled:

  • определена
  • объявлен, но не определен (Ndx = UND), которые будут предоставлены по ссылке или во время выполнения из другого объектного файла

так что вам понадобится extern "C" как при вызове:

  • C от C++: скажи g++ ожидать незамутненные символы, созданные gcc
  • C++ от C: tell 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 имеет только один возможный неискаженный символ, поэтому никакой дополнительной работы не требуется.

главная.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; }

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:

#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.

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

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

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

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

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

Extern говорит "не калечить имена функций"

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

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

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

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

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

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

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

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

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

я использовал модификатор extern "с", Перед для 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);
}

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

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

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

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

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

Так
в C++ с именем mangling уникально идентифицирует каждую функцию
в C даже без искажения имени однозначно идентифицирует каждую функцию

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

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

Comments

    Ничего не найдено.