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

<p>почему мы должны использовать:</p><pre><code>extern "c" { #include <foo.h> } </code></pre><p><strong>в частности:</strong></p><ul> <li> <p>когда мы должны использовать это?</p> </li> <li> <p>что происходит на уровне компилятора/компоновщика, что требует от нас его использования?</p> </li> <li> <p>как с точки зрения компиляции/компоновки это решает проблемы, которые требуют от нас его использования?</p> </li> </ul>

<p>c и c++ внешне похожи, но каждый компилируется в совершенно другой набор кода. когда вы включаете заголовочный файл с компилятором c++, компилятор ожидает код c++. однако, если это заголовок c, то компилятор ожидает, что данные, содержащиеся в заголовочном файле, будут скомпилированы в определенный формат - c++ "abi" или "application binary interface", поэтому компоновщик захлебывается. это предпочтительнее, чем передавать данные c++ функции, ожидающей данные c.</p><p>(чтобы разобраться в самом деле, abi в c++ обычно "искажает" имена своих функций/методов, поэтому, вызывая <code>printf()</code>, не помечая прототип как функцию c, c++ фактически генерирует код, вызывающий <code>\_zprintf</code>, плюс дополнительная хрень в конец.)</p><p>итак: используйте <code>extern "c" {...}</code> при включении заголовка c - это так просто. в противном случае у вас будет несоответствие в скомпилированном коде, и компоновщик захлебнется. однако для большинства заголовков вам даже не понадобится <code>extern</code>, поскольку большинство системных заголовков c уже учитывают тот факт, что они могут быть включены в код c++, а уже <code>extern</code> - их код.</p>

<p>extern "c" определяет, как символы в сгенерированном объектном файле должны быть названы. если функция объявлена без внешнего "c", имя символа в объектном файле будет использовать искажение имени c++. вот пример.</p><p>данный тест.с вроде так:</p><pre><code>void foo() { } </code></pre><p>компиляция и перечисление символов в объектном файле дает:</p><pre><code>$ g++ -c test.c $ nm test.o 0000000000000000 t \_z3foov u \_\_gxx\_personality\_v0 </code></pre><p>функция foo фактически называется "\_z3foov". эта строка содержит информацию о типе для возвращаемого типа и параметров, среди прочего. если вы вместо этого напишите test.c, как это:</p><pre><code>extern "c" { void foo() { } } </code></pre><p>затем скомпилируйте и посмотрите на символы:</p><pre><code>$ g++ -c test.c $ nm test.o u \_\_gxx\_personality\_v0 0000000000000000 t foo </code></pre><p>вы получаете связь c. имя функции "foo" в объектном файле просто "foo", и в ней нет всей информации о причудливых типах, получаемой из искажения имени.</p><p>обычно вы включаете заголовок в extern "c" {}, если код, который идет с ним, был скомпилирован с помощью компилятора c, но вы пытаетесь вызвать его из c++. когда вы делаете это, вы говорите компилятору, что все объявления в заголовке будут использовать связь си. когда вы связываете свой код, ваши файлы .o будут содержать ссылки на "foo", а не "\_z3fooblah", что, как мы надеемся, соответствует тому, что находится в библиотеке, с которой вы ссылаетесь.</p><p>большинство современных библиотек устанавливают защиту вокруг таких заголовков, чтобы символы объявлялись с правильной связью. например во многих стандартных заголовках вы найдете:</p><pre><code>#ifdef \_\_cplusplus extern "c" { #endif ... declarations ... #ifdef \_\_cplusplus } #endif </code></pre><p>это гарантирует, что когда код c++ включает заголовок, символы в вашем объектном файле совпадают с тем, что находится в библиотеке c. вы должны только поместить extern "c" {} вокруг заголовка c, если он старый и у него нет этих охранников.</p>

<p>в c++ у вас могут быть разные сущности, которые имеют общее имя. например, вот список функций с именами <em>foo</em>:</p><ul> <li><code>a::foo()</code></li> <li><code>b::foo()</code></li> <li><code>c::foo(int)</code></li> <li><code>c::foo(std::string)</code></li> </ul><p>чтобы различать их все, компилятор c++ создаст уникальные имена для каждого в процессе, называемом распределением имен или декорированием. компиляторы c не делают этого. кроме того, каждый компилятор c++ может делать это по-своему.</p><p>extern "c" говорит компилятору c++ не выполнять никакого искажения имени в коде в фигурных скобках. это позволяет вам вызывать функции c из c++.</p>

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

<blockquote> <p>когда мы должны использовать это?</p> </blockquote><p>когда вы связываете библиотеки c в объектные файлы c++</p><blockquote> <p>что происходит на уровне компилятора/компоновщика, что требует от нас его использования?</p> </blockquote><p>c и c++ используют разные схемы именования символов. это говорит компоновщику использовать схему c при компоновке в данной библиотеке.</p><blockquote> <p>как с точки зрения компиляции/компоновки это решает проблемы, которые требуют от нас его использования?</p> </blockquote><p>использование схемы именования c позволяет ссылаться на символы стиля c. в противном случае компоновщик будет использовать символы стиля c++, которые не будут работать.</p>

<p>c и c++ имеют разные правила относительно имен символов. символы - это то, как компоновщик знает, что вызов функции "openbankaccount" в одном объектном файле, созданный компилятором, является ссылкой на функцию, которую вы назвали "openbankaccount" в другом объектном файле, созданном из другого исходного файла тем же (или совместимым) компилятор. это позволяет сделать программу из более чем одного исходного файла, что облегчает работу с большим проектом.</p><p>в си правило очень простое, символы все равно находятся в одном пространстве имен. таким образом, целое число "socks" хранится как "socks", а функция count\_socks - как "count\_socks".</p><p>линкеры были созданы для c и других языков, таких как c, с этим простым правилом именования символов. таким образом, символы в компоновщике являются просто строками.</p><p>но в c++ язык позволяет вам иметь пространства имен, полиморфизм и другие вещи, которые противоречат такому простому правилу. все шесть ваших полиморфных функций, называемых "add", должны иметь разные символы, иначе неправильная будет использоваться другими объектными файлами. это делается путем "искажения" (это технический термин) имен символов.</p><p>при связывании кода c++ с библиотеками c или кодом, вам нужно extern "c" что-нибудь написанное на c, например заголовочные файлы для библиотек c, чтобы сообщить компилятору c++, что эти имена символов не должны быть искажены, в то время как остальные ваш код c++, конечно, должен быть искажен, иначе он не будет работать.</p>

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

<p>вы должны использовать extern "c" в любое время, когда вы включаете функции определения заголовка, находящиеся в файле, скомпилированном компилятором c, который используется в файле c++. (многие стандартные библиотеки c могут включать эту проверку в свои заголовки, чтобы сделать ее проще для разработчика)</p><p>например, если у вас есть проект с 3 файлами, util.c, util.h и main.cpp, и оба файла .c и .cpp скомпилированы с помощью компилятора c++ (g ++, cc и т.д.), то это не так. это действительно необходимо, и может даже вызвать ошибки компоновщика. если ваш процесс сборки использует обычный компилятор c для util.c, то вам нужно будет использовать extern "c" при включении util.h.</p><p>происходит то, что c++ кодирует параметры функции в своем названии. так работает перегрузка функций. все, что обычно происходит с функцией c, это добавление подчеркивания ("\_") в начало имени. без использования extern "c" компоновщик будет искать функцию с именем dosomething @@ int @ float (), когда фактическое имя функции - \_dosomething () или просто dosomething ().</p><p>использование extern "c" решает вышеуказанную проблему, сообщая компилятору c++, что он должен искать функцию, которая следует соглашению об именовании c вместо c++.</p>

<p>конструкция <code>extern "c" {}</code> указывает компилятору не выполнять искажение имен, объявленных в фигурных скобках. обычно компилятор c++ "улучшает" имена функций, чтобы они кодировали информацию о типе аргументов и возвращаемого значения; это называется <em>искаженное имя</em>. конструкция <code>extern "c"</code> предотвращает искажение.</p><p>обычно используется, когда код c++ должен вызывать библиотеку языка c. это также может быть использовано при предоставлении функции c++ (например, из dll) клиентам c.</p>

<p>это используется для решения проблем искажения имен. extern c означает, что функции находятся в "плоском" c-стиле api.</p>

<p><strong> декомпилируйте сгенерированный двоичный код <code>g++</code>, чтобы увидеть, что происходит </strong></p><p>я перехожу к этому ответу из: <a href="https://stackoverflow.com/questions/1041866/what-is-the-effect-of-extern-c-in-c/30526795#30526795">каково влияние extern "c" в c++?</a> , поскольку этот вопрос считался дубликатом этого.</p><p>main.cpp</p><pre><code>void f() {} void g(); extern "c" { void ef() {} void eg(); } /* prevent g and eg from being optimized away. */ void h() { g(); eg(); } </code></pre><p>скомпилировать с gcc 4.8 linux <a href="https://stackoverflow.com/questions/26294034/how-to-make-an-executable-elf-file-in-linux-using-a-hex-editor/30648229#30648229">elf</a> вывод:</p><pre><code>g++ -c main.cpp </code></pre><p>декомпилируйте таблицу символов:</p><pre><code>readelf -s main.o </code></pre><p>вывод содержит:</p><pre><code>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 </code></pre><p><strong> интерпретация </strong></p><p>мы видим, что:</p><ul> <li> <p><code>ef</code> и <code>eg</code> хранились в символах с тем же именем, что и в коде</p> </li> <li> <p>другие символы были искажены. давайте разобьем их:</p> <pre><code>$ c++filt _z1fv f() $ c++filt \_z1hv h() $ c++filt \_z1gv g() </code></pre></li> </ul><p>вывод: оба следующих типа символов были <em> не </em> искалечены:</p><ul> <li>определенный</li> <li>объявлено, но не определено (<code>ndx = und</code>), будет предоставлено по ссылке или во время выполнения из другого объектного файла</li> </ul><p>таким образом, вам потребуется <code>extern "c"</code> при вызове:</p><ul> <li>c из c++: скажите <code>g++</code> ожидать, что не исправленные символы будут созданы <code>gcc</code></li> <li>c++ из c: скажите <code>g++</code> генерировать не исправленные символы для использования <code>gcc</code></li> </ul><p><strong> вещи, которые не работают во внешнем c </strong></p><p>становится очевидным, что любая функция c++, требующая искажения имени, не будет работать внутри <code>extern c</code>:</p><pre><code>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) { } } </code></pre><p><strong> пример минимального запуска c из c++ </strong></p><p>для полноты картины и для новичков см. также: <a href="https://stackoverflow.com/questions/13694605/how-to-use-c-source-files-in-a-c-project/51912672#51912672">как использовать исходные файлы c в проекте c++?</a> </p><p>вызов c из c++ довольно прост: каждая функция c имеет только один возможный не искаженный символ, поэтому никакой дополнительной работы не требуется.</p><p>main.cpp</p><pre><code>#include <cassert> #include "c.h" int main() { assert(f() == 1); } </code></pre><p>c.h</p><pre><code>#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 </code></pre><p>к.ц</p><pre><code>#include "c.h" int f(void) { return 1; } </code></pre><p>бежать:</p><pre><code>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 </code></pre><p>без <code>extern "c"</code> ссылка завершается с:</p><pre><code>main.cpp:6: undefined reference to `f()' </code></pre><p>потому что <code>g++</code> ожидает найти искаженное <code>f</code>, которое <code>gcc</code> не произвело.</p><p> <a href="https://github.com/cirosantilli/cpp-cheat/tree/bf5f48628d0b01ba6a3fcea6f1162b28539654c9/c-from-cpp">пример на github</a> .</p><p><strong> пример минимального запуска c++ из примера c </strong></p><p>вызов c++ из немного сложнее: нам нужно вручную создавать не искаженные версии каждой функции, которую мы хотим представить.</p><p>здесь мы проиллюстрируем, как показать перегрузки функций c++ для c.</p><p>main.c</p><pre><code>#include <assert.h> #include "cpp.h" int main(void) { assert(f\_int(1) == 2); assert(f\_float(1.0) == 3); return 0; } </code></pre><p>cpp.h</p><pre><code>#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 </code></pre><p>cpp.cpp</p><pre><code>#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); } </code></pre><p>бежать:</p><pre><code>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 </code></pre><p>без <code>extern "c"</code> происходит сбой с:</p><pre><code>main.c:6: undefined reference to `f\_int' main.c:7: undefined reference to `f\_float' </code></pre><p>потому что <code>g++</code> генерирует искаженные символы, которые <code>gcc</code> не может найти.</p><p> <a href="https://github.com/cirosantilli/cpp-cheat/tree/bf5f48628d0b01ba6a3fcea6f1162b28539654c9/cpp-from-c">пример на github</a> .</p><p>проверено в ubuntu 18.04.</p>