web-dev-qa-db-ja.com

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

3つのコンパイル済みオブジェクトがあり、すべてが同じcompiler/versionによって生成されたと仮定します。

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

簡単にするために、すべてのヘッダーがC++ 11で記述されていると仮定しましょう。3つの標準バージョンの間でセマンティクスが変更されていない構造のみを使用します。相互依存関係はヘッダーを含めることで正しく表現され、コンパイラーは反対しませんでした。

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


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

63
ricab

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

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

どうして?それは私たちの実装の重要な特性であるため、私たちは確実に努力しています。

問題があるのは、異なるバージョンのGCCでコンパイルされたオブジェクトをリンクする場合ですおよび GCCのその標準のサポートが完了する前に、新しいC++標準の不安定な機能を使用しました。たとえば、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_で1つのオブジェクトをコンパイルし、GCC 8および_-std=c++17_で別のオブジェクトをコンパイルすると、GCC 7および8でのC++ 17サポートはまだ実験的で進化しているため、問題が発生します。

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

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

これは、使用される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、および使用されるバージョンにより、これらの部分はOKになります)。後でGCC 8と_-std=c++17_を使用して4番目のオブジェクトGをコンパイルしたい場合、FとGのC++ 17シンボルのため、Fを同じバージョン(またはFにリンクしない)で再コンパイルする必要があります。互換性がありません。

上記のD、E、Fの互換性に関する唯一の注意点は、プログラムでGCC 7以降の_libstdc++.so_共有ライブラリを使用する必要があることです。オブジェクトFはGCC 7でコンパイルされているため、そのリリースの共有ライブラリを使用する必要があります。GCC7でプログラムの一部をコンパイルすると、GCC 4.9の_libstdc++.so_に存在しないシンボルに依存する可能性があるためですGCC 5.同様に、GCC 8で構築されたオブジェクトGにリンクした場合、GCC 8の_libstdc++.so_を使用して、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も、C++ 11要件に準拠しているため、_cxx11_と呼ばれます)。どの実装がデフォルトであるかは、GCCがどのように構成されたかによって異なりますが、デフォルトはマクロを使用してコンパイル時にいつでもオーバーライドできます。

72
Jonathan Wakely

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

すべてのヘッダーがC++ 11で書かれていると仮定しましょう

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

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

これらはすべて、作成したヘッダーファイルにのみ仮定を適用できることを意味します。これらのヘッダーファイルは、さまざまな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:基本概念

Change:新しい通常の(非配置)デアロケーター

根拠:サイズの割り当て解除に必要です。

元の機能への影響:有効な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:宣言

Change:constexpr非静的メンバー関数は、暗黙的にconstメンバー関数ではありません。

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

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

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

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

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

Change:getsは定義されていません。

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

元の機能への影響: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++標準の異なるバージョン間でbinary互換性を有効にするように設計されています。ただし、これは実際には規格自体の要件ではありません。

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

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

11
Hadi Brais

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

新しい標準を意味するように、言語自体の変更(例:ranged-for)はほとんど問題ありません(新しい標準言語機能を備えたサードパーティライブラリヘッダーに競合が存在する場合があります)。

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

各コンパイラバージョンには、C++標準ライブラリ(gccを使用したlibstdc ++、clangを使用したlibc ++、VC++を使用したMS C++標準ライブラリなど)の実装と、各標準バージョンの実装は多くありません。また、場合によっては、コンパイラが提供するもの以外の標準ライブラリの他の実装を使用できます。気を付けるべきことは、古い標準ライブラリの実装を新しいものにリンクすることです。

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

2
E. Vakili