C++ 標準によると:
変換単位には、変数、関数、クラス型、列挙型、またはテンプレートの複数の定義が含まれてはなりません。
//--translation_unit.cpp--//
int a;
void foo()
{
int a; //Second defention of a. ODR fails.
}
ODR が実際にどのように機能するか説明してもらえますか?
C++ 標準によると:
変換単位には、変数、関数、クラス型、列挙型、またはテンプレートの複数の定義が含まれてはなりません。
//--translation_unit.cpp--//
int a;
void foo()
{
int a; //Second defention of a. ODR fails.
}
ODR が実際にどのように機能するか説明してもらえますか?
2 つの異なる変数を定義するため、これは規則に違反しません。それらは同じ名前を持っていますが、異なるスコープで宣言されているため、別のエンティティです。それぞれに 1 つの定義があります。
関数のスコープ内の宣言は、グローバル名前空間内の宣言を隠すと言われています。関数内では、非修飾名 a
はローカル変数を参照し、修飾名 ::a
はグローバルを参照します。
スコープが異なるため、ODR に違反しません。
1つ目a
はグローバルスコープです
グローバル スコープ (ファイル スコープとも呼ばれます) を持つ変数は、それが定義された時点以降、ファイル全体で認識されます。
2 番目a
にはローカル スコープがあります
ローカル スコープ (ブロック スコープとも呼ばれます) を持つ変数は、それが定義されているブロック内でのみ認識されます。
C++ の ODR についてより明確に理解するために、調査する必要がある概念は、ストレージ期間、スコープ、およびリンケージです。
ODR が実際にどのように機能するか説明してもらえますか?
ODR 違反の例を次に示します。
/* file : module.cpp */
#include <stdio.h>
inline int foo() {
printf("module.foo: %p\n", &foo);
return 1;
}
static int bar = foo();
/* file : main.cpp */
#include <stdio.h>
inline int foo() {
printf("main.foo: %p\n", &foo);
return 2;
}
int main(int argc, char *argv[]) {
return foo();
}
ご覧のとおり、関数int foo()
は 2 つのモジュールで異なる方法で定義されています。ここで、要求された最適化レベル ( O3と O0 )に応じて異なる動作がどのように生成されるかを観察します。
$ clang++ --std=c++11 -O0 main.cpp module.cpp && ./a.out
module.foo: 0x100a4aef0
module.foo: 0x100a4aef0
$ clang++ --std=c++11 -O3 main.cpp module.cpp && ./a.out
module.foo: 0x101146ee0
main.foo: 0x101146ee0
インライン関数の場合、コンパイラは各コンパイル モジュールでリンカー シンボルを生成するため、出力は異なります。このシンボル (各コンパイル モジュール内) は、「いずれかを選択してください。それらはすべて同じです」とマークされています。最初のケースでは、すべての最適化が無効になっている場合、リンカは module.cpp から定義を取得します。2 番目のケースでは、コンパイラは両方の関数をインライン化するだけなので、リンカーからの追加作業は必要ありません。
ODR に違反すると奇妙な動作が発生する例は他にもあります。だから、それをしないでください:)
PSボーナス、実生活からのケース:
/* a.cpp */
struct Rect
{
int x,y,w,h;
Rect(): x(0),y(0),w(0),h(0)
};
/* b.cpp */
struct Rect
{
double x,y,w,h;
Rect(): x(0),y(0),w(0),h(0)
};
ここでの問題は、前の例と同じです (Rect コンストラクターが暗黙的にインラインであるため)。Moon のフェーズに応じて、コンパイラはいずれかの実装を選択して奇妙な結果を生成していました (int
バージョンは初期化されていない部分を残しdoubles
、double
バージョンは s の外に出てint
そこでメモリを破壊します)。それを防ぐ良い方法は、匿名の名前空間を使用する (C++) か、構造体をstatic
(C) として宣言することです。
/* file.cpp */
namespace {
struct Rect { ... };
}
/* file.c */
static struct Rect { ... };
再度定義しませんでしa
た。
新しい variable を定義しましたa
。関数内でのみスコープを持ち、元のスコープ (グローバル スコープを持つ) とは関係なく、関数内で元のスコープをシャドウします。