C ++: Відсутність стандартизації на бінарному рівні


14

Чому ISO / ANSI не стандартизував C ++ на бінарному рівні? З C ++ існує багато проблем з портативністю, що відбувається лише через відсутність її стандартизації на бінарному рівні.

Дон Бокс пише, (цитуючи свою книгу " Основні COM" , розділ COM як кращий C ++ )

C ++ та портативність


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

Чи є більше переваг Або втрата цієї нестачі бінарної стандартизації?


Це краще запитати на programmers.stackexchange.com , бачачи, як це більше суб'єктивне питання?
Стівен Фурлані

1
Пов'язані моє запитання на самому ділі: stackoverflow.com/questions/2083060 / ...
арак

4
Дон Бокс - це завзяття. Ігноруйте його.
Джон Дайблінг

8
Ну, і C не стандартизований ANSI / ISO на бінарному рівні; OTOH C має фактично стандартний ABI, а не де-юре . C ++ не має такого стандартизованого ABI, оскільки різні виробники мали різні цілі зі своїми реалізаціями. Наприклад, винятки у скарбничці VC ++ у верхній частині Windows SEH. POSIX не має SEH, і тому брати цю модель не мало б сенсу (тому G ++ та MinGW не використовують цю модель).
Біллі ONeal

3
Я бачу це як особливість, а не слабкість. Якщо ви прив’яжете реалізацію до певного ABI, ми ніколи не матимемо інновацій, і нове обладнання буде прив’язане до дизайну мови (а оскільки між кожною новою версією, яка триває в апаратній індустрії, існує 15 років) та задушкою впроваджувати нові ідеї для більш ефективного виконання коду не буде. Ціна полягає в тому, що весь код у виконаному файлі повинен будуватися одним компілятором / версією (проблема, але не є основною).

Відповіді:


16

Мови з бінарною сумісною формою є порівняно новою фазою [*], наприклад, час виконання JVM та .NET. Компілятори C і C ++ зазвичай випромінюють нативний код.

Перевага полягає в тому, що немає необхідності в JIT, або інтерпретаторі байт-коду, або VM, або будь-якому іншому. Наприклад, ви не можете записати завантажувальний код, який працює при запуску машини, як гарний, портативний байт-код Java, якщо, можливо, машина не зможе виконати байт-код Java, або у вас є якийсь перетворювач з Java на небінарний, сумісний з кодом виконуваний код (теоретично: не впевнений, що це може бути рекомендовано на практиці для завантажувального коду). Ви можете написати це на C ++, більш-менш, хоч і не портативному C ++ навіть на рівні джерела, оскільки це буде багато возитися з чарівними апаратними адресами.

Недоліком є ​​те, що звичайний кодовий код працює взагалі лише в архітектурі, для якої він був складений, і виконувані файли можуть завантажуватися лише завантажувачем, який розуміє їх виконуваний формат, і лише пов'язувати з іншими виконуваними файлами та входити в них для тієї самої архітектури та АБІ.

Навіть якщо ви досягнете такого далекого зв’язку, з'єднання двох виконуваних файлів разом буде працювати лише правильно, якщо: (a) ви не порушите Правило однієї дефініції, що легко зробити, якщо вони були складені з різними компіляторами / параметрами / що завгодно, такі, що вони використовували різні визначення одного класу (або в заголовку, або через те, що кожне статично пов'язане між різними реалізаціями); та (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 ++" Stroustrup:

"Явна мета полягала в тому, щоб співставити C з точки зору часу роботи, компактності коду та компактності даних. Ідеальним - що було досягнуто - було, щоб C з класами можна було використовувати для того, для чого C може бути використаний."


1
+1 - точно. Як можна створити стандартний ABI, який працював як на коробках ARM, так і на Intel? Не було б сенсу!
Біллі ONeal

1
на жаль, у цьому не вдалося. Ви можете робити все, що робить C ... крім динамічного завантаження модуля C ++ під час виконання. вам доведеться 'повернутися' до використання функцій C у відкритому інтерфейсі.
gbjbaanb

6

Це не помилка, це особливість! Це надає виконавцям свободу оптимізувати їх реалізацію на бінарному рівні. Маленький ендіан i386 та його нащадки - не єдині процесори, які існують чи існують.


6

Проблема, описана в котировці, викликана цілком навмисним уникненням стандартизації схем управління іменами символів (я вважаю, що « стандартизація на бінарному рівні » є оманливою фразою в цьому відношенні, хоча проблема пов'язана з Бінарним інтерфейсом додатка компілятора ( ABI).

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

Питання описано та пояснено, мабуть, краще, ніж я можу тут , із прикладами схем, які використовуються різними компіляторами.

Причини навмисної недостатньою стандартизації також пояснюються тут .


3

Метою ISO / ANSI була стандартизація мови C ++, проблема, яка здається досить складною, щоб вимагати років, щоб оновити мовні стандарти та підтримку компілятора.

Бінарна сумісність набагато складніша, враховуючи, що бінарні файли повинні працювати в різних архітектурах процесорів та різних середовищах ОС.


Щоправда, але проблема, описана у котируванні, насправді не має нічого спільного з "сумісністю бінарного рівня" (незважаючи на використання авторським терміном) у будь-якому сенсі, окрім таких речей, визначених у чомусь, що називається "Бінарний інтерфейс програми". Він насправді описує проблему несумісних схем керування іменами.

@Clifford: схема керування іменами - лише підмножина сумісності бінарних рівнів. остання більше нагадує термін парасольки!
Наваз

Я сумніваюся, що існує проблема зі спробою запуску бінарного файлу Linux на машині Windows. Все було б набагато краще, якби на платформі був ABI, так як принаймні тоді мова скриптів могла динамічно завантажуватись і запускати двійковий файл на одній платформі, або програми могли використовувати компоненти, побудовані з іншого компілятора. Сьогодні ви не можете використовувати C dll в Linux, і ніхто не скаржиться, але цей dll все ще може бути завантажений програмою python, де саме накопичується вигода.
gbjbaanb

2

Як заявив Енді, сумісність між платформами не була великою метою, тоді як широка платформа та реалізація обладнання були ціллю, з таким результатом, що ви можете написати відповідні реалізації для дуже широкого вибору систем. Бінарна стандартизація зробила б це практично недосяжним.

Сумісність C також була важливою і значно ускладнила б це.

Згодом були зроблені певні зусилля щодо стандартизації ABI для підмножини реалізацій.


Дарн, я забув сумісність C. Хороший бал, +1!
Енді Томас

1

Я думаю, що відсутність стандарту для C ++ є проблемою в сучасному світі розв'язаного модульного програмування. Однак ми маємо визначити, чого ми хочемо від такого стандарту.

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

Однак те, що люди хочуть, те саме, що ми маємо з модулями C - це стандартизований інтерфейс на бінарному рівні (тобто один раз складений). Наразі, якщо ви хочете завантажити dll в модульний додаток, ви експортуєте функції C і прив'язуєте їх до виконання. Ви не можете це зробити з модулем C ++. Було б чудово, якби ви могли, що також означало б, що dlls, написані одним компілятором, можуть бути завантажені іншим. Звичайно, ви все одно не зможете завантажити dll, створений для несумісної платформи, але це не проблема, яка потребує виправлення.

Отже, якщо орган стандарти визначив, яким інтерфейсом піддається модуль, тоді ми мали б набагато більшу гнучкість при завантаженні модулів C ++, нам не довелося б виставляти код C ++ як код C, і ми, мабуть, отримаємо набагато більше використання мови C ++ на мовах скриптів.

Ми також не повинні зазнавати таких речей, як COM, які намагаються вирішити цю проблему.


1
+1. Так, я згоден. Інші відповіді тут, в основному, усувають проблему, кажучи, що двійкова стандартизація забороняє оптимізацію, характерну для архітектури. Але це не сенс. Ніхто не сперечається про якийсь міжплатформенний бінарний формат. Проблема полягає в тому, що не існує стандартного інтерфейсу для динамічного завантаження модулів C ++.
Чарльз Сальвія

1

З C ++ існує багато проблем з портативністю, що відбувається лише через відсутність її стандартизації на бінарному рівні.

Я не думаю, що це зовсім так просто. Надані відповіді вже дають чудове обґрунтування щодо недостатньої уваги до стандартизації, але C ++ може бути занадто багатим мовою, щоб бути добре підходящим для того, щоб по-справжньому конкурувати з C як стандартом ABI.

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

Але стандарт ABI стосується не лише створення C ++ дилібів, вироблених в одному компіляторі, який може використовуватися іншим бінарним файлом, побудованим іншим компілятором. ABI використовується крос-мовами . Було б добре, якби вони могли принаймні покрити першу частину, але немає ніякого способу, як я бачу, як C ++ колись справді конкурував із C на роді універсального рівня ABI, настільки важливого для створення найбільш широко сумісних дилібів.

Уявіть просту пару функцій, експортованих так:

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

... і уявіть собі, Fooчи Barбули класи з параметризованими конструкторами, конструкторами копій, конструкторами переміщення та нетривіальними деструкторами.

Потім візьміть сценарій Python / Lua / C # / Java / Haskell / тощо. розробник намагається імпортувати цей модуль і використовувати його мовою.

Спочатку нам знадобиться стандарт керування іменами, як експортувати символи, використовуючи перевантаження функцій. Це легша частина. І все-таки це насправді не повинно називатись "mangling". Оскільки користувачі dylib повинні шукати символи за назвою, перевантаження тут повинні призводити до імен, які не схожі на повний безлад. Можливо, назви символів можуть бути подібними "f_Foo" "f_Bar_int"або щось подібне. Ми повинні бути впевнені, що вони не можуть зіткнутися з іменем, яке фактично визначено розробником, можливо, резервуючи деякі символи / символи / умови для використання ABI.

Але тепер більш жорсткий сценарій. Як, наприклад, розробник Python викликає конструктори переміщення, конструктори копій та деструктори? Можливо, ми могли б експортувати їх як частину дилібу. Але що , якщо Fooі Barекспортуються в різних модулях? Чи слід дублювати символи та реалізації, пов’язані з цим дилібом, чи ні? Я б запропонував нам це зробити, оскільки це може стати дуже прикро дуже швидко, інакше для початку потрібно заплутуватися в декількох інтерфейсах дилібів, щоб створити тут об'єкт, передати його сюди, скопіювати там, знищити його тут. Незважаючи на те, що те саме базове занепокоєння може дещо застосувати в C (лише вручну / явно), C прагне уникати цього лише за характером того, як люди програмують його.

Це лише невеликий зразок незграбності. Що відбувається, коли одна з fфункцій, що перераховані вище, кидає BazException(JavaScript) класу C ++ з конструкторами та деструкторами та походить std :: виняток у JavaScript?

У кращому випадку я думаю, що ми можемо лише сподіватися стандартизувати ABI, який працює з одного двійкового, що виробляється одним компілятором C ++, до іншого бінарного файлу, виробленого іншим. Це було б чудово, звичайно, але я просто хотів це зазначити. Як правило, супроводжує таке питання щодо розповсюдження узагальненої бібліотеки, яка працює між компіляторами, також часто є бажання зробити її справді узагальненою та сумісною міжмовними мовами.

Пропоноване рішення

Моє запропоноване рішення після спроб знайти шляхи використання інтерфейсів C ++ для API / ABI протягом багатьох років з інтерфейсами стилю COM - це просто стати розробником "C / C ++" (каламбур).

Використовуйте C для створення цих універсальних ІСВ, з C ++ для реалізації. Ми все ще можемо робити такі речі, як функції експорту, які повертають покажчики на непрозорі класи C ++ з явними функціями для створення та знищення таких об’єктів у купі. Спробуйте закохатись у цю естетику C з точки зору ABI, навіть якщо ми повністю використовуємо C ++ для реалізації. Абстрактні інтерфейси можна моделювати за допомогою таблиць функціональних покажчиків. Досить нудно перетворювати ці речі в API API, але переваги та сумісність розповсюдженого дистрибутива, як правило, роблять це дуже вартим.

Тоді, якщо нам не подобається використовувати цей інтерфейс так сильно безпосередньо (ми, мабуть, не повинні принаймні з причин RAII), ми можемо зафіксувати все, що нам потрібно, у статично пов'язану бібліотеку C ++, яку ми постачаємо разом із SDK. Клієнти C ++ можуть використовувати це.

Клієнти Python не захочуть використовувати або інтерфейс C, або C ++ безпосередньо, оскільки немає способів зробити ці пітоніки. Вони захочуть зафіксувати це у власних інтерфейсах pythonique, тому насправді добре, що ми просто експортуємо мінімум API API / ABI, щоб зробити це якомога простіше.

Я думаю, що багато індустрії С ++ виграють від цього більше, ніж намагаються вперто передавати інтерфейси в стилі COM тощо. Це також полегшило б усе наше життя, оскільки користувачі цих дилібів не повинні метушитися з незграбними АБІ. C робить його простим, а простота його з точки зору ABI дозволяє нам створювати API / ABI, які працюють природно і з мінімалізмом для всіх видів ПІІ.


1
Msgstr "Використовуйте C для створення цих універсальних ABI, з C ++ для реалізації." ... Я роблю так само, як і багато інших!
Наваз

-1

Я не знаю, чому це не стандартизується на бінарному рівні. Але я знаю, що з цим роблю. У Windows я оголошую функцію зовнішньої "C" BOOL WINAPI. (Звичайно, замініть BOOL будь-яким типом функції.) І вони експортуються чисто.


2
Але якщо ви заявите про це extern "C", він використовуватиме C ABI, що є фактичним стандартом для звичайного обладнання для ПК, навіть якщо це не накладено жодним комітетом.
Біллі ONeal

-3

Використовуйте, unzip foo.zip && make foo.exe && foo.exeякщо ви хочете портативність свого джерела.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.