201

C ++プログラマーが知っておくべき一般的な未定義の動作は何ですか?

言う、のように:

a[i] = i++;

4

11 に答える 11

233

ポインター

  • NULLポインターの逆参照
  • サイズ 0 の「新しい」割り当てによって返されたポインタの逆参照
  • 有効期間が終了したオブジェクトへのポインターの使用 (たとえば、割り当てられたオブジェクトまたは削除されたオブジェクトのスタック)
  • まだ確実に初期化されていないポインターの逆参照
  • 配列の境界外 (上または下) で結果を生成するポインター演算を実行する。
  • 配列の末尾を超えた場所でポインターを逆参照する。
  • 互換性のない型のオブジェクトへのポインターの変換
  • memcpy重複するバッファをコピーするために使用します。

バッファオーバーフロー

  • 負のオフセット、またはそのオブジェクトのサイズを超えるオフセットでのオブジェクトまたは配列の読み取りまたは書き込み (スタック/ヒープ オーバーフロー)

整数オーバーフロー

  • 符号付き整数のオーバーフロー
  • 数学的に定義されていない式の評価
  • 負の値による左シフト (負の量による右シフトは実装定義)
  • 数値のビット数以上の量だけ値をシフトする (例:int64_t i = 1; i <<= 72未定義)

型、キャスト、および定数

  • 対象の型で表現できない値に数値をキャストする (直接または static_cast を介して)
  • 確実に割り当てられる前に自動変数を使用する (例: int i; i++; cout << i;)
  • シグナルの受信時以外のタイプのオブジェクトの値を使用volatileするsig_atomic_t
  • 有効期間中に文字列リテラルまたはその他の const オブジェクトを変更しようとする
  • 前処理中に狭い文字列リテラルと広い文字列リテラルを連結する

関数とテンプレート

  • 値を返す関数から値を返さない (直接または try ブロックから流れ出すことによって)
  • 同じエンティティに対する複数の異なる定義 (クラス、テンプレート、列挙、インライン関数、静的メンバー関数など)
  • テンプレートのインスタンス化における無限再帰
  • 異なるパラメーターまたはパラメーターへのリンケージを使用して関数を呼び出し、関数が使用するように定義されているリンケージ。

OOP

  • 静的保存期間を持つオブジェクトのカスケード破壊
  • 部分的に重なっているオブジェクトに割り当てた結果
  • 静的オブジェクトの初期化中に関数を再帰的に再入力する
  • コンストラクタまたはデストラクタからオブジェクトの純粋仮想関数への仮想関数呼び出しを行う
  • 構築されていないか、すでに破棄されているオブジェクトの非静的メンバーの参照

ソースファイルと前処理

  • 改行で終わらない、またはバックスラッシュで終わる空でないソース ファイル (C++11 より前)
  • バックスラッシュの後に、文字定数または文字列定数で指定されたエスケープ コードの一部ではない文字が続きます (これは C++11 では実装定義です)。
  • 実装制限の超過 (ネストされたブロックの数、プログラム内の関数の数、使用可能なスタック スペースなど)
  • で表現できないプリプロセッサの数値long int
  • 関数のようなマクロ定義の左側の前処理ディレクティブ
  • #if式で定義されたトークンを動的に生成する

分類する

  • 静的ストレージ期間を持つプログラムの破棄中に exit を呼び出す
于 2008-12-15T07:15:24.583 に答える
31

関数パラメータが評価される順序は未規定の動作です。(これにより、プログラムがクラッシュしたり、爆発したり、ピザを注文したりすることはありません...未定義の動作とは異なります。)

唯一の要件は、関数を呼び出す前にすべてのパラメーターを完全に評価する必要があることです。


これ:

// 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);

どちらでもかまいません。それはコンパイラ次第です。副作用によっては、結果が重要になる場合があります。

于 2008-12-15T07:15:46.063 に答える
27

コンパイラは、式の評価部分を自由に並べ替えることができます (意味が変更されていないと仮定します)。

元の質問から:

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.
于 2008-12-15T07:35:13.420 に答える
5

未定義の動作に加えて、同様に厄介な実装定義の動作もあります。

未定義の動作は、プログラムが何かを実行したときに、その結​​果が標準で指定されていない場合に発生します。

実装定義の動作は、結果が標準によって定義されていないが、実装が文書化する必要があるプログラムによるアクションです。例は、スタック オーバーフローの質問からの「マルチバイト文字リテラル」です。 これをコンパイルできない C コンパイラはありますか? .

実装で定義された動作は、移植を開始するときにのみ噛み付きます (ただし、新しいバージョンのコンパイラへのアップグレードも移植です!)

于 2008-12-15T14:49:10.440 に答える
5

constを使用してネスを削除した後、定数に代入しconst_cast<>ます。

const int i = 10; 
int *p =  const_cast<int*>( &i );
*p = 1234; //Undefined
于 2008-12-15T07:36:48.953 に答える
5

私のお気に入りは「テンプレートのインスタンス化における無限再帰」です。コンパイル時に未定義の動作が発生するのはこれだけだと思う​​からです。

于 2008-12-15T08:17:32.243 に答える
4

変数は、1 つの式で 1 ​​回だけ更新できます (技術的には、シーケンス ポイント間で 1 回)。

int i =1;
i = ++i;

// Undefined. Assignment to 'i' twice in the same expression.
于 2008-12-15T07:22:53.133 に答える
3

さまざまな環境制限の基本的な理解。完全なリストは、C 仕様のセクション 5.2.4.1 にあります。ここにいくつかあります。

  • 1 つの関数定義で 127 個のパラメーター
  • 1 回の関数呼び出しで 127 個の引数
  • 1 つのマクロ定義で 127 個のパラメータ
  • 1 回のマクロ呼び出しで 127 個の引数
  • 論理ソース行に 4095 文字
  • 文字列リテラルまたはワイド文字列リテラルで4095文字(連結後)
  • オブジェクトで 65535 バイト (ホスト環境のみ)
  • #includedfiles の 15 ネスト レベル
  • switch ステートメントの 1023 個の case ラベル (ネストされた switch ステートメントのラベルを除く)

実際、switch ステートメントの case ラベルが 1023 という制限に少し驚きました。生成されたコード/lex/パーサーがかなり簡単に超えていることが予測できます。

これらの制限を超えると、未定義の動作 (クラッシュ、セキュリティ上の欠陥など) が発生します。

そうですね、これが C 仕様によるものであることは知っていますが、C++ はこれらの基本的なサポートを共有しています。

于 2011-03-19T05:03:00.623 に答える
2

C++ がサイズを保証する唯一の型はchar. サイズは 1 です。他のすべてのタイプのサイズは、プラットフォームに依存します。

于 2008-12-15T07:06:09.873 に答える
2

memcpy重複するメモリ領域間でコピーするために使用します。例えば:

char a[256] = {};
memcpy(a, a, sizeof(a));

この動作は、C++03 標準に組み込まれている C 標準に従って定義されていません。

7.21.2.1 memcpy 関数

あらすじ

1/ #include void *memcpy(void * restrict s1, const void * restrict s2, size_t n);

説明

2/ memcpy 関数は、s2 が指すオブジェクトから s1 が指すオブジェクトに n 文字をコピーします。重複するオブジェクト間でコピーが行われる場合、動作は未定義です。戻り値 3 memcpy 関数は s1 の値を返します。

7.21.2.2 memmove 関数

あらすじ

1 #include void *memmove(void *s1, const void *s2, size_t n);

説明

2 memmove 関数は、s2 が指すオブジェクトから s1 が指すオブジェクトに n 文字をコピーします。コピーは、s2 が指すオブジェクトの n 文字が最初に、s1 および s2 が指すオブジェクトと重複しない n 文字の一時配列にコピーされ、次に一時配列の n 文字がコピーされるかのように行われます。 s1 が指すオブジェクト。戻り値

3 memmove 関数は s1 の値を返します。

于 2012-06-26T16:10:25.950 に答える
2

異なるコンパイル単位の名前空間レベルのオブジェクトは、初期化順序が定義されていないため、初期化のために相互に依存するべきではありません。

于 2009-12-22T05:28:34.973 に答える