C++プログラマが知っておくべき一般的な未定義の動作は何ですか?
次のように言います:
a[i] = i++;
NULL
ポインターの逆参照memcpy
を使用して重複するバッファーをコピーする 。int64_t i = 1; i <<= 72
は未定義です)int i; i++; cout << i;
)volatile
またはsig_atomic_t
以外のタイプのオブジェクトの値を使用するlong int
で表現できないプリプロセッサ数値#if
式で定義済みトークンを動的に生成する関数パラメーターが評価される順序はunspecifiedbehaviorです。 (これにより、プログラムがクラッシュしたり、爆発したり、ピザが注文されたりすることはありません... undefined動作とは異なります。)
唯一の要件は、関数が呼び出される前にすべてのパラメーターを完全に評価する必要があることです。
この:
// 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.
const_cast<>
を使用してconst
nessを除去した後に定数に割り当てる:
const int i = 10;
int *p = const_cast<int*>( &i );
*p = 1234; //Undefined
私のお気に入りは「テンプレートのインスタンス化における無限再帰」です。コンパイル時に未定義の動作が発生するのはそれだけだと思うからです。
undefined behaviourの他に、同様に厄介なimplementation-defined behaviourもあります。
未定義の動作は、プログラムがその結果が標準で指定されていない何かを行うときに発生します。
実装定義の動作とは、結果が標準で定義されていないが、実装が文書化するために必要なプログラムによるアクションです。例は、スタックオーバーフローの質問からの「マルチバイト文字リテラル」です これをコンパイルできないCコンパイラはありますか?。
実装定義の動作は、移植を開始したときにのみ噛みつきます(ただし、コンパイラの新しいバージョンへのアップグレードも移植されます!)
変数は、式で1回のみ(技術的にはシーケンスポイント間で1回)更新できます。
int i =1;
i = ++i;
// Undefined. Assignment to 'i' twice in the same expression.
さまざまな環境制限の基本的な理解。完全なリストは、C仕様のセクション5.2.4.1にあります。ここにいくつかあります。
Switchステートメントの1023個のcaseラベルの制限に実際には少し驚いていました。生成されたコード/ Lex /パーサーの超過はかなり簡単に予測できます。
これらの制限を超えると、未定義の動作(クラッシュ、セキュリティ上の欠陥など)が発生します。
確かに、これはC仕様によるものですが、C++はこれらの基本的なサポートを共有しています。
異なるコンパイル単位の名前空間レベルのオブジェクトは、初期化の順序が定義されていないため、初期化に関して相互に依存しないでください。
C++がサイズを保証する唯一のタイプはchar
です。サイズは1です。他のすべてのタイプのサイズはプラットフォームに依存します。
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が指すオブジェクトからs1が指すオブジェクトにn文字をコピーします。重複するオブジェクト間でコピーが行われる場合、動作は未定義です。戻り値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の値を返します。