C и C ++ внешне похожи, но каждый компилируется в совершенно другой набор кода. Когда вы включаете заголовочный файл с компилятором C ++, компилятор ожидает код C ++. Однако, если это заголовок C, то компилятор ожидает, что данные, содержащиеся в заголовочном файле, будут скомпилированы в определенный формат - C ++ 'ABI' или 'Application Binary Interface', поэтому компоновщик захлебывается. Это предпочтительнее, чем передавать данные C ++ функции, ожидающей данные C.
(Чтобы разобраться в самом деле, ABI в C ++, как правило, «искажает» имена своих функций / методов, поэтому при вызове printf()без пометки прототипа как функции C, C ++ на самом деле генерирует вызов кода _Zprintf, плюс дополнительное дерьмо в конце. )
Итак: используйте extern "C" {...}при включении переменного заголовка - это так просто. В противном случае у вас будет несоответствие в скомпилированном коде, и компоновщик захлебнется. Однако для большинства заголовков вам даже не понадобится, externпотому что большинство системных заголовков C уже учитывают тот факт, что они могут быть включены в код C ++ и уже в externих код.
Не могли бы вы подробнее остановиться на том, что «большинство системных заголовков C уже учитывают тот факт, что они могут быть включены в код C ++ и уже вне их кода». ?
Булат М.
7
@BulatM. Они содержат что-то вроде этого: #ifdef __cplusplus extern "C" { #endif поэтому при включении из файла C ++ они по-прежнему рассматриваются как заголовок C.
Кальмариус
111
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
Вы получаете связь C. Имя функции «foo» в объектном файле просто «foo», и в ней нет всей информации о причудливых типах, получаемой из искажения имени.
Обычно вы включаете заголовок в extern "C" {}, если код, который идет с ним, был скомпилирован с помощью компилятора C, но вы пытаетесь вызвать его из C ++. Когда вы делаете это, вы говорите компилятору, что все объявления в заголовке будут использовать связь C. Когда вы связываете свой код, ваши файлы .o будут содержать ссылки на «foo», а не «_Z3fooblah», что, как мы надеемся, соответствует тому, что находится в библиотеке, с которой вы ссылаетесь.
Большинство современных библиотек устанавливают защиту вокруг таких заголовков, чтобы символы объявлялись с правильной связью. Например, во многих стандартных заголовках вы найдете:
Это гарантирует, что когда код C ++ включает заголовок, символы в вашем объектном файле совпадают с тем, что находится в библиотеке C. Вам следует только поместить extern "C" {} вокруг заголовка C, если он старый и у него нет этих охранников.
В C ++ у вас могут быть разные сущности, которые имеют общее имя. Например, вот список функций с именем foo :
A::foo()
B::foo()
C::foo(int)
C::foo(std::string)
Чтобы различать их все, компилятор C ++ создаст уникальные имена для каждого в процессе, называемом распределением имен или декорированием. Компиляторы C не делают этого. Кроме того, каждый компилятор C ++ может делать это по-своему.
extern "C" говорит компилятору C ++ не выполнять никакого искажения имени в коде в фигурных скобках. Это позволяет вам вызывать функции C изнутри C ++.
Это связано с тем, как разные компиляторы выполняют сортировку имен. Компилятор C ++ будет манипулировать именем символа, экспортируемого из файла заголовка, совершенно иначе, чем компилятор C, поэтому при попытке связывания вы получите сообщение об ошибке компоновщика, говорящее об отсутствии символов.
Чтобы решить эту проблему, мы говорим компилятору C ++ работать в режиме «C», поэтому он выполняет преобразование имен так же, как и компилятор C. После этого исправлены ошибки компоновщика.
C и C ++ имеют разные правила относительно имен символов. Символы - это то, как компоновщик знает, что вызов функции «openBankAccount» в одном объектном файле, созданный компилятором, является ссылкой на функцию, которую вы назвали «openBankAccount» в другом объектном файле, созданном из другого исходного файла тем же (или совместимым) компилятор. Это позволяет сделать программу из более чем одного исходного файла, что облегчает работу с большим проектом.
В Си правило очень простое, символы все равно находятся в одном пространстве имен. Таким образом, целое число «носки» сохраняется как «носки», а функция count_socks - как «счетчики».
Линкеры были созданы для C и других языков, таких как C, с этим простым правилом именования символов. Так что символы в компоновщике - это просто простые строки.
Но в C ++ язык позволяет вам иметь пространства имен, полиморфизм и другие вещи, которые противоречат такому простому правилу. Все шесть ваших полиморфных функций, называемых «add», должны иметь разные символы, иначе неправильная будет использоваться другими объектными файлами. Это делается путем "искажения" (это технический термин) имен символов.
При связывании кода C ++ с библиотеками C или кодом, вам нужно extern «C», что-либо написанное на C, например, файлы заголовков для библиотек C, чтобы сообщить компилятору C ++, что эти имена символов не должны быть искажены, в то время как остальные Ваш код C ++, конечно, должен быть искажен, иначе он не будет работать.
Когда вы связываете библиотеки C в объектные файлы C ++
Что происходит на уровне компилятора / компоновщика, что требует от нас его использования?
C и C ++ используют разные схемы именования символов. Это говорит компоновщику использовать схему C при компоновке в данной библиотеке.
Как с точки зрения компиляции / компоновки это решает проблемы, которые требуют от нас его использования?
Использование схемы именования C позволяет ссылаться на символы стиля C. В противном случае компоновщик будет использовать символы стиля C ++, которые не будут работать.
Вы должны использовать 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 ++.
Компилятор C ++ создает имена символов иначе, чем компилятор C. Итак, если вы пытаетесь вызвать функцию, которая находится в файле C, скомпилированном как код C, вам нужно сообщить компилятору C ++, что имена символов, которые он пытается разрешить, выглядят иначе, чем по умолчанию; в противном случае шаг ссылки потерпит неудачу.
extern "C" {}Конструкция инструктирует компилятор не выполнять коверкая по именам , объявленных в фигурных скобках. Обычно компилятор C ++ «улучшает» имена функций, чтобы они кодировали информацию о типе аргументов и возвращаемого значения; это называется искалеченным именем . extern "C"Конструкция предотвращает коверкание.
Обычно используется, когда код C ++ должен вызывать библиотеку языка C. Это также может быть использовано при предоставлении функции C ++ (например, из DLL) клиентам C.
Num:ValueSizeTypeBindVisNdxName8:00000000000000006 FUNC GLOBAL DEFAULT 1 _Z1fv
9:00000000000000066 FUNC GLOBAL DEFAULT 1 ef
10:000000000000000c16 FUNC GLOBAL DEFAULT 1 _Z1hv
11:00000000000000000 NOTYPE GLOBAL DEFAULT UND _Z1gv
12:00000000000000000 NOTYPE GLOBAL DEFAULT UND eg
интерпретация
Мы видим, что:
efи egхранились в символах с тем же именем, что и в коде
другие символы были искажены. Давайте разберем их:
Вывод: оба следующих типа символов не были искажены:
определенный
объявлен, но не определен ( Ndx = UND), должен быть предоставлен в ссылке или во время выполнения из другого объектного файла
Так что вам понадобится extern "C"оба при звонке:
C из C ++: сказать, g++чтобы ожидать непогашенные символы, произведенныеgcc
C ++ от C: сказать, g++чтобы генерировать не исправленные символы для gccиспользования
Вещи, которые не работают в extern C
Становится очевидным, что любая функция C ++, требующая искажения имени, не будет работать внутри extern C:
extern"C"{// Overloading.// error: declaration of C function ‘void f(int)’ conflicts withvoid f();void f(int i);// Templates.// error: template with C linkagetemplate<class C>void f(C i){}}
Вызов C из C ++ довольно прост: каждая функция C имеет только один возможный необработанный символ, поэтому никакой дополнительной работы не требуется.
#ifndef C_H
#define C_H
/* This ifdef allows the header to be used from both C and C++. */#ifdef __cplusplus
extern"C"{#endifint f();#ifdef __cplusplus
}#endif#endif
#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"{#endifint 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);}
#ifdef __cplusplus extern "C" { #endif
поэтому при включении из файла C ++ они по-прежнему рассматриваются как заголовок C.extern "C" определяет, как символы в сгенерированном объектном файле должны быть названы. Если функция объявлена без внешнего «C», имя символа в объектном файле будет использовать искажение имени C ++. Вот пример.
Данный тест.С вроде так:
Компиляция и перечисление символов в объектном файле дает:
Функция foo фактически называется "_Z3foov". Эта строка содержит информацию о типе для возвращаемого типа и параметров, среди прочего. Если вы вместо этого напишите test.C, как это:
Затем скомпилируйте и посмотрите на символы:
Вы получаете связь C. Имя функции «foo» в объектном файле просто «foo», и в ней нет всей информации о причудливых типах, получаемой из искажения имени.
Обычно вы включаете заголовок в extern "C" {}, если код, который идет с ним, был скомпилирован с помощью компилятора C, но вы пытаетесь вызвать его из C ++. Когда вы делаете это, вы говорите компилятору, что все объявления в заголовке будут использовать связь C. Когда вы связываете свой код, ваши файлы .o будут содержать ссылки на «foo», а не «_Z3fooblah», что, как мы надеемся, соответствует тому, что находится в библиотеке, с которой вы ссылаетесь.
Большинство современных библиотек устанавливают защиту вокруг таких заголовков, чтобы символы объявлялись с правильной связью. Например, во многих стандартных заголовках вы найдете:
Это гарантирует, что когда код C ++ включает заголовок, символы в вашем объектном файле совпадают с тем, что находится в библиотеке C. Вам следует только поместить extern "C" {} вокруг заголовка C, если он старый и у него нет этих охранников.
источник
В C ++ у вас могут быть разные сущности, которые имеют общее имя. Например, вот список функций с именем foo :
A::foo()
B::foo()
C::foo(int)
C::foo(std::string)
Чтобы различать их все, компилятор C ++ создаст уникальные имена для каждого в процессе, называемом распределением имен или декорированием. Компиляторы C не делают этого. Кроме того, каждый компилятор C ++ может делать это по-своему.
extern "C" говорит компилятору C ++ не выполнять никакого искажения имени в коде в фигурных скобках. Это позволяет вам вызывать функции C изнутри C ++.
источник
Это связано с тем, как разные компиляторы выполняют сортировку имен. Компилятор C ++ будет манипулировать именем символа, экспортируемого из файла заголовка, совершенно иначе, чем компилятор C, поэтому при попытке связывания вы получите сообщение об ошибке компоновщика, говорящее об отсутствии символов.
Чтобы решить эту проблему, мы говорим компилятору C ++ работать в режиме «C», поэтому он выполняет преобразование имен так же, как и компилятор C. После этого исправлены ошибки компоновщика.
источник
C и C ++ имеют разные правила относительно имен символов. Символы - это то, как компоновщик знает, что вызов функции «openBankAccount» в одном объектном файле, созданный компилятором, является ссылкой на функцию, которую вы назвали «openBankAccount» в другом объектном файле, созданном из другого исходного файла тем же (или совместимым) компилятор. Это позволяет сделать программу из более чем одного исходного файла, что облегчает работу с большим проектом.
В Си правило очень простое, символы все равно находятся в одном пространстве имен. Таким образом, целое число «носки» сохраняется как «носки», а функция count_socks - как «счетчики».
Линкеры были созданы для C и других языков, таких как C, с этим простым правилом именования символов. Так что символы в компоновщике - это просто простые строки.
Но в C ++ язык позволяет вам иметь пространства имен, полиморфизм и другие вещи, которые противоречат такому простому правилу. Все шесть ваших полиморфных функций, называемых «add», должны иметь разные символы, иначе неправильная будет использоваться другими объектными файлами. Это делается путем "искажения" (это технический термин) имен символов.
При связывании кода C ++ с библиотеками C или кодом, вам нужно extern «C», что-либо написанное на C, например, файлы заголовков для библиотек C, чтобы сообщить компилятору C ++, что эти имена символов не должны быть искажены, в то время как остальные Ваш код C ++, конечно, должен быть искажен, иначе он не будет работать.
источник
Когда вы связываете библиотеки C в объектные файлы C ++
C и C ++ используют разные схемы именования символов. Это говорит компоновщику использовать схему C при компоновке в данной библиотеке.
Использование схемы именования C позволяет ссылаться на символы стиля C. В противном случае компоновщик будет использовать символы стиля C ++, которые не будут работать.
источник
Вы должны использовать 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 ++.
источник
Компилятор C ++ создает имена символов иначе, чем компилятор C. Итак, если вы пытаетесь вызвать функцию, которая находится в файле C, скомпилированном как код C, вам нужно сообщить компилятору C ++, что имена символов, которые он пытается разрешить, выглядят иначе, чем по умолчанию; в противном случае шаг ссылки потерпит неудачу.
источник
extern "C" {}
Конструкция инструктирует компилятор не выполнять коверкая по именам , объявленных в фигурных скобках. Обычно компилятор C ++ «улучшает» имена функций, чтобы они кодировали информацию о типе аргументов и возвращаемого значения; это называется искалеченным именем .extern "C"
Конструкция предотвращает коверкание.Обычно используется, когда код C ++ должен вызывать библиотеку языка C. Это также может быть использовано при предоставлении функции C ++ (например, из DLL) клиентам C.
источник
Это используется для решения проблем искажения имен. extern C означает, что функции находятся в «плоском» C-стиле API.
источник
Декомпилируйте
g++
сгенерированный двоичный файл, чтобы увидеть, что происходитЧтобы понять почему
extern
это необходимо, лучше всего разобраться, что происходит подробно в объектных файлах, с примером:main.cpp
Скомпилируйте с выходом GCC 4.8 Linux ELF :
Декомпилируйте таблицу символов:
Вывод содержит:
интерпретация
Мы видим, что:
ef
иeg
хранились в символах с тем же именем, что и в кодедругие символы были искажены. Давайте разберем их:
Вывод: оба следующих типа символов не были искажены:
Ndx = UND
), должен быть предоставлен в ссылке или во время выполнения из другого объектного файлаТак что вам понадобится
extern "C"
оба при звонке:g++
чтобы ожидать непогашенные символы, произведенныеgcc
g++
чтобы генерировать не исправленные символы дляgcc
использованияВещи, которые не работают в extern C
Становится очевидным, что любая функция C ++, требующая искажения имени, не будет работать внутри
extern C
:Пример минимального запуска C из C ++
Для полноты картины и для новичков см. Также: Как использовать исходные файлы C в проекте C ++?
Вызов C из C ++ довольно прост: каждая функция C имеет только один возможный необработанный символ, поэтому никакой дополнительной работы не требуется.
main.cpp
ч
куб.см
Бегать:
Без
extern "C"
ссылки не получается с:потому что
g++
ожидает найти искалеченныхf
, которыеgcc
не производят.Пример на GitHub .
Минимальный работоспособный C ++ из примера C
Вызов C ++ из немного сложнее: нам нужно вручную создавать не искаженные версии каждой функции, которую мы хотим представить.
Здесь мы проиллюстрируем, как показать перегрузки функций C ++ для C.
main.c
cpp.h
cpp.cpp
Бегать:
Без
extern "C"
этого не получается с:потому что
g++
генерирует искаженные символы, которыеgcc
не могут найти.Пример на GitHub .
Проверено в Ubuntu 18.04.
источник