Почему ISO / ANSI не стандартизировал C ++ на двоичном уровне? Есть много проблем с переносимостью в C ++, что связано только с отсутствием стандартизации на двоичном уровне.
Дон Бокс пишет, (цитируя его книгу Essential COM , глава COM As A Better C ++ )
C ++ и Переносимость
После того как принято решение распространять класс C ++ в виде DLL, возникает один из фундаментальных недостатков C ++ , а именно отсутствие стандартизации на двоичном уровне . Хотя рабочий документ ISO / ANSI C ++ пытается систематизировать, какие программы будут компилироваться и каковы будут семантические эффекты их запуска, он не пытается стандартизировать бинарную модель времени исполнения C ++. Впервые эта проблема станет очевидной, когда клиент попытается соединиться с библиотекой импорта DLL-библиотеки FastString из среды разработки C ++, отличной от той, которая использовалась для создания DLL-библиотеки FastString.
Есть ли еще преимущества или потеря этого отсутствия бинарной стандартизации?
Ответы:
Языки с двоично-совместимой скомпилированной формой - это относительно новая фаза [*], например, среды выполнения JVM и .NET. Компиляторы C и C ++ обычно генерируют нативный код.
Преимущество состоит в том, что нет необходимости в JIT, или в интерпретаторе байт-кода, или в виртуальной машине, или в любой другой подобной вещи. Например, вы не можете написать загрузочный код, который запускается при запуске компьютера, как красивый, переносимый байт-код Java, если, возможно, машина не может выполнять нативно байт-код Java или если у вас есть какой-то преобразователь из Java в не двоичный код, совместимый с нативным исполняемый код (теоретически: не уверен, что это может быть рекомендовано на практике для кода начальной загрузки). Вы могли бы написать его на C ++, более или менее, хотя и не на переносимом C ++, даже на уровне исходного кода, поскольку он будет много портить магические аппаратные адреса.
Недостатком является то, что, конечно, нативный код выполняется только в той архитектуре, для которой он был скомпилирован, а исполняемые файлы могут быть загружены только загрузчиком, который понимает их исполняемый формат, и связываются и вызывают другие исполняемые файлы для той же архитектуры и вызывают их. ABI.
Даже если вы достигнете такого уровня, объединение двух исполняемых файлов будет работать корректно только в том случае, если: (а) вы не нарушаете правило единого определения, что легко сделать, если они были скомпилированы с разными компиляторами / опциями / чем угодно, так, что они использовали разные определения одного и того же класса (либо в заголовке, либо потому, что каждый из них статически связан с разными реализациями); и (b) все соответствующие подробности реализации, такие как структура структуры, идентичны в соответствии с параметрами компилятора, действующими на момент компиляции каждого из них.
Для стандарта C ++, определяющего все это, будет удалено множество свобод, доступных в настоящее время для разработчиков. Реализаторы используют эти свободы, особенно когда пишут код очень низкого уровня на C ++ (и C, который имеет ту же проблему).
Если вы хотите написать что-то, похожее на C ++, для переносимой в двоичном виде цели, есть C ++ / CLI, предназначенный для .NET, и Mono, так что вы можете (надеюсь) запустить .NET где-то, кроме Windows. Я думаю, что можно убедить компилятор MS создавать чистые сборки CIL, которые будут работать на Mono.
Есть также потенциально вещи, которые можно сделать, например, с помощью LLVM, чтобы создать переносимую двоичную среду C или C ++. Я не знаю, что появился какой-либо распространенный пример.
Но все они основаны на исправлении многих вещей, которые C ++ делает зависимыми от реализации (например, размеры типов). Тогда среда, которая понимает переносимые двоичные файлы, должна быть доступна в системе, где должен выполняться код. Допуская непереносимые двоичные файлы, C и C ++ могут пойти туда, где переносимые двоичные файлы не могут, и поэтому стандарт ничего не говорит о двоичных файлах.
Тогда на любой данной платформе реализации обычно все еще не обеспечивают двоичную совместимость между различными наборами параметров, хотя стандарт не останавливает их. Если Дон Боксу не нравится, что компиляторы Microsoft могут создавать несовместимые двоичные файлы из одного и того же источника, в соответствии с параметрами компилятора, то это команда разработчиков, на которую он должен жаловаться. Язык C ++ не запрещает компилятору или ОС фиксировать все необходимые детали, поэтому, если вы ограничитесь Windows, это не станет фундаментальной проблемой для C ++. Microsoft решила не делать этого.
Различия часто проявляются как еще одна вещь, которую вы можете ошибиться и вывести из строя вашу программу, но может быть значительный выигрыш в эффективности между, например, несовместимой отладкой и выпуском версий dll.
[*] Я не уверен, когда идея была впервые изобретена, вероятно, 1642 или что-то в этом роде, но их нынешняя популярность относительно нова, по сравнению с тем временем, когда C ++ принимал решения по проектированию, которые мешают ему определять двоичную переносимость.
источник
Кросс-платформенная и кросс-компиляторная совместимость не были основными целями C и C ++. Они родились в эпоху и предназначались для целей, для которых минимизация времени и пространства для конкретной платформы и для компилятора имела решающее значение.
Из Страуструпа "Дизайн и эволюция C ++":
источник
Это не ошибка, это особенность! Это дает разработчикам свободу оптимизировать их реализацию на двоичном уровне. I386 с прямым порядком байтов и его потомки - не единственные процессоры, которые существуют или существуют.
источник
Проблема, описанная в цитате, вызвана преднамеренным избеганием стандартизации схем искажения имен символов (я думаю, что « стандартизация на двоичном уровне » является вводящей в заблуждение фразой в этом отношении, хотя проблема связана с двоичным интерфейсом приложения компилятора ( ABI).
C ++ кодирует сигнатуру и информацию о типе функции или объекта данных и его принадлежность к классу / пространству имен в имя символа, и различным компиляторам разрешено использовать разные схемы. Следовательно, символ в статической библиотеке, DLL или объектном файле не будет связываться с кодом, скомпилированным с использованием другого компилятора (или, возможно, даже другой версии того же компилятора).
Проблема описана и объяснена, вероятно, лучше, чем я, здесь , с примерами схем, используемых различными компиляторами.
Причины преднамеренного отсутствия стандартизации также объясняются здесь .
источник
Целью ISO / ANSI была стандартизация языка C ++, проблема, которая кажется достаточно сложной, чтобы потребовались годы, чтобы обновить языковые стандарты и поддержку компилятора.
Бинарная совместимость намного сложнее, учитывая, что двоичные файлы должны работать на разных архитектурах ЦП и в разных средах ОС.
источник
Как сказал Энди, кросс-платформенная совместимость не была большой целью, в то время как широкая платформа и аппаратная реализация были целью, в результате чего вы можете написать соответствующие реализации для очень широкого выбора систем. Двоичная стандартизация сделала бы это практически недостижимым.
Совместимость с C была также важна и значительно усложнила бы это.
Впоследствии были предприняты некоторые попытки стандартизировать ABI для подмножества реализаций.
источник
Я думаю, что отсутствие стандарта для C ++ является проблемой в современном мире модульного программирования. Однако мы должны определить, чего мы хотим от такого стандарта.
Никто в здравом уме не хочет определять реализацию или платформу для двоичного файла. Таким образом, вы не можете взять библиотеку Windows x86 и начать использовать ее на платформе Linux x86_64. Это было бы немного.
Однако то, что хотят люди, - это то же самое, что и у нас с модулями C - стандартизированный интерфейс на двоичном уровне (т.е. после компиляции). В настоящее время, если вы хотите загрузить dll в модульное приложение, вы экспортируете функции C и связываетесь с ними во время выполнения. Вы не можете сделать это с модулем C ++. Было бы здорово, если бы вы могли, что также означало бы, что dll, написанные одним компилятором, могут быть загружены другим. Конечно, вы все равно не сможете загрузить dll, созданную для несовместимой платформы, но это не проблема, требующая исправления.
Так что, если бы орган по стандартизации определил, какой интерфейс предоставляет модуль, то у нас было бы гораздо больше гибкости при загрузке модулей C ++, нам не пришлось бы выставлять код C ++ как код C, и мы, вероятно, получили бы гораздо больше пользы C ++ в скриптовых языках.
Нам также не придется страдать от таких вещей, как COM, которые пытаются решить эту проблему.
источник
Я не думаю, что это так просто. Приведенные ответы уже дают превосходное обоснование отсутствия акцента на стандартизации, но C ++ может быть слишком богат языком, чтобы быть подходящим для реальной конкуренции с C как стандартом ABI.
Мы можем перейти к искажению имен, вызванному перегрузкой функций, несовместимостью vtable, несовместимостью с исключениями, выходящими за границы модуля, и т. Д. Все это - настоящая боль, и мне бы хотелось, чтобы они хотя бы стандартизировали макеты vtable.
Но стандарт ABI - это не только создание dlib-файлов C ++, созданных в одном компиляторе, которые могут использоваться другим двоичным файлом, созданным другим компилятором. ABI используется кросс-языки . Было бы неплохо, если бы они могли хотя бы охватить первую часть, но я не вижу, чтобы C ++ когда-либо действительно конкурировал с C на уровне универсального ABI, столь важном для создания наиболее широко совместимых dylib.
Представьте себе простую пару функций, экспортируемых так:
... и представьте себе ,
Foo
иBar
были классами с параметризованными конструкторами, конструкторы копирования, перемещение конструкторов и нетривиальные деструкторами.Затем возьмите сценарий Python / Lua / C # / Java / Haskell / и т.д. Разработчик пытается импортировать этот модуль и использовать его на своем языке.
Для начала нам понадобится стандарт искажения имен для экспорта символов с использованием перегрузки функций. Это более простая часть. Тем не менее, это не должно быть название «калеча». Поскольку пользователи dylib должны искать символы по имени, перегрузки здесь должны приводить к именам, которые не выглядят как полный беспорядок. Возможно, имена символов могут быть похожими
"f_Foo"
"f_Bar_int"
или что-то в этом роде. Мы должны быть уверены, что они не могут конфликтовать с именем, фактически определенным разработчиком, возможно, резервируя некоторые символы / символы / соглашения для использования ABI.Но сейчас более сложный сценарий. Как разработчик Python, например, вызывает конструкторы перемещения, конструкторы копирования и деструкторы? Может быть, мы могли бы экспортировать их как часть Dylib. Но что, если
Foo
иBar
экспортируются в разных модулях? Должны ли мы дублировать символы и реализации, связанные с этим дилибом или нет? Я бы посоветовал нам это сделать, так как это может очень быстро раздражать, иначе начинать запутываться в нескольких интерфейсах dylib просто для того, чтобы создать объект здесь, передать его сюда, скопировать туда, уничтожить здесь. В то время как та же самая основная проблема могла бы в некоторой степени применяться в C (просто в большей степени вручную / явно), C стремится избежать этого просто по природе того, как люди программируют с ней.Это всего лишь маленький образец неловкости. Что происходит, когда одна из
f
функций, приведенных выше, выбрасываетBazException
(также класс C ++ с конструкторами и деструкторами и выводом std :: exception) в JavaScript?В лучшем случае я думаю, что мы можем только надеяться стандартизировать ABI, который работает от одного двоичного файла, созданного одним компилятором C ++, до другого двоичного файла, созданного другим. Это было бы здорово, конечно, но я просто хотел указать на это. Обычно при распространении обобщенной библиотеки, работающей с кросс-компиляторами, часто возникает желание сделать ее действительно обобщенной и совместимой с кросс-языками.
Предлагаемое решение
Мое предлагаемое решение после того, как я в течение многих лет пытался найти способы использования интерфейсов C ++ для API / ABI с интерфейсами в стиле COM, - просто стать разработчиком "C / C ++" (каламбур).
Используйте C для создания этих универсальных ABI, а C ++ для реализации. Мы все еще можем делать такие вещи, как функции экспорта, которые возвращают указатели на непрозрачные классы C ++ с явными функциями для создания и уничтожения таких объектов в куче. Попробуйте влюбиться в эту эстетику C с точки зрения ABI, даже если мы полностью используем C ++ для реализации. Абстрактные интерфейсы могут быть смоделированы с использованием таблиц указателей функций. Утомительно включать это в C API, но преимущества и совместимость поставляемого дистрибутива, как правило, делают его очень полезным.
Тогда, если нам не нравится использовать этот интерфейс напрямую (нам, вероятно, не следует по крайней мере по причинам RAII), мы можем обернуть все, что мы хотим, в статически связанную библиотеку C ++, поставляемую с SDK. Клиенты C ++ могут использовать это.
Клиенты Python не захотят использовать интерфейс C или C ++ напрямую, так как нет способов сделать это Pythonique. Они захотят обернуть его в свои собственные интерфейсы pythonique, так что на самом деле хорошо, что мы просто экспортируем минимум API C / ABI, чтобы сделать это как можно проще.
Я думаю, что многие отрасли C ++ выиграют от делать это более, чем пытаться упорно интерфейсов корабля COM-стиля и так далее. Это также облегчит нашу жизнь, поскольку пользователям этих dlib не придется суетиться с неуклюжими ABI. C делает это простым, а простота этого с точки зрения ABI позволяет нам создавать API / ABI, которые работают естественно и с минимализмом для всех видов FFI.
источник
Я не знаю, почему он не стандартизируется на бинарном уровне. Но я знаю, что я делаю с этим. На Windows я объявляю функцию extern "C" BOOL WINAPI. (Конечно, замените BOOL на любой тип функции.) И они чисто экспортируются.
источник
extern "C"
, он будет использовать C ABI, который де-факто является стандартом для стандартного оборудования ПК, даже если он не навязывается какими-либо комитетами.Используйте,
unzip foo.zip && make foo.exe && foo.exe
если вы хотите переносимость вашего источника.источник