каково влияние extern "c" в c ++?

<p>что именно делает помещение <code>extern "c"</code> в код c++?</p><p>например:</p><pre><code>extern "c" { void foo(); } </code></pre>

<p>extern "c" делает имя функции в c++ связанным с c (компилятор не искажает имя), так что клиентский код c может ссылаться (т.е. использовать) вашу функцию, используя совместимый с c заголовочный файл, который содержит только объявление вашей функции. определение вашей функции содержится в двоичном формате (который был скомпилирован вашим компилятором c++), на который клиентский компоновщик 'c' затем будет ссылаться, используя имя 'c'.</p><p>поскольку c++ перегружает имена функций, а c - нет, компилятор c++ не может просто использовать имя функции в качестве уникального идентификатора для ссылки, поэтому он искажает имя, добавляя информацию об аргументах. компилятору ac не нужно искажать имя, так как вы не можете перегрузить имена функций в c. когда вы заявляете, что функция имеет внешнюю связь "c" в c++, компилятор c++ не добавляет информацию типа аргумента/параметра к имени, используемому для связь.</p><p>точно так же, как вы знаете, вы можете явно указать связь "c" для каждого отдельного объявления/определения или использовать блок для группировки последовательности объявлений/определений, чтобы иметь определенную связь:</p><pre><code>extern "c" void foo(int); extern "c" { void g(char); int i; } </code></pre><p>если вы заботитесь о технических деталях, они перечислены в разделе 7.5 стандарта c++ 03, вот краткое резюме (с акцентом на extern "c"):</p><ul> <li>extern "c" является спецификацией связи</li> <li>каждый компилятор <em> требуется </em> для обеспечения связи "c"</li> <li>спецификация связи должна встречаться только в области имен</li> <li><strike>все типы функций, имена функций и имена переменных имеют языковую связь</strike> <b> <a href="https://stackoverflow.com/questions/1041866/in-c-source-what-is-the-effect-of-extern-c#comment20842899\_1041880">см. комментарий ричарда:</a> </b> только имена функций и имена переменных с внешней связью имеют языковую связь</li> <li>два типа функций с разными языковыми связями являются разными типами, даже если в противном случае они идентичны</li> <li>гнездо спецификаций связи, внутреннее определяет окончательную связь</li> <li>extern "c" игнорируется для членов класса</li> <li>не более одной функции с конкретным именем может иметь связь "c" (независимо от пространства имен)</li> <li><strike>extern "c" заставляет функцию иметь внешнюю связь (не может сделать ее статической)</strike> <b> <a href="https://stackoverflow.com/questions/1041866/what-is-the-effect-of-extern-c-in-c?rq=1#comment20842893\_1041880">см. комментарий ричарда:</a> </b> 'static' inside 'extern "c"' допустим; заявленная сущность имеет внутреннюю связь и поэтому не имеет языковой связи</li> <li>связь с c++ с объектами, определенными в других языках, и с объектами, определенными в c++ из других языков, определяется реализацией и зависит от языка. только в тех случаях, когда стратегии размещения объектов двух языковых реализаций достаточно похожи, такая связь может быть достигнута</li> </ul>

<p>просто хотел добавить немного информации, так как я еще не видел ее опубликованной.</p><p>вы очень часто будете видеть код в заголовках c, например:</p><pre><code>#ifdef \_\_cplusplus extern "c" { #endif // all of your legacy c code here #ifdef \_\_cplusplus } #endif </code></pre><p>это позволяет вам использовать этот заголовочный файл c с вашим кодом c++, потому что будет определен макрос "\_\_cplusplus". но вы можете <em>также</em> по-прежнему использовать его с унаследованным кодом c, где макрос определен <em>не</em>, поэтому он не увидит уникальную конструкцию c++.</p><p>хотя я также видел код c++, такой как:</p><pre><code>extern "c" { #include "legacy\_c\_header.h" } </code></pre><p>который я представляю, выполняет то же самое.</p><p>не уверен, какой путь лучше, но я видел оба.</p>

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

<p><strong> декомпилируйте сгенерированный двоичный код <code>g++</code>, чтобы увидеть, что происходит </strong></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>

<h2>c++ искажает имена функций для создания объектно-ориентированного языка из процедурного языка</h2><p>большинство языков программирования не построены поверх существующих языков программирования. c++ построен поверх c, и, кроме того, это объектно-ориентированный язык программирования, построенный из процедурного языка программирования, и по этой причине существуют выражения c++, такие как <code>extern "c"</code>, которые обеспечивают обратную совместимость с c.</p><p>давайте посмотрим на следующий пример:</p><pre><code>#include <stdio.h> // two functions are defined with the same name // but have different parameters void printme(int a) { printf("int: %i\n", a); } void printme(char a) { printf("char: %c\n", a); } int main() { printme("a"); printme(1); return 0; } </code></pre><p>компилятор c не будет компилировать приведенный выше пример, поскольку одна и та же функция <code>printme</code> определяется дважды (даже если они имеют разные параметры <code>int a</code> vs <code>char a</code>).</p><blockquote> <p>gcc -o printme printme.c && ./printme;<br> <em> 1 ошибка. printme определяется более одного раза. </em></p> </blockquote><p>компилятор c++ скомпилирует приведенный выше пример. неважно, что <code>printme</code> определяется дважды.</p><blockquote> <p>g ++ -o printme printme.c && ./printme;</p> </blockquote><p>это связано с тем, что компилятор c++ неявно переименовывает ( <a href="https://en.wikipedia.org/wiki/name\_mangling">mangles</a> ) функции в зависимости от их параметров. в c эта функция не поддерживалась. однако, когда c++ был построен поверх c, язык был разработан, чтобы быть объектно-ориентированным, и должен был поддерживать возможность создавать различные классы с методами (функциями) с одинаковыми именами и переопределять методы ( <a href="https://en.wikipedia.org/wiki/method\_overriding">переопределение методов)</a> ) на основе разных параметров.</p><h2><code>extern "c"</code> говорит "не искажать имена функций c"</h2><p>однако представьте, что у нас есть устаревший c-файл с именем "parent.c", который <code>include</code>s называет имена функций из других унаследованных c-файлов, "parent.h", "child.h" и т.д. если старый файл "parent.c" запущен через компилятор c++ имена функций будут искажены, и они больше не будут совпадать с именами функций, указанными в "parent.h", "child.h" и т. д., поэтому имена функций в этих внешних файлах также должны будут быть искалеченным. переназначение имен функций в сложной программе на c, имеющих много зависимостей, может привести к поломке кода; поэтому может быть удобно предоставить ключевое слово, которое может сказать компилятору c++ не искажать имя функции.</p><p>ключевое слово <code>extern "c"</code> говорит компилятору c++ не искажать (переименовывать) имена функций c. пример использования: <code>extern "c" void printme(int a);</code></p>

<p>он изменяет связь функции таким образом, что функция вызывается из c. на практике это означает, что имя функции не <a href="http://en.wikipedia.org/wiki/name\_mangling">искалечено</a> .</p>

<p>ни один c-заголовок не может быть сделан совместимым с c++, просто заключая во внешнюю "c". когда идентификаторы в заголовке c конфликтуют с ключевыми словами c++, компилятор c++ будет жаловаться на это.</p><p>например, я видел следующий сбой кода в g ++:<br></p><pre><code>extern "c" { struct method { int virtual; }; } </code></pre><p>kinda имеет смысл, но есть что-то, что следует иметь в виду при переносе c-кода на c++.</p>

<p>он информирует компилятор c++ для поиска имен этих функций в стиле c при компоновке, потому что имена функций, скомпилированных в c и c++, различаются на этапе компоновки.</p>

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

<p>ранее я использовал extern "c" для файлов dll (динамически подключаемых библиотек), чтобы сделать функцию main () и т.д. "экспортируемой", чтобы позже ее можно было использовать в другом исполняемом файле из dll. может быть, пример того, где я использовал его, может быть полезным.</p><p>dll</p><pre><code>#include <string.h> #include <windows.h> using namespace std; #define dll extern "c" \_\_declspec(dllexport) //i defined dll for dllexport function dll main () { messagebox(null,"hi from dll","dll",mb\_ok); } </code></pre><p>exe</p><pre><code>#include <string.h> #include <windows.h> using namespace std; typedef lpvoid (winapi*function)();//make a placeholder for function from dll function maindllfunc;//make a variable for function placeholder int main() { char windir[max\_path];//will hold path of above dll getcurrentdirectory(sizeof(windir),windir);//dll is in same dir as exe strcat(windir,"\\exmple.dll");//concentrate dll name with path hinstance dll = loadlibrary(windir);//load example dll if(dll==null) { freelibrary((hmodule)dll);//if load fails exit return 0; } maindllfunc=(function)getprocaddress((hmodule)dll, "main"); //defined variable is used to assign a function from dll //getprocaddress is used to locate function with pre defined extern name "dll" //and matcing function name if(maindllfunc==null) { freelibrary((hmodule)dll);//if it fails exit return 0; } maindllfunc();//run exported function freelibrary((hmodule)dll); } </code></pre>

<p><code>extern "c"</code> - это спецификация связи, которая используется для <strong>вызова функций c</strong> в <strong>исходные файлы cpp</strong>. мы можем <strong>вызывать функции c, писать переменные и включать заголовки</strong>. функция объявлена во внешнем объекте и определена снаружи. синтаксис</p><p><strong>тип 1:</strong></p><pre><code>extern "language" function-prototype </code></pre><p><strong>тип 2:</strong></p><pre><code>extern "language" { function-prototype }; </code></pre><p><strong>например:</strong></p><pre><code>#include<iostream> using namespace std; extern "c" { #include<stdio.h> // include c header int n; // declare a variable void func(int,int); // declare a function (function prototype) } int main() { func(int a, int b); // calling function . . . return 0; } // function definition . . . void func(int m, int n) { // // } </code></pre>

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

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