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

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

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

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

Ответ 1

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

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

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

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

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

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

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

extern "C" int function(int a);

Теперь компилятор С++ теперь импортирует _function, а не _function_int, и все хорошо.

Ответ 2

Это не то, что они "не могут", они вообще не являются.

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

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

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

Ответ 3

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

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

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

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

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

Ответ 4

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

Итак, предпосылка просто испорчена.

Ответ 5

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

Ответ 6

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

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

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

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

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

int function(int a)

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

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

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

Ответ 7

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

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

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

Ответ 8

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

C ожидает имена имен, не зависящих от имени.

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

Ответ 9

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

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