Зачем нам нужен extern "C" {#include <foo.h>} в С++?

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

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

В частности:

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

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

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

117
задан 16 сент. '08 в 2:19
источник поделиться
10 ответов

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

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

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

106
ответ дан 16 сент. '08 в 2:27
источник

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

Учитывая test.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

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

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

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

#ifdef __cplusplus
extern "C" {
#endif

... declarations ...

#ifdef __cplusplus
}
#endif

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

102
ответ дан 16 сент. '08 в 2:38
источник

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

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

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

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

18
ответ дан 16 сент. '08 в 2:27
источник

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

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

13
ответ дан 16 сент. '08 в 2:24
источник

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

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

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

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

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

10
ответ дан 16 сент. '08 в 2:28
источник

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

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

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

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

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

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

10
ответ дан 16 сент. '08 в 2:29
источник

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

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

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

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

6
ответ дан 16 сент. '08 в 2:39
источник

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

6
ответ дан 16 сент. '08 в 2:25
источник

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

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

5
ответ дан 16 сент. '08 в 2:30
источник

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

5
ответ дан 16 сент. '08 в 2:25
источник

Другие вопросы по меткам или Задайте вопрос