Каков эффект внешнего “C” в C ++?
-
22-07-2019 - |
Вопрос
Что именно означает помещение extern "C"
в C ++-коде делать?
Например:
extern "C" {
void foo();
}
Решение
внешний параметр "C" привязывает имя функции в C ++ к 'C' (компилятор не искажает имя), так что клиентский код на C может ссылаться на вашу функцию (т. е. использовать) с помощью 'C' совместимого заголовочного файла, который содержит только объявление вашей функции.Определение вашей функции содержится в двоичном формате (который был скомпилирован вашим компилятором C ++), на который затем будет ссылаться компоновщик "C" клиента, используя имя "C".
Поскольку C ++ перегружает имена функций, а C - нет, компилятор C ++ не может просто использовать имя функции в качестве уникального идентификатора для ссылки, поэтому он искажает имя, добавляя информацию об аргументах.Компилятору C не нужно искажать имя, поскольку вы не можете перегружать имена функций в C.Когда вы указываете, что функция имеет внешнюю связь "C" в C ++, компилятор C ++ не добавляет информацию о типе аргумента / параметра к имени, используемому для связи.
Чтобы вы знали, вы можете явно указать привязку "C" к каждому отдельному объявлению / определению или использовать блок для группировки последовательности объявлений / определений, чтобы иметь определенную привязку:
extern "C" void foo(int);
extern "C"
{
void g(char);
int i;
}
Если вас интересуют технические детали, они перечислены в разделе 7.5 стандарта C ++ 03, вот краткое изложение (с ударением на внешнем "C"):
- внешний "C" - это спецификация связи
- Каждый компилятор является требуемый для обеспечения связи "С"
- спецификация связи должна встречаться только в области пространства имен
все типы функций, имена функций и переменных связаны с языкомСмотрите Комментарий Ричарда: Только имена функций и переменных с внешней привязкой имеют языковую привязку- два типа функций с различными языковыми связями являются различными типами, даже если в остальном идентичны
- гнездо спецификаций сцепления, внутреннее определяет конечное сцепление
- внешний символ "C" игнорируется для членов класса
- не более чем одна функция с определенным именем может иметь связь "C" (независимо от пространства имен).
extern "C" вынуждает функцию иметь внешнюю привязку (не может сделать ее статичной)Смотрите комментарий Ричарда: 'static' внутри 'extern "C"' допустим;объявленный таким образом объект имеет внутреннюю связь и, следовательно, не имеет языковой связи- Привязка из C ++ к объектам, определенным на других языках, и к объектам, определенным в C ++ из других языков, определяется реализацией и зависит от языка.Такая связь может быть достигнута только в том случае, если стратегии компоновки объектов двух языковых реализаций достаточно схожи
Другие советы
Просто хотел добавить немного информации, так как я еще не видел ее опубликованной.
Вы очень часто будете видеть код в заголовках C примерно так:
#ifdef __cplusplus
extern "C" {
#endif
// all of your legacy C code here
#ifdef __cplusplus
}
#endif
Это позволяет вам использовать этот заголовочный файл C вместе с кодом C ++, поскольку макрос " __cplusplus " будет определен Но вы можете также по-прежнему использовать его с унаследованным кодом C, где макрос определен НЕ , поэтому он не увидит уникальную конструкцию C ++.
Хотя я также видел код C ++, такой как:
extern "C" {
#include "legacy_C_header.h"
}
который я представляю, выполняет то же самое.
Не уверен, какой путь лучше, но я видел оба.
В каждой программе на C ++ все нестатические функции представлены в двоичном файле в виде символов. Эти символы являются специальными текстовыми строками, которые однозначно идентифицируют функцию в программе.
В C имя символа совпадает с именем функции. Это возможно, потому что в C нет двух нестатических функций, которые могут иметь одинаковое имя.
Поскольку C ++ допускает перегрузку и имеет много функций, которые C не поддерживает & # 8212; как классы, функции-члены, спецификации исключений - невозможно просто использовать имя функции в качестве имени символа. Чтобы решить эту проблему, C ++ использует так называемое искажение имени, которое преобразует имя функции и всю необходимую информацию (например, число и размер аргументов) в некоторую странную строку, обрабатываемую только компилятором и компоновщиком.
Таким образом, если вы укажете функцию, которая будет extern C, компилятор не выполняет искажение имени вместе с ней, и это может быть напрямую доступ осуществляется с использованием имени символа в качестве имени функции.
Это удобно при использовании dlsym ()
и dlopen ()
для вызова таких функций.
Декомпилировать a g++
сгенерированный двоичный файл, чтобы увидеть, что происходит
main.cpp
void f() {}
void g();
extern "C" {
void ef() {}
void eg();
}
/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }
Скомпилируйте с помощью GCC 4.8 Linux ЭЛЬФ выходной сигнал:
g++ -c main.cpp
Декомпилируйте таблицу символов:
readelf -s main.o
Выходные данные содержат:
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
Интерпретация
Мы видим, что:
ef
иeg
были сохранены в символах с тем же названием, что и в кодеостальные символы были искорежены.Давайте их распутаем:
$ c++filt _Z1fv f() $ c++filt _Z1hv h() $ c++filt _Z1gv g()
Заключение:оба следующих типа символов были нет искалеченный:
- определенный
- объявлено, но не определено (
Ndx = UND
), который будет предоставлен по ссылке или во время выполнения из другого объектного файла
Итак, вам понадобится extern "C"
оба при вызове:
- C из C ++:рассказать
g++
ожидать, что неперепутанные символы, созданныеgcc
- C++ из C:рассказать
g++
для создания неупорядоченных символов дляgcc
для использования
Вещи, которые не работают во внешнем C
Становится очевидным, что любая функция C ++, требующая изменения имени, не будет работать внутри extern C
:
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) { }
}
Минимальный исполняемый C из примера C ++
Для полноты картины и для новичков смотрите также: Как использовать исходные файлы C в проекте на C ++?
Вызвать C из C ++ довольно просто:каждая функция C имеет только один возможный неискаженный символ, поэтому никакой дополнительной работы не требуется.
main.cpp
#include <cassert>
#include "c.h"
int main() {
assert(f() == 1);
}
с.ч
#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
c.c
#include "c.h"
int f(void) { return 1; }
Бежать:
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
Без extern "C"
связь завершается неудачно с:
main.cpp:6: undefined reference to `f()'
потому что g++
ожидает найти искалеченный f
, который gcc
не производил.
Минимальный исполняемый C ++ из примера C
Вызвать C ++ из C немного сложнее:мы должны вручную создать неискаженные версии каждой функции, которую мы хотим предоставить.
Здесь мы проиллюстрируем, как подвергнуть перегрузке функции C ++ на C.
main.с
#include <assert.h>
#include "cpp.h"
int main(void) {
assert(f_int(1) == 2);
assert(f_float(1.0) == 3);
return 0;
}
cpp.h
#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
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);
}
Бежать:
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
Без extern "C"
это терпит неудачу с:
main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'
потому что g++
сгенерированные искаженные символы, которые gcc
не могу найти.
Протестировано в Ubuntu 18.04.
C ++ изменяет имена функций для создания объектно-ориентированного языка из процедурного языка
Большинство языков программирования не построены поверх существующих языков программирования. C ++ построен поверх C, и, кроме того, это объектно-ориентированный язык программирования, построенный из процедурного языка программирования, и по этой причине существуют выражения C ++, такие как extern " C "
, которые обеспечивают обратную совместимость с C.
Давайте посмотрим на следующий пример:
#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;
}
Компилятор AC не скомпилирует приведенный выше пример, потому что одна и та же функция printMe
определяется дважды (даже если они имеют разные параметры int a
vs char a код>).
gcc -o printMe printMe.c & amp; & amp; ./printMe;
1 ошибка. PrintMe определяется более одного раза.
Компилятор C ++ скомпилирует приведенный выше пример. Неважно, что printMe
определяется дважды.
g ++ -o printMe printMe.c & amp; & amp; ./printMe; р>
Это связано с тем, что компилятор C ++ неявно переименовывает ( mangles ) функции в зависимости от их параметров. В C эта функция не поддерживалась. Однако, когда C ++ был построен поверх C, язык был разработан для объектно-ориентированного подхода и должен был поддерживать возможность создавать разные классы с методами (функциями) с одинаковыми именами и переопределять методы ( переопределение метода ) на основе различных параметров.
extern " C "
говорит "не искажать имена функций C"
Однако представьте, что у нас есть устаревший C-файл с именем " parent.c " что включает
имена функций из других устаревших файлов C, " parent.h " ;, " child.h " и т. д. Если устаревший " parent.c " файл запускается через компилятор C ++, затем имена функций будут искажены, и они больше не будут совпадать с именами функций, указанными в " parent.h " ;, " child.h " ;, и т. д., поэтому имена функций в этих внешних файлы также должны быть искажены. Переназначение имен функций в сложной программе на C, имеющих много зависимостей, может привести к поломке кода; поэтому может быть удобно предоставить ключевое слово, которое может сказать компилятору C ++ не искажать имя функции.
Ключевое слово extern "C"
указывает компилятору C ++ не искажать (переименовывать) имена функций C. Пример использования: extern " C " void printMe (int a);
Он изменяет связывание функции таким образом, что функция вызывается из C. На практике это означает, что имя функции не является искалеченный .
Ни один C-заголовок не может быть сделан совместимым с C ++ путем простого переноса в extern "C". Когда идентификаторы в заголовке C конфликтуют с ключевыми словами C ++, компилятор C ++ будет жаловаться на это.
Например, я видел следующий сбой кода в g ++:
extern "C" {
struct method {
int virtual;
};
}
Kinda имеет смысл, но есть кое-что, о чем следует помнить при портировании C-кода на C ++. Р>
Он информирует компилятор C ++ о необходимости искать имена этих функций в стиле C при компоновке, поскольку имена функций, скомпилированных в C и C ++, различаются на этапе компоновки.
extern "C" предназначен для распознавания компилятором C ++ и уведомления компилятора о том, что указанная функция (или должна быть) скомпилирована в стиле C. Так что при ссылке, это ссылка на правильную версию функции из C.
Раньше я использовал 'extern "C"' для создания файлов dll (библиотеки динамических ссылок) и т.д.функция main() "экспортируема", поэтому ее можно использовать позже в другом исполняемом файле из библиотеки dll.Возможно, пример того, где я раньше его использовал, может быть полезен.
Библиотека DLL
#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);
}
EXE
#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);
}
extern "C"
это спецификация связи, которая используется для вызов функций C в Исходные файлы Cpp.Мы можем вызывайте функции C, записывайте переменные и включайте заголовки.Функция объявлена во внешней сущности и определена снаружи.Синтаксис таков
Тип 1:
extern "language" function-prototype
Тип 2:
extern "language"
{
function-prototype
};
например:
#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)
{
//
//
}
Этот ответ предназначен для нетерпеливых / которым нужно уложиться в крайние сроки, ниже приведена только часть / простое объяснение:
- в C ++ вы можете иметь одинаковое имя в классе с помощью перегрузки (например, поскольку все они имеют одинаковые имена, их нельзя экспортировать как есть из dll и т.д.) Решение этих проблем заключается в том, что они преобразуются в разные строки (называемые символами), символы учитывают имя функции, а также аргументы, поэтому каждая из этих функций, даже с одинаковым именем, может быть однозначно идентифицирована (также называемая искажением имени)
- в C у вас нет перегрузки, имя функции уникально (таким образом, отдельная строка для уникальной идентификации имени функции не требуется, поэтому символом является само имя функции)
Итак
в C ++ с искажением имени каждая функция однозначно идентифицируется
в C, даже без искажения имени, каждая функция однозначно идентифицирует
Чтобы изменить поведение C ++, то есть указать искажение этого имени не следует если это произойдет для определенной функции, вы можете использовать внешний "C" перед именем функции, по какой-либо причине, например, при экспорте функции с определенным именем из библиотеки dll для использования ее клиентами.
Прочтите другие ответы, для получения более подробных / более правильных ответов.
При смешивании C и C ++ (т. е. вызов функции C из C ++ и вызов функции C ++ из C) искажение имени C ++ вызывает проблемы с линковкой. Технически говоря, эта проблема возникает, только когда функции вызываемого абонента уже скомпилированы в двоичный файл (скорее всего, файл * .a) с использованием соответствующего компилятора.
Итак, нам нужно использовать extern " C " отключить искажение имени в C ++.