Безопасно ли связывать объекты C ++ 17, C ++ 14 и C ++ 11

103

Предположим, у меня есть три скомпилированных объекта, созданных одним и тем же компилятором / версией :

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

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

Какие это комбинации этих объектов и небезопасно ли объединить их в один двоичный файл? Зачем?


РЕДАКТИРОВАТЬ: приветствуются ответы, касающиеся основных компиляторов (например, gcc, clang, vs ++)

рикаб
источник
6
Не вопрос школы / собеседования. Вопрос связан с конкретным случаем: я работаю над проектом, который зависит от библиотеки с открытым исходным кодом. Я собираю эту библиотеку из исходного кода, но ее система сборки принимает только флаг для выбора между сборкой C ++ 03 / C ++ 11. Однако компилятор, который я использую, поддерживает другие стандарты, и я подумываю обновить свой собственный проект до C ++ 17. Я не уверен, безопасно ли это решение. Может ли быть перерыв в ABI или какой-то другой подход, при котором такой подход не рекомендуется? Я не нашел внятного ответа и решил задать вопрос по общему случаю.
ricab
6
Это полностью зависит от компилятора. В формальных спецификациях C ++ нет ничего, что регулирует эту ситуацию. Также существует небольшая вероятность того, что код, написанный для стандартов C ++ 03 или C + 11, будет иметь некоторые проблемы на уровне C ++ 14 и C ++ 17. При наличии достаточных знаний и опыта (и хорошо написанного кода для начала) можно будет исправить любую из этих проблем. Однако, если вы не очень хорошо знакомы с новыми стандартами C ++, вам лучше придерживаться того, что поддерживает система сборки и с чем она будет протестирована.
Сэм Варшавчик
10
@Someprogrammerdude: Это очень стоящий вопрос. Хотел бы я получить ответ. Все, что я знаю, это то, что libstdc ++ через RHEL devtoolset имеет обратную совместимость по дизайну, статически связываясь с новыми материалами и оставляя старые компоненты для динамического разрешения во время выполнения с использованием «родной» libstdc ++ дистрибутива. Но это не отвечает на вопрос.
Lightness Races на орбите
4
@nm: ... что в большинстве случаев так ... почти все, кто распространяет независимые от дистрибутива библиотеки C ++, делают это (1) в форме динамической библиотеки и (2) без контейнеров стандартной библиотеки C ++ на границах интерфейса. Библиотеки, которые поступают из дистрибутива Linux, очень просты, поскольку все они построены с одним и тем же компилятором, одной стандартной библиотекой и почти одинаковым набором флагов по умолчанию.
Matteo Italia
3
Просто чтобы прояснить предыдущий комментарий от @MatteoItalia «и при переключении из режима C ++ 03 в режим C ++ 11 (в частности, std :: string)». Это неправда, активная std::stringреализация в libstdc ++ не зависит от используемого -stdрежима . Это важное свойство именно для поддержки таких ситуаций, как ОП. Вы можете использовать новый std::stringкод в C ++ 03 и старый std::stringкод в C ++ 11 (см. Ссылку в последующем комментарии Маттео).
Джонатан Уэйкли

Ответы:

123

Какие это комбинации этих объектов и небезопасно ли объединить их в один двоичный файл? Зачем?

Для 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++.soGCC 4.9 или GCC 5. Аналогичным образом, если вы связались с объектом G, созданным с помощью GCC 8, вам нужно будет использовать libstdc++.soGCC 8, чтобы убедиться, что все символы, необходимые для G, найдены. Простое правило - обеспечить, чтобы общая библиотека, которую программа использует во время выполнения, была как минимум такой же новой, как версия, используемая для компиляции любого из объектов.

Еще одно предостережение при использовании GCC, уже упоминавшееся в комментариях к вашему вопросу, заключается в том, что, начиная с GCC 5, в libstdc ++ доступны две реализацииstd::string . Две реализации несовместимы по ссылкам (у них разные искаженные имена, поэтому их нельзя связать вместе), но они могут сосуществовать в одном двоичном файле (у них разные искаженные имена, поэтому не конфликтуйте, если один объект использует 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, верно? Применимо ли это также к статическим ссылкам? Спасибо за информацию о совместимости между версиями компилятора.
Хади Брайс 05
1
Я только что осознал серьезную ошибку, предлагая вознаграждение за этот вопрос. 😂
Lightness Races in Orbit
4
@ricab Я на 90% уверен, что ответ такой же для Clang / libc ++, но я понятия не имею о MSVC.
Джонатан Уэйкли
1
Это звездный ответ. Где-то задокументировано, что версия 5.0+ стабильна на 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 ++, поддерживаемыми компилятором.

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

Все это означает, что ваше предположение применимо только к написанным вами заголовочным файлам. Эти файлы заголовков могут вызывать несовместимость при включении в разные единицы перевода, ориентированные на разные стандарты C ++. Это обсуждается в Приложении C стандарта 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Директива может быть полезным здесь.

Остальные три пункта стандарта включают:

C.3.2 Раздел 3: основные концепции

Изменение : новый обычный (без размещения) деаллокатор

Обоснование : требуется для освобождения размера.

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

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

Однако в этом международном стандарте объявление оператора delete может совпадать с предопределенным обычным (без размещения) оператором delete (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, использующий функцию gets, может не соответствовать этому международному стандарту.

Возможная несовместимость между C ++ 14 и C ++ 17 обсуждается в C.4. Поскольку все нестандартные файлы заголовков написаны на C ++ 11 (как указано в вопросе), этих проблем не возникнет, поэтому я не буду их здесь упоминать.

Теперь обсудим совместимость на уровне компоновщика. В общем, потенциальные причины несовместимости включают следующее:

Если формат результирующего объектного файла зависит от целевого стандарта C ++, компоновщик должен иметь возможность связывать различные объектные файлы. К счастью, в GCC, LLVM и VC ++ это не так. То есть формат файлов объектов один и тот же независимо от целевого стандарта, хотя он сильно зависит от самого компилятора. Фактически, ни один из компоновщиков GCC, LLVM и VC ++ не требует знаний о целевом стандарте C ++. Это также означает, что мы можем связывать уже скомпилированные объектные файлы (статически связывая среду выполнения).

Если процедура запуска программы (вызываемая функция main) отличается для разных стандартов C ++ и разные процедуры несовместимы друг с другом, то связать объектные файлы будет невозможно. К счастью, в GCC, LLVM и VC ++ это не так. Кроме того, сигнатура mainфункции (и ограничения, которые применяются к ней, см. Раздел 3.6 стандарта) одинакова для всех стандартов C ++, поэтому не имеет значения, в какой единице перевода она существует.

Как правило, WPO может плохо работать с объектными файлами, скомпилированными с использованием различных стандартов C ++. Это зависит от того, какие именно этапы компилятора требуют знания целевого стандарта, а какие - нет, а также от того, какое влияние он оказывает на межпроцедурные оптимизации, перекрестные с объектными файлами. К счастью, GCC, LLVM и VC ++ хорошо спроектированы и не имеют этой проблемы (насколько мне известно).

Поэтому GCC, LLVM и VC ++ были разработаны для обеспечения двоичной совместимости между различными версиями стандарта C ++. Однако на самом деле это не является требованием самого стандарта.

Кстати, хотя компилятор VC ++ предлагает переключатель std , который позволяет настроить таргетинг на конкретную версию стандарта C ++, он не поддерживает таргетинг на C ++ 11. Минимальная версия, которую можно указать, - C ++ 14, которая используется по умолчанию, начиная с Visual C ++ 2013 Update 3. Вы можете использовать старую версию VC ++ для таргетинга на C ++ 11, но тогда вам придется использовать другие компиляторы VC ++. для компиляции разных единиц перевода, предназначенных для разных версий стандарта C ++, что, по крайней мере, нарушит WPO.

CAVEAT: Мой ответ может быть не полным или очень точным.

Хади Брайс
источник
Вопрос действительно касался связывания, а не компиляции. Я понимаю (благодаря этому комментарию ), что это, возможно, было неясно, и отредактировал его, чтобы прояснить, что любые включенные заголовки имеют одинаковую интерпретацию во всех трех стандартах.
ricab 05
@ricab Ответ касается как компиляции, так и компоновки. Я думал, вы спрашиваете об обоих.
Хади Брайс 05
1
В самом деле, но я считаю, что ответ слишком длинный и запутанный, особенно до тех пор, пока «я не буду обсуждать совместимость на уровне компоновщика». Вы можете заменить все вышеперечисленное на что-то вроде того, что если нельзя постулировать, что включенные заголовки имеют одинаковое значение в C ++ 11 и C ++ 14/17, то включать их в первую очередь небезопасно . Что касается оставшейся части, есть ли у вас источник, показывающий, что эти три пункта являются единственными потенциальными причинами несовместимости? В любом случае спасибо за ответ, я все еще
голосую
@ricab точно не могу сказать. Вот почему я добавил предостережение в конце ответа. Любой другой может расширить ответ, чтобы сделать его более точным или полным, на случай, если я что-то пропустил.
Хади Брайс 05
Это меня смущает: «Использование одного и того же компилятора означает, что будут использоваться одинаковые заголовок стандартной библиотеки и исходные файлы (...)». Как это может быть? Если у меня есть старый код, скомпилированный с помощью gcc5, «файлы компилятора», принадлежащие этой версии, не могут быть надежными в будущем. Для исходного кода, скомпилированного (очень) в разное время с разными версиями компилятора, мы можем быть уверены, что заголовок библиотеки и исходные файлы отличаются. Согласно вашему правилу, что они должны быть одинаковыми, вы должны перекомпилировать старый исходный код с помощью gcc5, ... и убедиться, что все они используют последние (одинаковые) «файлы компилятора».
user2943111
2

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

Как вы подразумеваете под новым стандартом , изменения в самом языке (например, ранжированные) почти не вызывают проблем (иногда возникают конфликты в заголовках сторонних библиотек с новыми стандартными языковыми функциями).

Но стандартная библиотека ...

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

Конфликт, который может возникнуть между сторонними библиотеками и вашим кодом, - это стандартная библиотека (и другие библиотеки), которая связана с этими сторонними библиотеками.

Э. Вакили
источник
«Каждая версия компилятора поставляется с реализацией STL» Нет, они не делают
Lightness Races in Orbit
@LightnessRacesinOrbit Вы имеете в виду, что между libstdc ++ и gcc нет разделения?
Э. Вакили 05
9
Нет, я имею в виду, что STL устарел чуть больше двадцати лет. Вы имели в виду Стандартную библиотеку C ++. Что касается остального ответа, можете ли вы предоставить некоторые ссылки / доказательства в поддержку своего утверждения? Я думаю, что для такого вопроса это важно.
Гонки за легкостью на орбите
3
Извините, нет, это не ясно из текста. Вы сделали несколько интересных утверждений, но еще не подтвердили их никакими доказательствами.
Гонки за легкостью на орбите