C ++ 17、C ++ 14、およびC ++ 11オブジェクトをリンクしても安全ですか


97

3つのコンパイル済みオブジェクトがあり、すべて同じコンパイラ/バージョンで生成されているとします。

  1. AはC ++ 11標準でコンパイルされました
  2. BはC ++ 14標準でコンパイルされました
  3. CはC ++ 17標準でコンパイルされました

簡単にするために、すべてのヘッダーがC ++ 11で記述されており、3つの標準バージョン間でセマンティクスが変更されていない構成体のみを使用しているため、相互依存関係がヘッダーインクルードで正しく表現され、コンパイラーが異議を唱えなかったとします。

これらのオブジェクトのどの組み合わせがそれであり、単一のバイナリにリンクするのは安全ではありませんか?どうして?


編集:主要なコンパイラ(gcc、clang、vs ++など)に関する回答を歓迎します


6
学校や面接の質問ではありません。問題は特定のケースに由来します。私はオープンソースライブラリに依存するプロジェクトに取り組んでいます。このライブラリをソースからビルドしますが、そのビルドシステムは、C ++ 03 / C ++ 11ビルドから選択するフラグのみを受け入れます。私が使用しているコンパイラは他の標準をサポートしていますが、自分のプロジェクトをC ++ 17にアップグレードすることを検討しています。それが安全な決定かどうかはわかりません。ABIの中断、またはこのアプローチが推奨されないその他の方法がある可能性はありますか?明確な答えが見つからなかったため、一般的なケースについて質問を投稿することにしました。
ricab

6
これはコンパイラに完全に依存します。この状況を管理する正式なC ++仕様には何もありません。また、C ++ 03またはC + 11標準で記述されたコードには、C ++ 14およびC ++ 17レベルでいくつかの問題が発生する可能性があります。十分な知識と経験(および最初から適切に記述されたコード)があれば、これらの問題を修正することが可能です。ただし、新しいC ++標準に精通していない場合は、ビルドシステムがサポートしているものを使用することをお勧めします。
Sam Varshavchik 2017年

9
@Someprogrammerdude:これは非常に価値のある質問です。答えがあったらいいのに。RHEL devtoolsetを介したlibstdc ++は、新しいものを静的にリンクし、古いものをディストリビューションの「ネイティブ」のlibstdc ++を使用して実行時に動的に解決することにより、後方互換性があることを知っています。しかし、それは質問の答えにはなりません。
オービットのライトネスレース

3
@nm:...ほとんどの場合...ディストリビューションに依存しないC ++ライブラリを配布するほとんどすべての人が(1)動的ライブラリ形式で(2)インターフェイス境界にC ++標準ライブラリコンテナなしでそうします。Linuxディストリビューションのライブラリは、すべて同じコンパイラ、同じ標準ライブラリ、ほとんど同じデフォルトのフラグセットで構築されているため、簡単です。
Matteo Italia

3
@MatteoItaliaからの以前のコメントを明確にするために、「C ++ 03からC ++ 11モードに切り替える場合(特にstd :: string)」これは真実ではありませんstd::string。libstdc++のアクティブな実装は、-std使用されるモードに依存しません。これは、OPのような状況を正確にサポートするための重要なプロパティです。std::stringC ++ 03コードでは新しいコードを使用できstd::string、C ++ 11コードでは古いコードを使用できます(Matteoのコメントのリンクを参照)。
ジョナサンウェイクリー2018年

回答:


116

これらのオブジェクトのどの組み合わせがそれであり、単一のバイナリにリンクするのは安全ではありませんか?どうして?

GCCの場合、オブジェクトA、B、Cの任意の組み合わせをリンクしても安全です。それらがすべて同じバージョンでビルドされ、ABI互換である場合、標準バージョン(つまり、-stdオプション)は違いを生じません。

どうして?それは、実装の重要な特性であり、確保するために私たちが懸命に取り組んでいるためです。

問題があるのは、異なるバージョンのGCCでコンパイルされたオブジェクトをリンクすると、し、GCCによるその標準のサポートが完了する前に新しいC ++標準の不安定な機能を使用場合です。たとえば、GCC 4.9を使用してオブジェクトをコンパイルし、-std=c++11GCC 5 を使用して別のオブジェクトをコンパイルすると、-std=c++11問題が発生します。C ++ 11のサポートはGCC 4.xで試験的に行われたため、C ++ 11機能のGCC 4.9バージョンと5バージョンの間には互換性のない変更がありました。同様に、GCC 7で1つのオブジェクトをコンパイル-std=c++17し、GCC 8で別のオブジェクトをコンパイルした場合-std=c++17、GCC 7および8でのC ++ 17サポートはまだ実験的で進化しているため、問題が発生します。

一方、次のオブジェクトの任意の組み合わせが機能します(ただし、libstdc++.soバージョンについては下記の注を参照してください)。

  • GCC 4.9でコンパイルされたオブジェクトDおよび -std=c++03
  • GCC 5でコンパイルされたオブジェクトEおよび -std=c++11
  • GCC 7でコンパイルされたオブジェクトFおよび -std=c++17

これは、使用されている3つのコンパイラバージョンすべてでC ++ 03サポートが安定しているため、C ++ 03コンポーネントはすべてのオブジェクト間で互換性があるためです。C ++ 11サポートはGCC 5以降は安定していますが、オブジェクトDはC ++ 11機能を使用せず、オブジェクトEとFはどちらもC ++ 11サポートが安定しているバージョンを使用します。C ++ 17のサポートは、使用されているどのコンパイラバージョンでも安定していませんが、オブジェクトFのみがC ++ 17機能を使用しているため、他の2つのオブジェクトとの互換性の問題はありません(共有する機能はC ++ 03のみです)またはC ++ 11、および使用されているバージョンにより、これらのパーツは正常に機能します)。後でGCC 8を使用して4番目のオブジェクトGをコンパイルする場合、-std=c++17FとGのC ++ 17シンボルに互換性がないため、Fを同じバージョンで再コンパイルする(またはFにリンクしない)必要があります。

上記のD、E、F間の互換性に関する唯一の注意点は、プログラムがlibstdc++.soGCC 7以降の共有ライブラリを使用する必要があることです。オブジェクトFはGCC 7でコンパイルされたので、そのリリースの共有ライブラリを使用する必要があります。GCC7でプログラムの一部をコンパイルすると、libstdc++.soGCC 4.9またはGCC 5に存在しないシンボルへの依存関係が導入される可能性があるためです。 GCC 8で構築されたオブジェクトGにリンクする場合は、libstdc++.so、GCC 8 from Gが必要とするすべてのシンボルを確実に検出する必要があります。単純なルールは、プログラムが実行時に使用する共有ライブラリが、少なくともオブジェクトのコンパイルに使用されるバージョンと同じくらい新しいことを確認することです。

GCCを使用する際のもう1つの注意点は、質問のコメントですでに述べたように、GCC 5以降、libstdc ++ std::string使用できる実装2つあることです。2つの実装はリンク互換ではありません(マングル名が異なるため、一緒にリンクできません)が、同じバイナリ内で共存できます(マングル名が異なるため、1つのオブジェクトstd::stringがその他の用途std::__cxx11::string)。オブジェクトが使用する場合std::string、通常はすべて同じ文字列実装でコンパイルする必要があります。でコンパイルし-D_GLIBCXX_USE_CXX11_ABI=0て元のgcc4-compatible実装-D_GLIBCXX_USE_CXX11_ABI=1を選択するか、新しい実装を選択しますcxx11実装(名前に騙されないでください。C++ 03でも使用できます。cxx11C ++ 11要件に準拠しているため)。デフォルトの実装は、GCCの構成方法によって異なりますが、デフォルトでは、マクロを使用してコンパイル時に常にオーバーライドできます。


「GCC 7でプログラムのいずれかの部分をコンパイルすると、GCC 4.9またはGCC 5のlibstdc ++。soに存在するシンボルに依存関係が生じる可能性があるため」、GCC 4.9またはGCC 5には存在しないということですか?これは静的リンクにも適用されますか?コンパイラのバージョン間の互換性に関する情報をありがとう。
Hadi Brais

1
私はこの質問に賞金を提供することに大きな欠陥があることに気づきました。😂–オービットのライトネス
レース

4
@ricab答えはClang / libc ++でも同じだと90%確信していますが、MSVCについてはわかりません。
ジョナサンウェイクリー2018年

1
この答えは素晴らしいです。5.0+が11/14で安定しているとどこかで文書化されていますか?
バリー

1
あまり明確ではないか、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までまだ試験段階としてリストされていますが、実際には、ABIに影響する5.xと6.xの間で何も変更されていないと思います。
Jonathan Wakely、2018年

16

答えには2つの部分があります。コンパイラーレベルでの互換性とリンカーレベルでの互換性。前者から始めましょう。

すべてのヘッダーがC ++ 11で記述されているとしましょう

同じコンパイラを使用すると、ターゲットのC ++標準に関係なく、同じ標準ライブラリヘッダーとソースファイル(コンパイラに関連付けられた1回)が使用されます。したがって、標準ライブラリのヘッダーファイルは、コンパイラがサポートするすべてのC ++バージョンと互換性があるように記述されています。

つまり、翻訳単位のコンパイルに使用されるコンパイラオプションが特定のC ++標準を指定している場合、新しい標準でのみ使用できる機能にはアクセスできません。これは__cplusplusディレクティブを使用して行われます。ベクトルを見る使い方の興味深い例ソースファイルを。同様に、コンパイラーは、新しいバージョンの標準によって提供される構文機能を拒否します。

つまり、想定は、記述したヘッダーファイルにのみ適用されるということです。これらのヘッダーファイルは、異なるC ++標準を対象とする異なる翻訳単位に含まれている場合、非互換性を引き起こす可能性があります。これについては、C ++標準の付録Cで説明されています。4つの条項があります。最初の条項についてのみ説明し、残りは簡単に説明します。

C.3.1条項2:字句の規則

単一引用符は、C ++ 11では文字リテラルを区切りますが、C ++ 14およびC ++ 17では数字の区切り文字です。純粋なC ++ 11ヘッダーファイルの1つに次のマクロ定義があるとします。

#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をターゲットとする2つの変換単位を検討します。C ++ 11を対象とする場合、引用符内のコンマはパラメーターの区切り文字とは見なされません。パラメータは1つだけです。したがって、コードは次と同等になります。

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

一方、C ++ 14を対象とする場合、一重引用符は桁区切り記号として解釈されます。したがって、コードは次と同等になります。

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

ここでのポイントは、純粋なC ++ 11ヘッダーファイルの1つで一重引用符を使用すると、C ++ 14/17をターゲットとする翻訳単位に驚くべきバグが発生する可能性があるということです。したがって、ヘッダーファイルがC ++ 11で記述されている場合でも、それが標準の新しいバージョンと互換性があることを保証するために、慎重に記述する必要があります。の__cplusplusディレクティブは、ここでは有用である可能性があります。

標準の他の3つの条項は次のとおりです。

C.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非静的メンバー関数は暗黙的にconstメンバー関数ではありません。

根拠:constexprメンバー関数がオブジェクトを変更できるようにするために必要です。

元の機能への影響:有効なC ++ 2011コードは、この国際標準でコンパイルできない場合があります。

たとえば、次のコードはC ++ 2011では有効ですが、同じ国際標準では無効です。これは、同じメンバー関数を異なる戻り値の型で2回宣言しているためです。

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

C.3.4節27:入出力ライブラリ

変更:取得が定義されていません。

根拠:getの使用は危険と見なされます。

元の機能への影響:この国際規格では、gets関数を使用する有効なC ++ 2011コードはコンパイルに失敗する場合があります。

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を参照)はすべてのC ++標準で同じであるため、どの翻訳単位が存在するかは関係ありません。

一般に、WPOは、異なるC ++標準を使用してコンパイルされたオブジェクトファイルではうまく機能しない可能性があります。これは、コンパイラのどのステージがターゲット標準の知識を必要とするか、どのステージが必要としないか、およびオブジェクトファイルをまたぐプロシージャ間の最適化に与える影響に正確に依存します。幸い、GCC、LLVM、VC ++は適切に設計されており、この問題はありません(私が認識していることではありません)。

したがって、GCC、LLVM、およびVC ++は、C ++標準の異なるバージョン間でのバイナリ互換性を可能にするように設計されています。ただし、これは規格自体の要件ではありません。

ちなみに、VC ++コンパイラは、C ++標準の特定のバージョンをターゲットにできるstdスイッチを提供していますが、C ++ 11のターゲットはサポートしていません。指定できる最小バージョンはC ++ 14です。これは、Visual C ++ 2013 Update 3以降のデフォルトです。古いバージョンのVC ++を使用してC ++ 11をターゲットにすることもできますが、別のVC ++コンパイラを使用する必要があります。 C ++標準のさまざまなバージョンを対象とするさまざまな翻訳単位をコンパイルします。これにより、少なくともWPOが機能しなくなります。

警告:私の答えは完全ではないか、非常に正確ではないかもしれません。


この質問は、コンパイルではなくリンクに関するものでした。私は(このコメントのおかげで)おそらく明確ではないことを認識し、含まれているヘッダーが3つの標準すべてで同じ解釈を持つことを明確にするために編集しました。
ricab 2018年

@ricab答えはコンパイルとリンクの両方をカバーしています。あなたは両方について尋ねていると思いました。
Hadi Brais

1
確かに、しかし、特に「今からリンカーレベルでの互換性について説明する」までは、答えが長すぎて混乱することに気づきました。含まれているヘッダーがC ++ 11とC ++ 14/17で同じ意味を持つと仮定できない場合は、上記のすべてのものを置き換えることができます。最初にそれらを含めるのは安全ではありません。残りの部分について、これら3つの箇条書きが非互換性の唯一の潜在的な理由であることを示す情報源はありますか?とにかく答えてくれてありがとう、私はまだ投票しています
ricab

@ricab確かに言えない。そのため、回答の最後に警告を追加しました。私が何かを見逃した場合に備えて、他の誰でも答えを拡張して、より正確または完全にすることができます。
Hadi Brais

これは私を混乱させます:「同じコンパイラを使用すると、同じ標準ライブラリヘッダーとソースファイル(...)が使用されることになります」。どうしてそうなのでしょうか?古いコードをgcc5でコンパイルした場合、そのバージョンに属していた「コンパイラファイル」は将来の保証にはなりません。さまざまなコンパイラバージョンを使用して(乱暴に)さまざまなタイミングでコンパイルされたソースコードの場合、ライブラリヘッダーとソースファイルが異なっていることを確認できます。これらが同じであるべきであるというあなたのルールで、あなたはgcc5で古いソースコードを再コンパイルする必要があります...そして、それらすべてが最新の(同じ)「コンパイラファイル」を使用することを確認してください。
user2943111

2

新しいC ++標準には、言語機能と標準ライブラリコンポーネントの2つの部分があります。

あなたがで平均として新しい標準、(例えば遠隔-のために)言語自体に変更は問題はほとんどありません(時には競合は、新しい標準言語機能を持つサードパーティのライブラリヘッダ内に存在しています)。

しかし、標準ライブラリ...

各コンパイラバージョンには、C ++標準ライブラリの実装(gccを使用したlibstdc ++、clangを使用したlibc ++、VC ++を使用したMS C ++標準ライブラリなど)と1つの実装が含まれますが、各標準バージョンの実装は多くありません。また、提供されているコンパイラー以外の標準ライブラリーの実装を使用する場合もあります。注意する必要があるのは、古い標準ライブラリの実装を新しいものにリンクすることです。

サードパーティライブラリとコードの間で発生する可能性のある競合は、そのサードパーティライブラリにリンクする標準ライブラリ(および他のライブラリ)です。


「各コンパイラバージョンにはSTLの実装が付属していますいいえ、ありません
オービットのライトネスレース

@LightnessRacesinOrbitたとえば、libstdc ++とgccの間に関係がないということですか?
E.ヴァキリ2018年

8
いいえ、つまり、STLは20年余りの間、実質的に廃止されています。あなたはC ++標準ライブラリを意味しました。残りの回答については、あなたの主張を裏付けるいくつかの参照/証拠を提供できますか?このような質問には、それが重要だと思います。
オービットのライトネスレース

3
申し訳ありませんが、テキストからははっきりしていません。あなたはいくつかの興味深い主張をしましたが、まだ証拠を裏付けていません。
オービットのライトネスレース
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.