2016-04-14 4 views
133

Недавно у меня было интервью, и один вопрос спросил, что такое использование extern "C" в коде на C++. Я ответил, что использовать C-функции в C++-коде, поскольку C не использует имя-mangling. Меня спросили, почему C не использует шифрование имен, и, честно говоря, я не мог ответить.Почему функции C не могут быть названы?

Я понимаю, что когда компилятор C++ компилирует функции, он дает специальное имя функции главным образом потому, что мы можем иметь перегруженные функции с тем же именем в C++, которые должны быть разрешены во время компиляции. В C имя функции останется неизменным или, может быть, с _ перед ним.

Мой запрос: что не так, что позволяет компилятору C++ также использовать функции C? Я бы предположил, что неважно, какие имена им дает компилятор. Аналогично мы называем функции в C и C++.

+74

C не нуждается в том, чтобы заманить имена, потому что у него нет функции перегрузки. – EOF

+9

Как вы связываете библиотеки C с кодом C++, если компилятор C++ управляет именами функций? – Mat

+0

, но что неправильно, позволяя компилятору просто калечить их в любом случае? Почему это повлияет на нашу программу? Тогда нам не понадобится extern «C». – Engineer999

ответ

188

Это было как-то ответило выше, но я постараюсь внести в контекст.

Во-первых, C пришел первым. Таким образом, то, что C делает, вроде, «default». Он не управляет именами, потому что это просто не так. Имя функции - это имя функции. Глобальный глобальный и т. Д.

Затем пришел C++. C++ хотел иметь возможность использовать тот же компоновщик, что и C, и иметь возможность связываться с кодом, написанным на C. Но C++ не мог оставить C "mangling" (или, если есть), как есть. Посмотрите следующий пример:

int function(int a); 
int function(); 

В C++ это различные функции с различными кузовами. Если ни один из них не искалечен, оба будут называться «функция» (или «_функция»), и компоновщик будет жаловаться на переопределение символа. Решение C++ заключалось в том, чтобы использовать типы аргументов в имени функции. Таким образом, один называется _function_int, а другой называется _function_void (не фактическая схема манипуляции), и избегается столкновение.

Теперь у нас проблема. Если int function(int a) был определен в модуле C, и мы просто берем его заголовок (то есть объявление) в коде C++ и используя его, компилятор будет генерировать инструкцию для компоновщика для импорта _function_int. Когда функция была определена, в модуле C она не называлась. Он назывался _function. Это приведет к ошибке компоновщика.

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

extern "C" int function(int a); 

C++ компилятор теперь знает, чтобы импортировать _function, а не _function_int, и все в порядке.

+1

@ShacharShamesh: Я спросил об этом в другом месте, но как насчет ссылки в C++ скомпилированных библиотеках? Когда компилятор перешагивает и компилирует мой код, который вызывает одну из функций в скомпилированной библиотеке C++, как он узнает, какое имя будет искажать или давать функции при простом вызове объявления или вызова функции? Как узнать, что там, где оно определено, оно искажено именем чего-то еще? Таким образом, в C++ должен быть стандартный метод определения имен? – Engineer999

+1

Каждый компилятор делает это в своем собственном путь. Если вы компилируете все с одним и тем же компилятором, это не имеет значения. Но если вы попытаетесь использовать, скажем, библиотеку, которая была скомпилирована с компилятором Borland, из программы, которую вы создаете с помощью компилятора Microsoft, ну ... удачи; вам это понадобится :) –

+5

@ Engineer999 Вы когда-нибудь задавались вопросом, почему нет таких вещей, как переносимые библиотеки C++, но они либо точно определяют, какую версию (и флаги) компилятора (и стандартной библиотеки) вы должны использовать или просто экспортировать C API? Вот так. C++ - это, по крайней мере, самый портативный язык, когда-либо изобретенный, а C - полная противоположность. В этом отношении есть усилия, но пока вы хотите что-то действительно портативное, вы будете придерживаться C. – Voo

19

MSVC фактически mangle C имена, хотя и простым способом. Он иногда добавляет @4 или другое небольшое число. Это относится к вызовам конвенций и необходимости очистки стека.

Таким образом, помещение является просто ошибочным.

+0

Да, но это делается ** только ** для функций '__stdcall' и' __fastcall', которые ** не являются стандартным методом C вызова ** (который остается старым славным ** «вызывающим» очищает стек »**) .. –

+2

На самом деле это не манго. Это просто соглашение об именах (или название adorning) для конкретного поставщика, чтобы препятствовать тому, чтобы связанные с исполняемыми файлами проблемы связаны с библиотеками DLL, построенными с функциями, имеющими разные соглашения о вызовах. – Peter

+2

Как насчет добавления с '_'? – OrangeDog

45

Это не значит, что они «не могут», они не являются, в общем.

Если вы хотите, чтобы вызвать функцию в библиотеке C называется foo(int x, const char *y), это не хорошо, позволяя Вашему C++ компилятор калечить, что в foo_I_cCP() (или что-то, только что сделал схему коверкая на месте здесь) только потому, что он может.

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

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

+0

Это часть, которую мне не хватает. Почему компилятор C++ управляет именем функции, когда видит свое объявление только или видит, что он вызывается. Разве это не просто калечит имена функций, когда видит их реализацию? Это будет иметь для меня больше смысла – Engineer999

+13

@ Engineer999: Как у вас есть одно имя для определения, а другое - для объявления? _ «Есть функция, называемая Брайан, которую вы можете вызвать». «Хорошо, я позвоню Брайану». «Извините, нет функции, называемой Брайан». Оказывается, это называется Грэхем. –

+0

Что касается связывания в скомпилированных библиотеках C++? Когда компилятор перешагивает и компилирует наш код, который вызывает одну из функций в скомпилированной библиотеке C++, как он узнает, какое имя следует калечить или давать функции при простом вызове объявления или вызова функции? – Engineer999

8

C++ хочет иметь возможность взаимодействовать с C-кодом, который ссылается на него или связан с ним.

C ожидает, что имена функций, не называемые именами.

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

9

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

C не требует этого, поскольку он не допускает перегрузки функций.

Обратите внимание, что имя mangling - это одна (но, конечно, не единственная!) Причина, по которой нельзя полагаться на «C++ ABI».

32

Что не так, что позволяет компилятору C++ также использовать функции C?

Они больше не будут функциями C.

Функция - это не просто подпись и определение; как работает функция, во многом определяется такими факторами, как конвенция о вызове. «Бинарный интерфейс приложения», указанный для использования на вашей платформе, описывает, как системы разговаривают друг с другом. C++ ABI, используемый вашей системой, определяет схему переключения имен, так что программы в этой системе знают, как вызывать функции в библиотеках и т. Д. (Прочтите C++ Itanium ABI для отличного примера. Вы очень быстро поймете, почему это необходимо.)

То же самое относится к C ABI в вашей системе. На некоторых C ABI действительно есть схема смены имени (например, Visual Studio), поэтому это меньше относится к «отключению манипуляции с именами» и более о переключении с C++ ABI на C ABI для определенных функций. Мы отмечаем функции C как функции C, к которым относится C ABI (а не C++ ABI). Объявление должно соответствовать определению (будь то в одном проекте или в какой-либо сторонней библиотеке), в противном случае объявление бессмысленно. Без этого ваша система просто не знает, как найти/вызвать эти функции.

А почему платформы не определяют C и C++ АБИС, чтобы быть такими же, и избавиться от этих «проблем», что это частично историческими — оригинального C АБИС не были достаточным для C++, который имеет пространство имен, классы и перегрузка оператора, все из которых должны каким-то образом быть представлены в имени символа в удобном для пользователя образом —, но можно также утверждать, что создание программ на C, которые теперь соблюдают C++, является несправедливым по отношению к сообществу C, которое должно было бы с массово более сложным ABI только ради других людей, которые хотят интероперабельности.

+2

'+ int (PI/3)', но с одним зерном соли: я бы очень осторожно говорил о «C++ ABI» ... AFAIK, есть * попытки * при определении C++ ABI, но нет ** реальные ** * de facto */* de jure * стандарты - как https://isocpp.org/files/papers/n4028.pdf (и я полностью согласен), цитата, * глубоко иронично, что C++ на самом деле всегда поддерживал способ опубликования API со стабильным двоичным ABI-кодом , прибегая к подмножеству C C++ через extern «C». *. 'C++ Itanium ABI' именно это - * некоторые * C++ ABI для Itanium ... как обсуждалось на http://stackoverflow.com/questions/7492180/c-abi-issues-list – vaxquis

+3

@vaxquis: Да, а не «C++» ABI », но« C++ ABI »так же, как у меня есть« ключ дома », который не работает на каждом доме. Угадайте, что это может быть яснее, хотя я попытался сделать это как можно более ясным, начав с фразы _ «C++ ABI **, используемой вашей системой **» _. Я бросил осветлитель в более поздних высказываниях для краткости, но я приму изменение, которое уменьшает путаницу здесь! –

+1

AIUI C abi как правило, является свойством платформы, в то время как C++ ABI, как правило, является свойством отдельного компилятора и часто даже является свойством отдельной версии компилятора. Поэтому, если вы хотите связать между модулями, созданными с использованием разных инструментов вендоров, вам необходимо использовать C abi для интерфейса. – plugwash

13

Очень распространены программы, которые частично написаны на языке C и частично написаны на каком-либо другом языке (часто это ассемблерный язык, но иногда Pascal, FORTRAN или что-то еще). Также распространено, что программы содержат разные компоненты, написанные разными людьми, у которых, возможно, нет исходного кода для всего.

На большинстве платформ есть спецификация - часто называемая ABI [Application Binary Interface], которая описывает, что должен сделать компилятор для создания функции с определенным именем, которое принимает аргументы некоторых конкретных типов и возвращает значение определенного типа. В некоторых случаях ABI может определять более чем одно «соглашение о вызове»; компиляторы для таких систем часто предоставляют средство для указания того, какое соглашение о вызове должно использоваться для конкретной функции. Например, на Macintosh, большинство Toolbox подпрограммы используют соглашение о вызовах Pascal, поэтому прототип что-то вроде «LineTo» будет что-то вроде:

/* Note that there are no underscores before the "pascal" keyword because 
    the Toolbox was written in the early 1980s, before the Standard and its 
    underscore convention were published */ 
pascal void LineTo(short x, short y); 

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

+0

Да, это ответ. Если это просто C и C++, тогда трудно понять, почему это делается именно так. Чтобы понять, мы должны положить вещи в контексте старого способа статической связи. Статическая привязка кажется примитивной для программистов Windows, но это основная причина, по которой C ** не может ** подменять имена. – user34660

+2

@ user34660: Не qutie. Это связано с тем, что C не может указывать на существование функций, реализация которых потребует либо вытеснять экспортируемые имена, либо допускать существование нескольких одинаково названных символов, которые отличаются вторичными характеристиками. – supercat

+0

Известно ли нам, что были попытки «отследить» такие вещи или что такие вещи были расширениями, доступными для C до C++? – user34660

12

Я добавлю еще один ответ, чтобы обсудить некоторые из тангенциальных дискуссий, которые имели место.

C ABI (прикладной двоичный интерфейс), первоначально вызываемый для передачи аргументов в стеке в обратном порядке (т. Е. - нажатый справа налево), где вызывающий объект также освобождает хранилище стека. Современная ABI фактически использует регистры для передачи аргументов, но многие из соображений о переходе возвращаются к тому, что передается исходный аргумент стека.

Оригинальный Pascal ABI, напротив, подтолкнул аргументы слева направо, и вызываемому пришлось выставить аргументы. Оригинальный C ABI превосходит оригинальный Pascal ABI в двух важных точках. Аргумент push order означает, что смещение стека первого аргумента всегда известно, позволяя функции, которые имеют неизвестное количество аргументов, где ранние аргументы управляют количеством других аргументов (ala printf).

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

Оригинальный Windows 3.1 ABI был основан на Паскале. Как таковой, он использовал Pascal ABI (аргументы в порядке слева направо, вызываемые позывы). Поскольку любое несоответствие в аргументе может привести к повреждению стека, была создана схема переключения. Каждое имя функции исказилось с номером, указывающим размер в байтах его аргументов.Так, на 16-битной машине, следующая функция (синтаксис C):

int function(int a) 

был подогнан к [email protected], потому что int два байта в ширине. Это было сделано так, что если объявление и определение не совпадают, компоновщик не сможет найти функцию, а не повреждает стек во время выполнения. И наоборот, если ссылки программы, то вы можете быть уверены, что правильное количество байт выставляется из стека в конце вызова.

32-битные Windows и последующие используют вместо этого ABI stdcall. Он похож на Pascal ABI, за исключением того, что порядок push похож на C, справа налево. Как и Pascal ABI, имя mangling искажает размер байта аргументов в имени функции, чтобы избежать повреждения стека.

В отличие от утверждений, сделанных в другом месте здесь, C ABI не изменяет имена функций даже в Visual Studio. И наоборот, функции управления, декорированные спецификацией ABI stdcall, не уникальны для VS. GCC также поддерживает этот ABI, даже при компиляции для Linux. Это широко используется Wine, в котором используется собственный загрузчик, чтобы обеспечить возможность компоновки исполняемых файлов из Linux в скомпилированные DLL-файлы Windows.

3

Переплетение имен функций и переменных C позволило бы проверить их типы во время связи. В настоящее время все (?) C-реализации позволяют вам определять переменную в одном файле и вызывать ее как функцию в другом. Или вы можете объявить функцию с неверной подписью (например void fopen(double), а затем вызвать его.

Я предложил a scheme for the type-safe linkage of C variables and functions путем использования коверкая в 1991 году Схема не был принят, потому что, как и другие отметили здесь, это будет

+1

Вы имеете в виду «разрешить проверку их типов по * ссылке * времени». Типы * * проверяются во время компиляции, но связь с неподписанными именами не позволяет проверить, согласны ли декларации, используемые в разных единицах компиляции. И если они не согласны, это ваша система сборки, которая принципиально нарушена и нуждается в исправлении. – cmaster

 Смежные вопросы

  • Нет связанных вопросов^_^