Чи безпечно пов’язувати об’єкти C ++ 17, C ++ 14 та C ++ 11


101

Припустимо, у мене є три скомпільовані об'єкти, всі створені одним і тим же компілятором / версією :

  1. A був складений за стандартом C ++ 11
  2. B був складений за стандартом C ++ 14
  3. C був складений за стандартом C ++ 17

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

Які комбінації цих об’єктів це і чи не безпечно пов’язувати в єдиний двійковий файл? Чому?


EDIT: вітаються відповіді, що охоплюють основні компілятори (наприклад, gcc, clang, vs ++)


6
Не питання школи / співбесіди. Питання випливає з конкретного випадку: я працюю над проектом, який залежить від бібліотеки з відкритим кодом. Я будую цю бібліотеку з джерела, але її система збірки приймає лише прапор для вибору між C ++ 03 / C ++ 11. Компілятор, який я використовую, підтримує інші стандарти, і я розглядаю можливість оновити свій власний проект до C ++ 17. Я не впевнений, чи це безпечне рішення. Чи може бути перерва в ABI чи інший спосіб, коли підхід не є доцільним? Я не знайшов чіткої відповіді і вирішив опублікувати запитання про загальний випадок.
ricab

6
Це повністю залежить від компілятора. У офіційних специфікаціях С ++ немає нічого, що регулює цю ситуацію. Існує також невелика ймовірність того, що код, написаний на стандарти C ++ 03 або C + 11, матиме деякі проблеми на рівні C ++ 14 та C ++ 17. Маючи достатньо знань та досвіду (і добре написаного коду для початку), слід мати можливість виправити будь-яку з цих проблем. Якщо ви, однак, не дуже добре знайомі з новішими стандартами C ++, вам краще дотримуватися того, що підтримує система складання, і перевірено на роботу.
Сам Варшавчик

10
@Someprogrammerdude: Це надзвичайно варте запитання. Я хотів би мати відповідь. Мені відомо лише те, що libstdc ++ через RHEL devtoolset є зворотно сумісним за дизайном, шляхом статичного зв'язування в нових матеріалах і залишення старіших речей динамічно розв'язуватися під час використання, використовуючи "рідний" libstdc ++ дистрибутива. Але це не відповідає на питання.
Гонки легкості на орбіті

4
@nm: ... що в основному так ... майже всі, хто поширює незалежні від розподілу бібліотеки C ++, роблять це (1) у динамічній формі бібліотеки та (2) без стандартних контейнерів бібліотек C ++ на межах інтерфейсу. Бібліотеки, що надходять з дистрибутива Linux, дуже прості, оскільки всі вони створені з одним і тим же компілятором, однаковою стандартною бібліотекою та майже однаковим набором прапорів за замовчуванням.
Matteo Italia

3
Просто для роз’яснення попереднього коментаря від @MatteoItalia "та при переході з режиму C ++ 03 на режим C ++ 11 (зокрема, std :: string)." Це неправда, активна std::stringреалізація в libstdc ++ не залежить від використовуваного -stdрежиму . Це є важливою властивістю, саме для підтримки ситуацій, подібних до ОП. Ви можете використовувати нове std::stringв коді С ++ 03, а можете використовувати старе std::stringв коді С ++ 11 (див. Посилання в подальшому коментарі Маттео).
Джонатан Уейклі

Відповіді:


121

Які комбінації цих об’єктів це і чи не безпечно пов’язувати в єдиний двійковий файл? Чому?

Для GCC безпечно пов'язувати будь-яку комбінацію об'єктів A, B і C. Якщо всі вони побудовані з однією версією, то вони сумісні з ABI, стандартна версія (тобто -stdопція) не має ніякого значення.

Чому? Тому що це важлива властивість нашої реалізації, яку ми наполегливо працюємо, щоб забезпечити.

Проблеми виникають у тому, що ви пов’язуєте об’єкти, скомпільовані з різними версіями GCC, і використовували нестабільні функції нового стандарту C ++ до того, як підтримка GCC цього стандарту завершиться. Наприклад, якщо ви компілюєте об'єкт за допомогою GCC 4.9 -std=c++11та іншого об'єкта за допомогою GCC 5, у -std=c++11вас виникнуть проблеми. Підтримка C ++ 11 була експериментальною в GCC 4.x, і тому відбулися несумісні зміни між GCC 4.9 та 5 версіями функцій C ++ 11. Подібним чином, якщо ви скомпілюєте один об'єкт з GCC 7, а -std=c++17інший об'єкт - з GCC 8, у -std=c++17вас виникнуть проблеми, оскільки підтримка C ++ 17 в GCC 7 і 8 все ще експериментальна і розвивається.

З іншого боку, буде працювати будь-яка комбінація наступних об’єктів (хоча дивіться примітку нижче про libstdc++.soверсію):

  • об'єкт D, складений з GCC 4.9 та -std=c++03
  • об'єкт E, складений з GCC 5 та -std=c++11
  • об'єкт F, складений з GCC 7 та -std=c++17

Це пояснюється тим, що підтримка C ++ 03 стабільна у всіх трьох використовуваних версіях компілятора, а тому компоненти C ++ 03 сумісні між усіма об’єктами. Підтримка C ++ 11 стабільна з GCC 5, але об'єкт D не використовує жодних функцій C ++ 11, а обидва об'єкти E і F використовують версії, де підтримка C ++ 11 стабільна. Підтримка C ++ 17 не є стабільною в жодній із використовуваних версій компілятора, але лише об'єкт F використовує функції C ++ 17, тому з двома іншими об'єктами не виникає проблем сумісності (єдині функції, якими вони діляться, походять від C ++ 03 або C ++ 11, і використовувані версії роблять ці частини в порядку). Якщо пізніше ви захотіли скомпілювати четвертий об'єкт, G, використовуючи GCC 8, -std=c++17то вам потрібно буде перекомпілювати F з тією ж версією (або не посилатись на F), оскільки символи C ++ 17 у F та G несумісні.

Єдиним застереженням щодо сумісності, описаної вище між D, E та F, є те, що ваша програма повинна використовувати libstdc++.soспільну бібліотеку з GCC 7 (або пізнішої версії). Оскільки об'єкт F був скомпільований з GCC 7, вам потрібно використовувати спільну бібліотеку з цього випуску, оскільки компіляція будь-якої частини програми з GCC 7 може ввести залежності від символів, яких немає в libstdc++.soз GCC 4.9 або GCC 5. Аналогічно, якщо ви пов'язані з об'єктом G, побудованим за допомогою GCC 8, вам потрібно буде використовувати libstdc++.soз GCC 8, щоб переконатися, що всі символи, необхідні G, знайдені. Просте правило полягає в тому, щоб забезпечити спільну бібліотеку, яку програма використовує під час виконання, принаймні настільки ж нову, як і версія, яка використовується для компіляції будь-якого з об’єктів.

Ще одне застереження при використанні GCC, про яке вже згадувалося в коментарях до вашого питання, полягає в тому, що з GCC 5 існують дві реалізації,std::string доступні в libstdc ++. Дві реалізації не є сумісними з посиланнями (вони мають різні спотворені імена, тому не можуть бути пов'язані між собою), але можуть співіснувати в одному і тому ж двійковому файлі (вони мають різні спотворені імена, тому не конфліктуйте, якщо один об'єкт використовує std::stringта інші види використання std::__cxx11::string). Якщо ваші об'єкти використовують, std::stringто зазвичай всі вони повинні бути скомпільовані з однаковою реалізацією рядків. Скомпілюйте, -D_GLIBCXX_USE_CXX11_ABI=0щоб вибрати оригінальну gcc4-compatibleреалізацію, або -D_GLIBCXX_USE_CXX11_ABI=1вибрати нову cxx11реалізацію (не обманюйте себе назвою, її також можна використовувати в C ++ 03, вона називаєтьсяcxx11оскільки він відповідає вимогам C ++ 11). Яка реалізація є типовою, залежить від того, як було налаштовано GCC, але за замовчуванням завжди можна замінити макрос під час компіляції.


"оскільки компіляція будь-якої частини програми з GCC 7 може ввести залежності від символів, які присутні в libstdc ++. отже, з GCC 4.9 або GCC 5" ви мали на увазі, що НЕ присутні з GCC 4.9 або GCC 5, правда? Чи стосується це також статичного посилання? Дякуємо за інформацію про сумісність між версіями компілятора.
Хаді Брайс

1
Я щойно зрозумів величезний недолік у тому, щоб запропонувати нагороду з цього питання. 😂
Гонки легкості на орбіті

4
@ricab Я на 90% впевнений, що відповідь однакова для Clang / libc ++, але я не маю уявлення про MSVC.
Джонатан Уейклі

1
Ця відповідь - зоряна. Чи десь задокументовано, що 5.0+ стабільний для 11/14?
Баррі,

1
Не дуже чітко чи в одному місці. gcc.gnu.org/gcc-5/changes.html#libstdcxx та gcc.gnu.org/onlinedocs/libstdc++/manual/api.html#api.rel_51 оголошують бібліотечну підтримку C ++ 11 повною (мова підтримка була повнофункціональною раніше, але все ще "експериментальна"). Підтримка бібліотеки C ++ 14 досі перелічена як експериментальна до 6.1, але я думаю, що на практиці нічого не змінилося між 5.x та 6.x, що впливає на ABI.
Джонатан Уейклі,

17

Відповідь складається з двох частин. Сумісність на рівні компілятора та сумісність на рівні лінкера. Почнемо з першого.

припустимо, всі заголовки написані на C ++ 11

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

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

Все це означає, що ваше припущення може стосуватися лише файлів заголовків, які ви написали. Ці заголовкові файли можуть спричинити несумісність, якщо вони включені в різні блоки перекладу, орієнтовані на різні стандарти C ++. Це обговорюється в додатку С до стандарту С ++. Є 4 речення, я обговорю лише перший, а коротко згадаю решту.

C.3.1 Пункт 2: лексичні умовні позначення

Поодинокі лапки розмежовують символьний літерал у C ++ 11, тоді як вони є роздільниками цифр у C ++ 14 та C ++ 17. Припустимо, у вас є таке визначення макросу в одному із чистого заголовного файлу C ++ 11:

#define M(x, ...) __VA_ARGS__

// Maybe defined as a field in a template or a type.
int x[2] = { M(1'2,3'4) };

Розглянемо дві одиниці перекладу, які включають файл заголовка, але цільові C ++ 11 та C ++ 14, відповідно. При націлюванні на C ++ 11 кома в лапках не вважається роздільником параметрів; параметр є лише один раз. Тому код буде еквівалентний:

int x[2] = { 0 }; // C++11

З іншого боку, при націлюванні на C ++ 14, одинарні лапки інтерпретуються як роздільники цифр. Тому код буде еквівалентний:

int x[2] = { 34, 0 }; // C++14 and C++17

Справа тут у тому, що використання одинарних лапок в одному із чистих файлів заголовків C ++ 11 може призвести до дивовижних помилок у одиницях перекладу, націлених на C ++ 14/17. Отже, навіть якщо файл заголовка написаний на C ++ 11, він повинен бути написаний обережно, щоб переконатися, що він сумісний з пізнішими версіями стандарту. __cplusplusДиректива може бути корисним тут.

Інші три статті стандарту включають:

С.3.2 Пункт 3: основні поняття

Зміна : Новий звичайний (не розміщений) дилокатор

Обґрунтування : Потрібно для розміщення великих розмірів.

Вплив на оригінальну функцію : дійсний код C ++ 2011 може оголосити глобальну функцію розподілу розміщення та функцію звільнення таким чином:

void operator new(std::size_t, std::size_t); 
void operator delete(void*, std::size_t) noexcept;

Однак у цьому міжнародному стандарті декларація видалення оператора може збігатися із заздалегідь визначеним звичайним (не розміщенням) оператором видалення (3.7.4). Якщо так, програма неправильно сформована, як це було для функцій розподілу членів класу та функцій звільнення (5.3.4).

C.3.3 Пункт 7: декларації

Зміна : нестатичні функції-члени constexpr не є неявними функціями-членами.

Обґрунтування : Необхідно дозволити функціям-членам constexpr мутувати об'єкт.

Вплив на оригінальну функцію : Діючий код C ++ 2011 може не скомпілюватись у цьому міжнародному стандарті.

Наприклад, такий код є дійсним у C ++ 2011, але недійсним у цьому міжнародному стандарті, оскільки він двічі оголошує одну і ту ж функцію-член із різними типами повернення:

struct S {
constexpr const int &f();
int &f();
};

C.3.4 Пункт 27: Бібліотека вводу / виводу

Зміна : отримує не визначено.

Обґрунтування : Використання отримувачів вважається небезпечним.

Вплив на оригінальну особливість : дійсний код C ++ 2011, який використовує функцію get, може не вдатися до компіляції в цьому міжнародному стандарті.

Потенційні несумісності між C ++ 14 і C ++ 17 обговорюються в C.4. Оскільки всі нестандартні файли заголовків написані на C ++ 11 (як зазначено у питанні), ці проблеми не виникатимуть, тому я не буду їх тут згадувати.

Зараз я обговорю сумісність на рівні компонувальника. Загалом, потенційні причини несумісності включають наступне:

  • Формат об'єктних файлів.
  • Програмуйте процедури запуску та завершення та mainточку входу.
  • Оптимізація цілої програми (WPO).

Якщо формат результуючого об'єктного файлу залежить від цільового стандарту C ++, компонувальник повинен мати можливість зв'язувати різні об'єктні файли. У країнах GCC, LLVM та VC ++ це, на щастя, не так. Тобто формат файлів об’єктів однаковий, незалежно від цільового стандарту, хоча він сильно залежить від самого компілятора. Насправді жоден з компонувальників GCC, LLVM та VC ++ не потребує знань про цільовий стандарт C ++. Це також означає, що ми можемо зв'язати об'єктні файли, які вже скомпільовані (статично пов'язуючи час виконання).

Якщо підпрограма запуску програми (функція, яка викликає main) відрізняється для різних стандартів C ++ і різні підпрограми не сумісні між собою, тоді неможливо буде зв’язати об’єктні файли. У країнах GCC, LLVM та VC ++ це, на щастя, не так. Крім того, підпис mainфункції (та обмеження, що застосовуються до неї, див. Розділ 3.6 стандарту) однаковий у всіх стандартах С ++, тому не має значення, в якій одиниці перекладу вона існує.

Загалом, WPO може погано працювати з об'єктними файлами, скомпільованими з використанням різних стандартів C ++. Це залежить від того, які саме етапи компілятора вимагають знання цільового стандарту, а які - ні, та вплив, який він робить на міжпроцедурну оптимізацію, яка перетинає об’єктні файли. На щастя, GCC, LLVM та VC ++ добре розроблені і не мають цієї проблеми (не те, що мені відомо).

Тому GCC, LLVM та VC ++ були розроблені, щоб забезпечити двійкову сумісність у різних версіях стандарту C ++. Однак це насправді не є вимогою самого стандарту.

До речі, хоча компілятор VC ++ пропонує перемикач std , який дозволяє націлювати певну версію стандарту C ++, він не підтримує націлювання на C ++ 11. Мінімальною версією, яку можна вказати, є C ++ 14, яка за замовчуванням починається з оновлення Visual C ++ 2013 3. Для націлювання на C ++ 11 можна використовувати стару версію VC ++, але тоді вам доведеться використовувати різні компілятори VC ++ компілювати різні одиниці перекладу, орієнтовані на різні версії стандарту С ++, що як мінімум порушить WPO.

CAVEAT: Моя відповідь може бути не повною або дуже точною.


Питання справді мало стосуватися посилань, а не компіляції. Я усвідомлюю (завдяки цьому коментарю ), що, можливо, було незрозумілим, і відредагував його, щоб було зрозуміло, що будь-які включені заголовки мають однакову інтерпретацію у всіх трьох стандартах.
рікаб

@ricab Відповідь охоплює компіляцію та посилання. Я думав, ви питаєте про обидва.
Хаді Брайс

1
Дійсно, але я вважаю, що відповідь занадто довга і заплутана, особливо до "Зараз я обговорю сумісність на рівні компонувальника". Ви можете замінити все вищенаведене чимось на кшталт, якщо включені заголовки не можуть постулювати, щоб вони мали однакове значення в C ++ 11 та C ++ 14/17, тоді їх не безпечно включати в першу чергу . В іншій частині, чи є у вас джерело, яке показує, що ці три пункти кулі - єдині потенційні причини несумісності? Дякую за відповідь у будь-якому випадку, я все ще голосую
рікаб

@ricab Я не можу сказати точно. Ось чому я додав застереження в кінці відповіді. Будь-хто інший може розширити відповідь, щоб зробити її більш точною або повною, якщо я щось пропустив.
Хаді Брайс

Це мене бентежить: "Використання того самого компілятора означає, що будуть використовуватися однакові стандартні заголовки бібліотеки та вихідні файли (...)". Як це може бути так? Якщо у мене є старий код, скомпільований за допомогою gcc5, «файли компілятора», що належали до цієї версії, не можуть бути підтвердженням у майбутньому. Для вихідного коду, скомпільованого в (дико) різний час з різними версіями компілятора, ми можемо бути впевнені, що заголовок бібліотеки та вихідні файли відрізняються. За вашим правилом, що вони повинні бути однаковими, вам доведеться перекомпілювати старіший вихідний код за допомогою gcc5, ... і переконатися, що всі вони використовують найновіші (однакові) "файли компілятора".
user2943111

2

Нові стандарти C ++ складаються з двох частин: мовні функції та стандартні бібліотечні компоненти.

Як ви маєте на увазі під новим стандартом , зміни в самій мові (наприклад, діапазон для) майже не виникають (іноді конфлікти існують у заголовках сторонніх бібліотек із новими функціями стандартної мови).

Але стандартна бібліотека ...

Кожна версія компілятора постачається із реалізацією стандартної бібліотеки C ++ (libstdc ++ з gcc, libc ++ з clang, стандартною бібліотекою MS C ++ з VC ++, ...) та рівно однією реалізацією, не так багато реалізації для кожної стандартної версії. Також у деяких випадках ви можете використовувати іншу реалізацію стандартної бібліотеки, ніж наданий компілятором. Вам слід подбати про пов’язання старої стандартної реалізації бібліотеки з новою.

Конфлікт, який може виникнути між сторонніми бібліотеками та вашим кодом, - це стандартна бібліотека (та інші бібліотеки), яка посилається на ці сторонні бібліотеки.


"Кожна версія компілятора поставляється із реалізацією STL" Ні, вони цього не роблять
Lightness Races на орбіті

@LightnessRacesinOrbit Ви маєте на увазі, що немає зв'язку між, наприклад, libstdc ++ та gcc?
Е. Вакілі

8
Ні, я маю на увазі STL фактично застарів вже трохи більше двадцяти років. Ви мали на увазі стандартну бібліотеку C ++. Що стосується решти відповіді, чи можете ви надати деякі посилання / докази для підтвердження своєї вимоги? Я думаю, що для такого питання важливо.
Гонки легкості на орбіті

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