Что означает для имени или типа наличие определенной языковой связи? - PullRequest
40 голосов
/ 23 апреля 2011

Согласно (c) ANSI ISO / IEC 14882: 2003, стр. 127:

Гнездо спецификаций сцепления.Когда гнездятся спецификации связей, самый внутренний определяет язык.Спецификация связи не устанавливает область действия.Спецификация связи должна встречаться только в области пространства имен (3.3).В спецификации связи указанная языковая связь применяется к типам функций всех деклараторов функций, имен функций и имен переменных, представленных объявлением (ями).

extern "C" void f1(void(*pf)(int));
// the name f1 and its function type have C language
// linkage; pf is a pointer to a C function

extern "C" typedef void FUNC();
FUNC f2;
// the name f2 has C++ language linkage and the
// function's type has C language linkage

extern "C" FUNC f3;
// the name of function f3 and the function's type
// have C language linkage

void (*pf2)(FUNC*);
// the name of the variable pf2 has C++ linkage and
// the type of pf2 is pointer to C++ function that
// takes one parameter of type pointer to C function

Что все это делаетимею в виду?Например, какую связь имеет функция f2(), связь языка C или C ++?

Как указывает @Johannes Schaub, нет реального объяснения того, что это означает в Стандарте, поэтому его можно интерпретироватьпо-разному в разных компиляторах.

Пожалуйста, объясните различия в объектном файле:

  • имя функции с связью языка C и языком C ++.
  • тип функциис привязкой к языку C и языку C ++.

Ответы [ 7 ]

16 голосов
/ 19 мая 2011

Языковая связь - это термин, используемый для связи между фрагментами кода C++ и non-C++.Как правило, в программе на C ++ все имена функций, типы функций и даже имена переменных имеют связь по умолчанию с языком C ++.

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

Поскольку вам необходимо знать концепцию name mangling, которая кодирует имена функций, типы функций и имена переменных, чтобы сгенерировать для них уникальное имя.Это позволяет компоновщику различать общие имена (как в случае перегрузки функций).Изменение имени нежелательно при связывании модулей C с библиотеками или объектными файлами, скомпилированными с помощью компилятора C ++.Для предотвращения искажения имени в таких случаях используются спецификаторы связи.В этом случае extern "C" является спецификатором связи.Давайте рассмотрим пример (код c ++ упоминает здесь ):

typedef int (*pfun)(int);  // line 1
extern "C" void foo(pfun); // line 2
extern "C" int g(int)      // line 3
...
foo( g ); // Error!        // line 5

В строке 1 объявляется pfun для указания на функцию C ++, поскольку в ней отсутствует спецификатор связи.

Строка 2 поэтому объявляет, что foo является функцией C, которая принимает указатель на функцию C ++.

Строка 5 пытается вызвать foo с указателем на g, функцией C, несовпадением типов.

Различия в связывании имени функции:

Давайте возьмем два разных файла:

Один с extern "c" связью (file1.cpp):

#include <iostream>
using namespace std;

extern "C"
{
void foo (int a, int b)
{
    cout << "here";
}
}

int main ()
{
    foo (10,20);
    return 0;
}

Один без extern "c" linkage (file2.cpp):

#include <iostream>
using namespace std;

void foo (int a, int b)
{
    cout << "here";
}

int main ()
{
    foo (10,20);
    return 0;
}

Теперь скомпилируйте эти два и проверьте objdump.

# g++ file1.cpp -o file1
# objdump -Dx file1

# g++ file2.cpp -o file2
# objdump -Dx file2

С внешней связью "C" нет именикалеча для функции foo.Таким образом, любая программа, которая ее использует (при условии, что мы делаем из нее разделяемую библиотеку), может напрямую вызывать foo (с помощью вспомогательных функций, таких как dlsym и dlopen) без учета каких-либо эффектов искажения имен.1042 * С другой стороны, когда не используется extern "C", func: foo искажается некоторыми предопределенными правилами (известными используемому компилятору / компоновщику), и поэтому приложение не может напрямую вызывать его из него, указав имякак foo.Вы можете, однако, назвать его искаженным именем (в данном случае _Z3fooii), но никто не использует его по очевидной причине.

0000000000400774 <_Z3fooii>:
  400774:   55                      push   %rbp
  400775:   48 89 e5                mov    %rsp,%rbp
 ...
...
  400791:   c9                      leaveq 
  400792:   c3                      retq   

0000000000400793 <main>:
  400793:   55                      push   %rbp
  400794:   48 89 e5                mov    %rsp,%rbp
  400797:   be 14 00 00 00          mov    $0x14,%esi
  40079c:   bf 0a 00 00 00          mov    $0xa,%edi
  4007a1:   e8 ce ff ff ff          callq  400774 <_Z3fooii>
  4007a6:   b8 00 00 00 00          mov    $0x0,%eax
  4007ab:   c9                      leaveq 
  4007ac:   c3                      retq   

Эта страница также являетсяХорошее чтение для этой конкретной темы.

Хорошая и четко объясненная статья о соглашении о вызовах: http://www.codeproject.com/KB/cpp/calling_conventions_demystified.aspx

2 голосов
/ 20 мая 2011

Что все это значит?Например, какую связь имеет функция f2 (), связь языка C или C ++?

extern "C" typedef void FUNC();
FUNC f2;
// the name f2 has C++ language linkage and the 
// function's type has C language linkage 

То, что вы называете функцией f2 (), имеет два аспекта этой связи:

  • искажение или отсутствие его имени в таблице символов (которая имеет связь с языком C ++) и
  • соглашение о вызовах C или C ++, необходимое для вызова функции (C).

Для вызова f2() вы найдете его имя или символ в объектном файле, который будет искаженной версией «функции с именем f2, не имеющей аргументов».Вы можете проверить это тривиально, скомпилировав вышеприведенный код и осмотрев объект (например, w / GNU tools nm --demangle).

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

Пожалуйста, объясните различия в объектном файле: имя функции сСвязь языка C и языка C ++.

  • для связи C, "f2" будет символом в объектном файле, полученным в результате f2()
  • для связи C ++,некоторая искаженная версия «функции с именем f2, не имеющей аргументов» (для GNU, _Z2f2v, которая преобразуется в f2())

тип функции со связью языка C и связью языка C ++.

Как обсуждалось выше, речь идет о соглашении об использовании регистра / стека для вызова кода по адресу функции.Эта метаинформация не обязательно хранится в информации таблицы символов объекта (и, конечно, не является частью самого ключа имени символа).

Далее, поскольку каждая функция принимает одно из соглашений о вызовах,компилятору нужно знать соглашение о вызовах, которое следует использовать при указании на функцию: с этой проницательностью я думаю, что оставшийся код в вопросе станет понятным.

Отличное обсуждение: http://developers.sun.com/solaris/articles/mixing.html -в частности, я рекомендую раздел Работа с указателями на функции .

2 голосов
/ 19 мая 2011

Это связано с ABI (Application Binary Interface) программы.

Поскольку API определяет внешний интерфейс исходного кода программы, ABI определяет внешний интерфейс двоичного кода программы (скомпилированная версия).


Первоначально функции C просто имели несколько различных форм. Что-то вроде

int foo(int);

будет префиксом подчеркивания компилятором, чтобы сформировать _foo, а затем экспортируется , чтобы быть доступным для других приложений.

Однако этого было недостаточно. Например, если вы посмотрите на Windows API, вы увидите такие вещи, как:

DWORD CreateWindowW(...);        //Original parameters
DWORD CreateWindowExW(..., ...); //More parameters

Это потому, что нет способа отличить перегрузки функции, просто посмотрев на имя функции, поэтому люди начали менять их, добавляя суффикс Ex (или тому подобное).

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

Все было хорошо, за исключением того, что не было полностью стандартизировано .

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

2 голосов
/ 19 мая 2011
extern "C" typedef void FUNC();
FUNC f2;
// the name f2 has C++ language linkage and the
// function's type has C language linkage

Имя FUNC объявлено со связью "C", поскольку в первой строке указано extern "C".

Имя f2 имеет связь C ++, поскольку это значение по умолчанию, иНикакая другая связь не указана во второй строке.

Тот факт, что имя f2 используется для ссылки на функцию с связью C, не меняет связь с name .

2 голосов
/ 18 мая 2011

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

"тип функции имеет связь с языком C" Я не знаю деталей, но знаю высокий уровень. По сути, это делает скомпилированную C ++ функцию скомпонованной из C. Если я правильно помню, в C и C ++ способ передачи параметров в функцию отличается. В этом случае функция f2 будет передавать параметры, как это делает компилятор C. таким образом функция будет связываться как из C, так и из C ++.

1 голос
/ 19 мая 2011

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

// file1.c
void foo(void) {}

И после компиляции file1.obj хранит код и информацию об определении символа foo.

Но когда появляется C ++, имена символов становятся более сложными. Функция может быть перегружена или быть членом класса. Но компоновщик не хочет этого знать. Чтобы сохранить простоту и возможность повторного использования старых компоновщиков, необходимо указать одно имя, например, foo:

void foo(void) {}
void foo(int) {}
void ClassA::foo(void) {}

Но это уже нельзя назвать просто foo, так что здесь идет искажение имени. И мы можем получить от компилятора некоторые варианты, такие как foo_void, foo_int, foo_void_classa. И, наконец, компоновщик счастлив, поскольку все они выглядят как простые символы.

Когда мы хотим вызвать функцию foo, скомпилированную с помощью компилятора C, в коде C ++, мы должны сообщить компилятору, что мы хотим, чтобы foo был foo в стиле C, а не foo_void, как мог бы предположить компилятор C ++. Это делается с помощью:

extern "C" void foo();

Теперь компилятор знает, что foo компилируется с использованием компилятора C, и передает информацию компоновщику, который этот код вызывает foo. Компоновщик сопоставит его с определением foo в file1.obj. Так что это все, что я думаю.

Некоторые другие директивы, такие как cdecl или stdcall, специфичны для Windows и сообщают, как передаются параметры в вызовах функций. Да, для C и C ++ это cdecl. Но функции Windows API используют соглашение stdcall - Pascal (простота и исторически Microsoft однажды предоставила среду разработки для Windows на Pascal).

0 голосов
/ 10 апреля 2012

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

...