Недавно у меня было интервью, и был задан один вопрос: в чем смысл использования extern "C"
кода на C ++? Я ответил, что это использование функций C в коде C ++, так как C не использует искажение имен. Меня спросили, почему C не использует искажение имен и, честно говоря, я не мог ответить.
Я понимаю, что когда компилятор C ++ компилирует функции, он дает специальное имя функции главным образом потому, что мы можем иметь перегруженные функции с тем же именем в C ++, которые должны быть разрешены во время компиляции. В C имя функции останется прежним, или, возможно, с _ перед ним.
Мой вопрос: что плохого в том, чтобы позволить компилятору C ++ также манипулировать функциями C? Я бы предположил, что не имеет значения, какие имена им дает компилятор. Мы вызываем функции одинаково в C и C ++.
источник
extern "C"
говорит искажать имя так же, как это делал бы компилятор Си.Ответы:
Это было своего рода ответом выше, но я постараюсь поместить вещи в контекст.
Сначала С пришел первым. Таким образом, то, что делает C, является своего рода «по умолчанию». Он не искажает имена, потому что это не так. Имя функции - это имя функции. Глобал есть глобал и так далее.
Затем появился C ++. C ++ хотел иметь возможность использовать тот же компоновщик, что и C, и иметь возможность ссылаться с кодом, написанным на C. Но C ++ не мог оставить C "искаженным" (или его отсутствием) как есть. Посмотрите на следующий пример:
В C ++ это разные функции с разными телами. Если ни один из них не искажен, оба будут называться «function» (или «_function»), и компоновщик будет жаловаться на переопределение символа. Решением C ++ было преобразование типов аргументов в имя функции. Итак, один называется,
_function_int
а другой называется_function_void
(не фактическая схема искажения), и столкновения избегают.Теперь мы остались с проблемой. Если он
int function(int a)
был определен в модуле C, а мы просто берем его заголовок (то есть объявление) в коде C ++ и используем его, компилятор сгенерирует команду компоновщику для импорта_function_int
. Когда функция была определена, в модуле C она не называлась так. Это было названо_function
. Это приведет к ошибке компоновщика.Чтобы избежать этой ошибки, во время объявления функции мы сообщаем компилятору, что это функция, предназначенная для связи или компиляции с помощью компилятора C:
C ++ компилятор знает теперь импортировать ,
_function
а не_function_int
, и все хорошо.источник
-std=c++11
, стандарту , и избегая использования чего-либо вне стандарта. Это то же самое, что объявлять версию Java (хотя более новые версии Java обратно совместимы). Это не ошибка стандартов, люди используют специфичные для компилятора расширения и зависимый от платформы код. С другой стороны, их нельзя винить, так как в стандарте не хватает многих вещей (особенно IO, например, сокетов). Комитет, похоже, медленно догоняет это. Поправь меня, если я что-то пропустил.Это не то, что они «не могут», они не являются , в общем.
Если вы хотите вызвать функцию из библиотеки C с именем
foo(int x, const char *y)
, не стоит позволять вашему компилятору C ++ изменять это вfoo_I_cCP()
(или что-то еще, просто составив схему искажения на месте) просто потому, что это возможно.Это имя не разрешается, функция находится на C, а ее имя не зависит от списка типов аргументов. Поэтому компилятор C ++ должен это знать и пометить эту функцию как C, чтобы избежать искажения.
Помните, что указанная функция C может находиться в библиотеке, исходный код которой у вас нет, все, что у вас есть, это предварительно скомпилированный двоичный файл и заголовок. Таким образом, ваш компилятор C ++ не может делать «свое дело», он не может изменить то, что находится в библиотеке.
источник
extern "C"
:)Они больше не были бы C-функциями.
Функция - это не просто подпись и определение; как работает функция, во многом определяется такими факторами, как соглашение о вызовах. «Двоичный интерфейс приложения», указанный для использования на вашей платформе, описывает, как системы взаимодействуют друг с другом. Интерфейс C ++ ABI, используемый вашей системой, определяет схему искажения имени, чтобы программы в этой системе знали, как вызывать функции в библиотеках и так далее. (Прочитайте C ++ Itanium ABI для отличного примера. Вы очень быстро поймете, почему это необходимо.)
То же самое относится к C ABI в вашей системе. Некоторые C ABI на самом деле имеют схему искажения имени (например, Visual Studio), поэтому речь идет не о «отключении искажения имени», а о переключении с C ++ ABI на C ABI для определенных функций. Мы помечаем функции C как функции C, к которым относится C ABI (а не C ++ ABI). Объявление должно соответствовать определению (будь то в том же проекте или в какой-нибудь сторонней библиотеке), иначе объявление не имеет смысла.Без этого ваша система просто не будет знать, как найти / вызвать эти функции.
Что касается того, почему платформы не определяют CI и C ++ ABI одинаковыми и избавляются от этой «проблемы», то это отчасти исторически - исходных C ABI не хватало для C ++, который имеет пространства имен, классы и перегрузку операторов, все из которых нужно каким-то образом представлять в имени символа в удобной для компьютера форме, но можно также утверждать, что создание программ на C, в настоящее время подчиняющихся C ++, несправедливо по отношению к сообществу C, которое должно было бы мириться с гораздо более сложным ABI только ради некоторых других людей, которые хотят взаимодействия.
источник
+int(PI/3)
, но с небольшим количеством соли: я бы очень осторожно говорил о "C ++ ABI" ... AFAIK, есть попытки определить C ++ ABI, но нет реальных стандартов де-факто / де-юре - как isocpp.org/files /papers/n4028.pdf утверждает (и я искренне согласен), цитата : глубоко иронично, что C ++ всегда всегда поддерживал способ публикации API со стабильным двоичным ABI - прибегая к подмножеству C в C ++ через extern «C ». ,C++ Itanium ABI
это всего лишь некоторый C ++ ABI для Itanium ... как обсуждено на stackoverflow.com/questions/7492180/c-abi-issues-listMSVC фактически делает MANGLE имена C, хотя в простой форме. Иногда добавляется
@4
или другое небольшое число. Это относится к соглашениям о вызовах и необходимости очистки стека.Так что предпосылка просто ошибочна.
источник
_
?/Gd, /Gr, /Gv, /Gz
. (То есть, используется стандартное соглашение о вызовах, если только объявление функции явно не определяет соглашение о вызове.). Вы думаете о том,__cdecl
какое стандартное соглашение о вызовах по умолчанию.Очень часто есть программы, которые частично написаны на C и частично написаны на каком-то другом языке (часто на ассемблере, но иногда на Pascal, FORTRAN или чем-то еще). Также часто программы содержат разные компоненты, написанные разными людьми, которые могут не иметь исходного кода для всего.
На большинстве платформ существует спецификация, часто называемая ABI [Application Binary Interface], которая описывает, что должен делать компилятор для создания функции с определенным именем, которая принимает аргументы некоторых определенных типов и возвращает значение некоторого определенного типа. В некоторых случаях ABI может определять более одного «соглашения о вызовах»; Компиляторы для таких систем часто предоставляют средства указания, какое соглашение о вызовах следует использовать для конкретной функции. Например, в Macintosh большинство подпрограмм Toolbox используют соглашение о вызовах Pascal, поэтому прототип для чего-то вроде «LineTo» будет выглядеть примерно так:
Если весь код в проекте был скомпилирован с использованием одного и того же компилятора, не имеет значения, какое имя компилятор экспортировал для каждой функции, но во многих ситуациях для кода на C будет необходимо вызывать функции, которые были скомпилированы с использованием других инструментов и не может быть перекомпилирован с помощью настоящего компилятора [и вполне может даже не быть в C]. Таким образом, возможность определения имени компоновщика имеет решающее значение для использования таких функций.
источник
Я добавлю еще один ответ, чтобы ответить на некоторые из обсуждений, которые имели место.
C ABI (двоичный интерфейс приложения) первоначально вызывал передачу аргументов в стеке в обратном порядке (т. Е. Толкает справа налево), где вызывающая сторона также освобождает хранилище стека. Современный ABI фактически использует регистры для передачи аргументов, но многие из искажающих соображений восходят к передаче оригинального стека.
Оригинальный ABI Pascal, напротив, выдвигал аргументы слева направо, и вызываемый должен был выдвигать аргументы. Оригинальный C ABI превосходит оригинальный Pascal ABI в двух важных моментах. Порядок проталкивания аргументов означает, что смещение стека первого аргумента всегда известно, что позволяет функциям с неизвестным числом аргументов, где ранние аргументы управляют количеством других аргументов (ala
printf
).Второй способ превосходства C ABI - это поведение в том случае, если вызывающий и вызываемый абоненты не согласны с тем, сколько аргументов существует. В случае C, если вы на самом деле не обращаетесь к аргументам после последнего, ничего плохого не происходит. В Паскале неправильное количество аргументов извлекается из стека, и весь стек поврежден.
Оригинальный Windows 3.1 ABI был основан на Pascal. Как таковой, он использовал Паскаль ABI (аргументы в порядке слева направо, Callee Pops). Поскольку любое несоответствие номера аргумента может привести к повреждению стека, была сформирована схема искажения. Каждое имя функции было искажено числом, указывающим размер в байтах его аргументов. Итак, на 16-битной машине, следующая функция (синтаксис C):
Был искалечен
function@2
, потому чтоint
имеет ширину два байта. Это было сделано для того, чтобы в случае несоответствия объявления и определения компоновщик не смог найти функцию, а не повредил стек во время выполнения. И наоборот, если программа соединяется, то вы можете быть уверены, что в конце вызова выбрано правильное количество байтов из стека.32-битная Windows и далее использовать
stdcall
вместо этого ABI. Это похоже на Паскаль ABI, за исключением того, что порядок нажатия такой же, как в C, справа налево. Как и в Паскаль ABI, искажение имени меняет размер байтов аргументов в имя функции, чтобы избежать повреждения стека.В отличие от утверждений, сделанных в другом месте, C ABI не искажает имена функций, даже в Visual Studio. И наоборот, функции искажения, украшенные
stdcall
спецификацией ABI, не уникальны для VS. GCC также поддерживает этот ABI, даже при компиляции для Linux. Это широко используется Wine , который использует собственный загрузчик, чтобы разрешить связывание исполняемых файлов Linux скомпилированных библиотек Windows во время выполнения.источник
Компиляторы C ++ используют искажение имен, чтобы разрешить уникальные имена символов для перегруженных функций, чья сигнатура в противном случае была бы одинаковой. Он также в основном кодирует типы аргументов, что позволяет проводить полиморфизм на уровне функций.
C не требует этого, поскольку не допускает перегрузки функций.
Обратите внимание, что искажение имен является одной (но, конечно, не единственной!) Причиной, по которой нельзя полагаться на C ++ ABI.
источник
C ++ хочет иметь возможность взаимодействовать с кодом C, который ссылается на него или на который он ссылается.
C ожидает неименованные искаженные имена функций.
Если C ++ искажает его, он не найдет экспортированные не искаженные функции из C, или C не найдет экспортированные функции C ++. Компоновщик C должен получить имя, которое он сам ожидает, потому что он не знает, приходит он или собирается в C ++.
источник
Изменение имен функций и переменных Си позволило бы проверять их типы во время соединения. В настоящее время все (?) Реализации C позволяют вам определять переменную в одном файле и вызывать ее как функцию в другом. Или вы можете объявить функцию с неправильной подписью (например,
void fopen(double)
а затем вызвать ее).Я предложил схему безопасного для типов связывания переменных и функций языка Си с помощью искажения еще в 1991 году. Схема никогда не была принята, потому что, как другие отметили здесь, это разрушило бы обратную совместимость.
источник