Почему искажение имени не стандартизировано
Мне просто интересно, почему искажение имен никогда не было стандартизировано стандартом C++. Очевидно, что наличие разных алгоритмов искажения имен наносит ущерб совместимости [1], и я не вижу никаких преимуществ в том, чтобы определять его в зависимости от реализации.
То есть, вопреки правилам вызова или размеру примитивов, сама машина не заботится и даже не знает, как вызывается функция. Так почему же он не был стандартизирован и почему он до сих пор не стандартизирован? В прошлом компиляторы меняли правила между версиями.
[1] все эти люди экспортируют функции как extern "C"
говорить о многом
2 ответа
Стандарт не касается деталей реализации. Есть много, много вещей, которые зависят от реализации и которые мешают программам работать вместе: как устроены классы, структура vtable
и т. д. Как правило, компиляторы изменят название искажения, если они изменят любое из них. Это сделано намеренно, так как предотвращает связывание кода, который не будет работать.
Для данной платформы возможно определить ABI C++; все компиляторы, которые придерживаются его, будут использовать совместимые реализации и иметь общее искажение имен. Однако это проблема для поставщиков платформы; по тем или иным причинам очень немногие поставщики определили ABI C++.
И причина extern "C"
работает потому, что почти все платформы определяют C ABI.
Стандарт на самом деле не требует искажения имени. В этом отношении Стандарт не требует чисел с плавающей запятой IEEE, целых чисел, дополняющих два, или какого-либо другого числа.
Пока не было широко распространенного ABI, на которое можно было бы положиться, GCC изо всех сил старалась использовать другую схему искажения имен, чем ее конкуренты:
G ++ не выполняет искажение имен так же, как другие компиляторы C++. Это означает, что объектные файлы, скомпилированные с одним компилятором, не могут использоваться с другим.
Этот эффект является намеренным, чтобы защитить вас от более тонких проблем. Компиляторы различаются по многим внутренним деталям реализации C++, в том числе: как расположены экземпляры классов, как реализовано множественное наследование и как обрабатываются вызовы виртуальных функций. Если бы кодировка имени была сделана одинаково, ваши программы связывались бы с библиотеками, предоставленными другими компиляторами - но программы при запуске запускались бы с ошибкой. Несовместимые библиотеки затем обнаруживаются во время соединения, а не во время выполнения.
Перенос имен также сложнее, чем понимают многие программисты. Например, как Стандарт определит приемлемые соглашения о вызовах для всех платформ, на которых может работать C++? Нужна ли система RISC для поддержки x86? stdcall
хотя системы RISC обычно передают свои аргументы в регистрах, а не в стеке?
Я тоже всегда хотел увидеть стандарт изменения имен C++. Я не говорю, что этот "стандарт" должен быть частью спецификации ISO C++ (больше, чем это сделано для ISO C). Но есть большое преимущество в том, что он где-то стандартизирован, особенно если вы создаете динамические библиотеки (DLL в Windows, dylibs в большинстве реализаций Unix/Mac), которые, по вашему мнению, могут использоваться другими исполняемыми файлами.
Если у меня есть код C, которым я хочу поделиться, размещение его в динамической библиотеке очень полезно, а экспорт прототипов функций C может использоваться многими другими разработчиками в совершенно разных средах разработки. Программисты Visual Basic могут легко получить доступ к таким DLL, как и программы Java, использующие JNI и т. Д. Python-привязки также легко выполняются вокруг них.
Однако, если у меня есть код на C++, такая DLL имеет очень ограниченное применение. Если клиентские приложения не знают точно протокол изменения имен реализации DLL, экспорт становится практически невозможным. Если я не хочу раскрывать исходный код C++, мне обычно остается создать статическую библиотеку (.lib) и выпустить ее с заголовком C++ и настройками сборки. Даже здесь пользователь, по сути, должен использовать мою конкретную среду разработки C++.
Я считаю, что мне нужно создать оболочку C API для классов C++, которые переходят в объект C++. Например, я могу использовать непрозрачный 64-битный "дескриптор", который под капотом представляет собой карту для указателей объектов, и поэтому открытая функция C "Foo(objHdl,x,y,z)" отображается на "obj->foo(x,y,z)". Затем я перехожу к другому пути на стороне клиента, так что если мой клиентский код тоже является C++, то я создаю клиентские классы с зеркальными методами, которые переходят к функциям C.
Как видите, все это становится немного громоздким.
И мне совершенно ненужно. Если бы существовал стандартизированный протокол изменения имен C++, клиент C++ мог бы просто вызвать его. (Возможно, я захочу продолжить экспорт C API в DLL, для этих клиентов на C, но было бы неплохо предложить выбор.)
Даже если бы такие стандарты были специфичными для платформы, это было бы началом. Поскольку проблема в настоящее время такова, одна среда разработки C++ может управлять вещами совершенно иначе, чем другая среда разработки C++, даже в той же ОС. И даже в одной и той же среде разработки между версиями этой среды происходят изменения.
Эквивалентная ситуация в C может быть не идеальной, но ее гораздо легче обойти. Например, в моих экспортированных функциях C API я обычно не использую обобщенные типы данных, такие как "int" или "short", в моих открытых функциях, а, скорее, конкретно "int32_t" и т. Д. Я обычно избегаю проблем с выравниванием структуры, делая свой C API возвращает каждое поле отдельно, что-то вроде GetXXXField(xxxHandle, e_xxxFieldEnum, &xxxfieldValue). В конце концов, они становятся котлом, и этот образец используется годами.