C ++: отсутствие стандартизации на двоичном уровне

14

Почему ISO / ANSI не стандартизировал C ++ на двоичном уровне? Есть много проблем с переносимостью в C ++, что связано только с отсутствием стандартизации на двоичном уровне.

Дон Бокс пишет, (цитируя его книгу Essential COM , глава COM As A Better C ++ )

C ++ и Переносимость


После того как принято решение распространять класс C ++ в виде DLL, возникает один из фундаментальных недостатков C ++ , а именно отсутствие стандартизации на двоичном уровне . Хотя рабочий документ ISO / ANSI C ++ пытается систематизировать, какие программы будут компилироваться и каковы будут семантические эффекты их запуска, он не пытается стандартизировать бинарную модель времени исполнения C ++. Впервые эта проблема станет очевидной, когда клиент попытается соединиться с библиотекой импорта DLL-библиотеки FastString из среды разработки C ++, отличной от той, которая использовалась для создания DLL-библиотеки FastString.

Есть ли еще преимущества или потеря этого отсутствия бинарной стандартизации?

Наваз
источник
Это лучше спросить на programmers.stackexchange.com , видя, что это скорее субъективный вопрос?
Стивен Фурлани
1
На самом деле мой связанный вопрос: stackoverflow.com/questions/2083060/…
AraK
4
Дон Бокс - фанатик Игнорируй его.
Джон Диблинг
8
Ну, C не стандартизирован ANSI / ISO на двоичном уровне; OTOH C имеет де-факто стандарт ABI, а не де-юре . C ++ не имеет такого стандартизированного ABI, потому что у разных производителей были разные цели с их реализациями. Например, исключения в VC ++ в сочетании с Windows SEH. POSIX не имеет SEH, и поэтому использование этой модели не имело бы смысла (поэтому G ++ и MinGW не используют эту модель).
Билли ОНил
3
Я вижу это как особенность, а не слабость. Если вы привяжете реализацию к определенному ABI, то у нас никогда не будет инноваций, и новое оборудование будет привязано к дизайну языка (а поскольку между каждой новой версией в аппаратной индустрии существует 15 лет) и удушением внедрять новые идеи, чтобы сделать код более эффективным, не будет. Цена заключается в том, что весь код в исполняемом файле должен быть собран одним и тем же компилятором / версией (проблема, но не основная).

Ответы:

16

Языки с двоично-совместимой скомпилированной формой - это относительно новая фаза [*], например, среды выполнения 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 ++ принимал решения по проектированию, которые мешают ему определять двоичную переносимость.

Стив Джессоп
источник
@Steve Но C имеет четко определенный ABI на i386 и AMD64, поэтому я могу передать указатель на функцию, скомпилированную GCC-версией X, на функцию, скомпилированную MSVC-версией Y. Это невозможно с помощью функции C ++.
user877329
7

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

Из Страуструпа "Дизайн и эволюция C ++":

«Явная цель состояла в том, чтобы сопоставить C с точки зрения времени выполнения, компактности кода и компактности данных ... Идеал - который был достигнут - заключался в том, чтобы C с классами мог использоваться для любого C, для которого он мог бы использоваться».

Энди Томас
источник
1
+1 - точно. Как построить стандартный ABI, который бы работал на ARM и Intel? Не имеет смысла!
Билли Онеал
1
к сожалению, это не удалось в этом. Вы можете делать все, что делает C ... за исключением динамической загрузки модуля C ++ во время выполнения. Вы должны «вернуться» к использованию функций C в открытом интерфейсе.
gbjbaanb
6

Это не ошибка, это особенность! Это дает разработчикам свободу оптимизировать их реализацию на двоичном уровне. I386 с прямым порядком байтов и его потомки - не единственные процессоры, которые существуют или существуют.


источник
6

Проблема, описанная в цитате, вызвана преднамеренным избеганием стандартизации схем искажения имен символов (я думаю, что « стандартизация на двоичном уровне » является вводящей в заблуждение фразой в этом отношении, хотя проблема связана с двоичным интерфейсом приложения компилятора ( ABI).

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

Проблема описана и объяснена, вероятно, лучше, чем я, здесь , с примерами схем, используемых различными компиляторами.

Причины преднамеренного отсутствия стандартизации также объясняются здесь .

Клиффорд
источник
3

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

Бинарная совместимость намного сложнее, учитывая, что двоичные файлы должны работать на разных архитектурах ЦП и в разных средах ОС.


источник
Да, но проблема, описанная в цитате, на самом деле не имеет ничего общего с «совместимостью на двоичном уровне» (несмотря на то, что автор использует этот термин) в каком-либо смысле, кроме тех, которые определены в так называемом «двоичном интерфейсе приложения». Он фактически описывает проблему несовместимых схем искажения имен.
@Clifford: схема искажения имени - это лишь часть совместимости двоичного уровня. последнее больше похоже на общий термин!
Наваз
Я сомневаюсь, что есть проблема с попыткой запустить бинарный файл Linux на компьютере с Windows. Все было бы намного лучше, если бы существовала ABI для каждой платформы, так как по крайней мере тогда язык сценариев мог бы динамически загружать и запускать двоичный файл на той же платформе, или приложения могли использовать компоненты, созданные с помощью другого компилятора. Вы не можете использовать C dll на linux сегодня, и никто не жалуется, но этот C dll все еще может быть загружен приложением python, в котором преимущество накапливается.
gbjbaanb
2

Как сказал Энди, кросс-платформенная совместимость не была большой целью, в то время как широкая платформа и аппаратная реализация были целью, в результате чего вы можете написать соответствующие реализации для очень широкого выбора систем. Двоичная стандартизация сделала бы это практически недостижимым.

Совместимость с C была также важна и значительно усложнила бы это.

Впоследствии были предприняты некоторые попытки стандартизировать ABI для подмножества реализаций.

Flexo
источник
Черт, я забыл C совместимость. Хороший вопрос +1!
Энди Томас
1

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

Никто в здравом уме не хочет определять реализацию или платформу для двоичного файла. Таким образом, вы не можете взять библиотеку Windows x86 и начать использовать ее на платформе Linux x86_64. Это было бы немного.

Однако то, что хотят люди, - это то же самое, что и у нас с модулями C - стандартизированный интерфейс на двоичном уровне (т.е. после компиляции). В настоящее время, если вы хотите загрузить dll в модульное приложение, вы экспортируете функции C и связываетесь с ними во время выполнения. Вы не можете сделать это с модулем C ++. Было бы здорово, если бы вы могли, что также означало бы, что dll, написанные одним компилятором, могут быть загружены другим. Конечно, вы все равно не сможете загрузить dll, созданную для несовместимой платформы, но это не проблема, требующая исправления.

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

Нам также не придется страдать от таких вещей, как COM, которые пытаются решить эту проблему.

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

Есть много проблем с переносимостью в C ++, что связано только с отсутствием стандартизации на двоичном уровне.

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

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

Но стандарт ABI - это не только создание dlib-файлов C ++, созданных в одном компиляторе, которые могут использоваться другим двоичным файлом, созданным другим компилятором. ABI используется кросс-языки . Было бы неплохо, если бы они могли хотя бы охватить первую часть, но я не вижу, чтобы C ++ когда-либо действительно конкурировал с C на уровне универсального ABI, столь важном для создания наиболее широко совместимых dylib.

Представьте себе простую пару функций, экспортируемых так:

void f(Foo foo);
void f(Bar bar, int val);

... и представьте себе , 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.


источник
1
«Используйте C для создания этих универсальных ABI, а C ++ для реализации». ... Я делаю то же самое, как и многие другие!
Наваз
-1

Я не знаю, почему он не стандартизируется на бинарном уровне. Но я знаю, что я делаю с этим. На Windows я объявляю функцию extern "C" BOOL WINAPI. (Конечно, замените BOOL на любой тип функции.) И они чисто экспортируются.

Майк Джонс
источник
2
Но если вы объявите это extern "C", он будет использовать C ABI, который де-факто является стандартом для стандартного оборудования ПК, даже если он не навязывается какими-либо комитетами.
Билли ОНил
-3

Используйте, unzip foo.zip && make foo.exe && foo.exeесли вы хотите переносимость вашего источника.

Сьерд
источник