Зачем нам нужен extern "C" {#include <foo.h>} в C ++? - PullRequest
129 голосов
/ 16 сентября 2008

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

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

В частности:

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

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

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

Ответы [ 11 ]

116 голосов
/ 16 сентября 2008

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

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

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

107 голосов
/ 16 сентября 2008

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

Данный тест. С так:

void foo() { }

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

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

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

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

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

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

Вы получаете связь С. Имя функции «foo» в объектном файле просто «foo», и в ней нет всей информации о причудливых типах, получаемой из искажения имени.

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

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

#ifdef __cplusplus
extern "C" {
#endif

... declarations ...

#ifdef __cplusplus
}
#endif

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

21 голосов
/ 16 сентября 2008

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

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

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

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

14 голосов
/ 16 сентября 2008

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

Чтобы решить эту проблему, мы говорим компилятору C ++ работать в режиме "C", чтобы он выполнял манипулирование именами так же, как и компилятор C. После этого исправлены ошибки компоновщика.

11 голосов
/ 16 сентября 2008

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

Когда вы связываете библиотеки C в объектные файлы C ++

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

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

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

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

10 голосов
/ 16 сентября 2008

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

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

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

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

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

7 голосов
/ 16 сентября 2008

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

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

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

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

7 голосов
/ 16 сентября 2008

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

6 голосов
/ 16 сентября 2008

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

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

5 голосов
/ 16 сентября 2008

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

...