C ++プログラマが知っておくべき一般的な未定義の動作は何ですか?
次のように言います:
a[i] = i++;
C ++プログラマが知っておくべき一般的な未定義の動作は何ですか?
次のように言います:
a[i] = i++;
回答:
NULL
ポインターの逆参照memcpy
重複するバッファをコピーするために使用します。int64_t i = 1; i <<= 72
未定義)int i; i++; cout << i;
)volatile
またはsig_atomic_t
シグナルの受信時のタイプのオブジェクトの値の使用long int
#if
式で定義されたトークンを動的に生成する関数パラメーターが評価される順序は、不特定の動作です。(これにより、プログラムがクラッシュしたり、爆発したり、ピザを注文したりすることはありません。未定義の動作とは異なります。)
唯一の要件は、関数を呼び出す前にすべてのパラメーターを完全に評価する必要があることです。
この:
// The simple obvious one.
callFunc(getA(),getB());
これと同等にすることができます:
int a = getA();
int b = getB();
callFunc(a,b);
またはこれ:
int b = getB();
int a = getA();
callFunc(a,b);
それはどちらかです。それはコンパイラ次第です。副作用によっては、結果が重要になる場合があります。
コンパイラーは、式の評価部分を自由に並べ替えることができます(意味が変更されていない場合)。
元の質問から:
a[i] = i++;
// This expression has three parts:
(a) a[i]
(b) i++
(c) Assign (b) to (a)
// (c) is guaranteed to happen after (a) and (b)
// But (a) and (b) can be done in either order.
// See n2521 Section 5.17
// (b) increments i but returns the original value.
// See n2521 Section 5.2.6
// Thus this expression can be written as:
int rhs = i++;
int lhs& = a[i];
lhs = rhs;
// or
int lhs& = a[i];
int rhs = i++;
lhs = rhs;
ダブルチェックロック。そして、1つの簡単な間違いを犯します。
A* a = new A("plop");
// Looks simple enough.
// But this can be split into three parts.
(a) allocate Memory
(b) Call constructor
(c) Assign value to 'a'
// No problem here:
// The compiler is allowed to do this:
(a) allocate Memory
(c) Assign value to 'a'
(b) Call constructor.
// This is because the whole thing is between two sequence points.
// So what is the big deal.
// Simple Double checked lock. (I know there are many other problems with this).
if (a == null) // (Point B)
{
Lock lock(mutex);
if (a == null)
{
a = new A("Plop"); // (Point A).
}
}
a->doStuff();
// Think of this situation.
// Thread 1: Reaches point A. Executes (a)(c)
// Thread 1: Is about to do (b) and gets unscheduled.
// Thread 2: Reaches point B. It can now skip the if block
// Remember (c) has been done thus 'a' is not NULL.
// But the memory has not been initialized.
// Thread 2 now executes doStuff() on an uninitialized variable.
// The solution to this problem is to move the assignment of 'a'
// To the other side of the sequence point.
if (a == null) // (Point B)
{
Lock lock(mutex);
if (a == null)
{
A* tmp = new A("Plop"); // (Point A).
a = tmp;
}
}
a->doStuff();
// Of course there are still other problems because of C++ support for
// threads. But hopefully these are addresses in the next standard.
私のお気に入りは「テンプレートのインスタンス化における無限再帰」です。これは、コンパイル時に未定義の動作が発生するのはこれだけだと私は信じているからです。
未定義の動作に加えて、同様に厄介な実装定義の動作もあります。
未定義の動作は、プログラムが標準で指定されていない結果を実行した場合に発生します。
実装定義の動作は、その結果が標準で定義されていないが実装が文書化する必要があるプログラムによるアクションです。例は、スタックオーバーフローの質問からの「マルチバイト文字リテラル」です。これをコンパイルできないCコンパイラはありますか?。
実装で定義された動作は、移植を開始したときのみあなたを噛みます(ただし、新しいバージョンのコンパイラへのアップグレードも移植されます!)
変数は、式で1回だけ更新できます(技術的には、シーケンスポイント間で1回)。
int i =1;
i = ++i;
// Undefined. Assignment to 'i' twice in the same expression.
さまざまな環境制限の基本的な理解。完全なリストは、C仕様のセクション5.2.4.1にあります。ここにいくつかあります。
実際には、switchステートメントのケースラベルが1023の制限に少し驚いていましたが、生成されたコード/ lex /パーサーがかなり簡単に超えられることを予測できます。
これらの制限を超えると、動作が未定義になります(クラッシュ、セキュリティ上の欠陥など)。
そうです、これはC仕様によるものですが、C ++はこれらの基本的なサポートを共有しています。
memcpy
重複するメモリ領域間でコピーするために使用します。例えば:
char a[256] = {};
memcpy(a, a, sizeof(a));
この動作は、C ++ 03標準に含まれるC標準に従って定義されていません。
あらすじ
1 / #include void * memcpy(void * restrict s1、const void * restrict s2、size_t n);
説明文
2 / memcpy関数は、s2が指すオブジェクトからn文字をs1が指すオブジェクトにコピーします。重複するオブジェクト間でコピーが行われた場合の動作は未定義です。戻り値3 memcpy関数はs1の値を返します。
あらすじ
1 #include void * memmove(void * s1、const void * s2、size_t n);
説明文
2 memmove関数は、s2が指すオブジェクトからn文字をs1が指すオブジェクトにコピーします。コピーは、s2が指すオブジェクトのn文字が、s1およびs2が指すオブジェクトと重ならないn文字の一時配列に最初にコピーされた後、一時配列のn文字がs1が指すオブジェクト。戻り値
3 memmove関数はs1の値を返します。
C ++がサイズを保証する唯一のタイプはchar
です。そしてサイズは1です。他のすべてのタイプのサイズはプラットフォームに依存します。