Почему функции C не могут быть искажены именем?


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

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

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

9 134

9 ответов:

это был своего рода ответ выше, но я постараюсь поставить вещи в контекст.

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

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

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

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

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

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

extern "C" int function(int a);

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

дело не в том, что они "не могут", они не, в целом.

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

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

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

что плохого в том, чтобы позволить компилятору 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++ ABIs, чтобы быть одинаковыми и избавиться от этой "проблемы", это частично исторически - оригинальные C ABIs не были достаточными для C++, что имеет пространства имен, классы и перегрузку операторов, все из которых должны каким-то образом быть представлены в имени символа в удобной для компьютера манере - но можно также утверждать, что заставить программы C теперь соблюдать C++ несправедливо по отношению к сообществу C, которому пришлось бы мириться с массово более сложным ABI только ради некоторых других людей, которые хотят взаимодействия.

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

таким образом, предпосылка просто ошибочна.

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

на большинстве платформ существует спецификация -- часто называемая 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]. Возможность определения имени компоновщика таким образом, решающее значение для использования таких функций.

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

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

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

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

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

int function(int a)

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

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

в отличие от утверждений, сделанных в другом месте здесь, C ABI не искажает имена функций, даже в Visual Studio. И наоборот, калечить функции, украшенные stdcall спецификация аби не уникальна для ПРОТИВ. ССЗ также поддерживает эту Аби, даже при компиляции для Linux. Это широко используется вина, который использует свой собственный загрузчик, чтобы разрешить связывание времени выполнения скомпилированных двоичных файлов Linux с скомпилированными DLL Windows.

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

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

обратите внимание, что имя mangling является одним (но, конечно, не единственным!) причина, по которой нельзя полагаться на "C++ ABI".

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

C ожидает не-имя-искаженные имена функций.

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

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

Я предложил!--4-->схема для типобезопасной связи переменных и функций C благодаря использованию калечить еще в 1991 году. Схема так и не была принята, потому что, как отмечали здесь другие, Это разрушило бы обратную совместимость.