c++ - зачем - extern variable c




Каков эффект extern «C» в C++? (9)

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

Например:

extern "C" {
   void foo();
}

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;
}

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

Экстерн говорит: «Не подделывайте имена функций»

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

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


extern «C» делает имя функции в C ++ имеет ссылку «C» (компилятор не изменяет имя), чтобы клиентский код C мог ссылаться на (т. е. использовать) вашу функцию, используя совместимый заголовочный файл «C», который содержит только объявление вашей функции. Определение вашей функции содержится в двоичном формате (который был скомпилирован вашим C ++-компилятором), который связывает клиентский C-linker с именем 'C'.

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

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

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

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

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

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


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

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

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

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


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


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

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


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

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

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

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

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


Я использовал «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);
}

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

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

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





extern-c