Зачем нам нужен extern"C" {#include} в C++? [дубликат]

этот вопрос уже есть ответ здесь:

почему нам нужно использовать:

extern "C" {
#include <foo.h>
}

в частности:

  • когда мы должны использовать его?

  • что происходит в уровень компилятора / компоновщика, который требует от нас его использования?

  • как с точки зрения компиляции/связывания это решает проблемы, которые требуют от нас его использования?

10 ответов


C и c++ внешне похожи, но каждый компилируется в совершенно другой набор кода. При включении файла заголовка с компилятором C++ компилятор ожидает код c++. Если, однако, это заголовок C, то компилятор ожидает, что данные, содержащиеся в файле заголовка, будут скомпилированы в определенный формат-C++ 'ABI' или 'Application Binary Interface', поэтому компоновщик задыхается. Это предпочтительнее, чем передача данных c++ функции, ожидающей данных C.

(чтобы попасть в действительно, ABI C++, как правило, "искажает" имена своих функций / методов, поэтому вызывает printf() не помечая прототип как функцию C, C++ фактически генерирует вызов кода _Zprintf, плюс дополнительное дерьмо в конце.)

так: использовать extern "C" {...}; при включении заголовка c-это так просто. В противном случае у вас будет несоответствие в скомпилированном коде, и компоновщик задохнется. Для большинства заголовков, однако, вам даже не нужно extern потому что большинство заголовков system C будут уже учтите тот факт, что они могут быть включены кодом C++ и уже extern их код.


extern " C " определяет, как должны называться символы в сгенерированном объектном файле. Если функция объявлена без extern "C", имя символа в объектном файле будет использовать искажение имени c++. Вот пример.

данного теста.С вот так:

void foo() { }

сбор и перечисление символов в объектном файле дает:

$ g++ -c test.C
$ nm test.o
0000000000000000 T _Z3foov
                 U __gxx_personality_v0

функция foo фактически называется "_Z3foov". Эта строка содержит сведения о типе для возвращаемого типа и параметров, среди которых прочие вещи. Если вы вместо этого напишете тест.C вот так:

extern "C" {
    void foo() { }
}

затем скомпилируйте и посмотрите на символы:

$ g++ -c test.C
$ nm test.o
                 U __gxx_personality_v0
0000000000000000 T foo

вы получаете с рычага. Имя функции " foo "в объектном файле-это просто" foo", и у него нет всей причудливой информации о типе, которая исходит из имени mangling.

вы обычно включаете заголовок в extern" C " {}, если код, который идет с ним, был скомпилирован с компилятором C, но вы пытаетесь вызвать его из C++. Когда ты делаешь это, ты ... сообщая компилятору, что все объявления в заголовке будут использовать связь C. Когда вы связываете свой код, ваш .o файлы будут содержать ссылки на" foo", а не" _Z3fooblah", который, надеюсь, соответствует тому, что находится в библиотеке, с которой вы связываетесь.

большинство современных библиотек поставят охрану вокруг таких заголовков, чтобы символы объявлялись с правильной связью. например, во многих стандартных заголовках вы найдете:

#ifdef __cplusplus
extern "C" {
#endif

... declarations ...

#ifdef __cplusplus
}
#endif

Это гарантирует, что когда C++ кода включает заголовок, символы в вашем объектном файле соответствуют тому, что находится в библиотеке C. Вам нужно только поместить extern" C " {} вокруг вашего заголовка C, если он старый и еще не имеет этих охранников.


В C++ у вас могут быть разные сущности с общим именем. Например, вот список функций с именем фу:

  • A::foo()
  • B::foo()
  • C::foo(int)
  • C::foo(std::string)

чтобы различать их все, компилятор C++ создаст уникальные имена для каждого в процессе под названием name-mangling или decorating. Компиляторы C этого не делают. Кроме того, каждый компилятор C++ может сделать это это другой путь.

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


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

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


когда мы должны использовать его?

когда вы связываете c libaries в объектные файлы c++

что происходит в уровень компилятора / компоновщика, который требует нас использовать его?

C и C++ используют разные схемы для именования символов. Это говорит компоновщику использовать схему C при связывании в данной библиотеке.

как с точки зрения компиляции / связывания это решает проблемы, которые требуют от нас использовать его?

использование схемы именования C позволяет ссылаться на символы C-стиля. В противном случае компоновщик попробует символы в стиле C++, которые не будут работать.


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

В C правило очень просто, символы все равно находятся в одном пространстве имен. Таким образом, целое число "socks" хранится как "socks", а функция count_socks хранится как "count_socks".

компоновщики были построены для C и других языков, таких как C с этим простым правилом именования символов. Таким образом, символы в компоновщике-это просто простые строки.

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

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


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

например, если у вас есть проект с 3 файлами, util.c, util.h и main.cpp и как .с и. cpp-файлы компилируются с компилятором C++ (g++, cc и т. д.), Тогда он действительно не нужен и может даже вызвать ошибки компоновщика. Если в процессе сборки используется обычный компилятор C для util.c, тогда вам нужно будет использовать extern " C " при включении util.h.

происходит то, что C++ кодирует параметры функции в ее имени. Вот как работает перегрузка функций. Все, что происходит с функцией C, - это добавление подчеркивания ("_") в начало имени. Без использования extern " C " компоновщик будет искать функцию с именем DoSomething@@int@float (), когда фактическое имя функции is _DoSomething () или просто DoSomething ().

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


на extern "C" {} construct указывает компилятору не выполнять искажение имен, объявленных в фигурных скобках. Обычно компилятор C++ "улучшает" имена функций, чтобы они кодировали информацию о типе аргументов и возвращаемое значение; это называется исковеркали имя. The extern "C" построить предотвращает коверкание.

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


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


Это используется для решения проблем с искажением имен. extern C означает, что функции находятся в" плоском " API C-стиле.