C++
 Computer >> コンピューター >  >> プログラミング >> C++

C++プログラミングで注意したい代表的な未定義動作(UB)一覧

C++では、標準規格上「未定義動作(Undefined Behavior:UB)」と定められている操作が数多く存在します。未定義動作を引き起こした場合、コンパイラはどのようなコードを生成してもよいことになっており、プログラムのクラッシュ、誤動作、最適化による予期しない挙動など、深刻な問題につながる可能性があります。

ここでは、C++プログラミングにおいて特によく見られる未定義動作の原因をまとめます。いずれも標準規格によって未定義動作と明記されているものであり、コードを書く際には必ず回避するようにしましょう。

よくある未定義動作の原因

  • 符号付き整数のオーバーフロー
    intなどの符号付き整数型で表現範囲を超える演算を行った場合。unsigned整数のラップアラウンドとは異なり、こちらは未定義動作となります。
  • 無効なポインタの参照外し(デリファレンス)
    NULLポインタ、サイズ0のnew式が返すポインタ、確実に初期化されていないポインタ、配列の末尾より先の位置を指すポインタなどを逆参照すること。
  • 寿命が尽きたオブジェクトへのポインタの使用
    スコープを抜けて破棄されたオブジェクトや、deleteで解放済みのオブジェクトを指すポインタ(ダングリングポインタ)を使うこと。
  • 配列の境界外になるポインタ演算
    ポインタに対する加減算の結果が、配列の有効な範囲(末尾の1つ先まで)の外側になること。
  • 互換性のない型へのポインタ変換
    実際のオブジェクトの型と互換性のない型へポインタをキャストしてアクセスすること(厳密なエイリアシング違反など)。
  • 負のオフセットによるオブジェクト・配列へのアクセス
    オブジェクトや配列に対して、負の位置にある要素を読み書きすること。
  • コンストラクタ・デストラクタからの純粋仮想関数の呼び出し
    オブジェクトの構築中・破棄中に、そのクラス自身の純粋仮想関数を仮想呼び出しすること。
  • 戻り値のある関数でのreturn文の省略
    値を返すことが宣言されている関数で、値を返さずに関数を終了させること(main関数を除く)。

まとめ

未定義動作はコンパイルエラーにならないため気づきにくく、デバッグを非常に困難にします。さらに、コンパイラの最適化によって「たまたま動いていたコード」が突然壊れることもあります。サニタイザ(AddressSanitizer、UBSanなど)や静的解析ツールを活用し、これらの危険な操作を日頃から避けることを心がけましょう。

  1. C言語入門:ポインタ配列とポインタへのポインタ(二重ポインタ)の基本をわかりやすく解説

    はじめにC言語において、ポインタはメモリを直接扱える強力な機能です。本記事では、その応用形である「ポインタ配列」と「ポインタへのポインタ(二重ポインタ)」について、宣言方法・初期化方法・値へのアクセス方法を、サンプルコードと実行結果とともに順を追って解説します。 ポインタ配列とは通常の変数と同様に、ポインタも配列として宣言することができます。これを「ポインタ配列」と呼びます。各要素がそれぞれ別のアドレスを保持できるため、複数のデータを間接的に扱う際に便利です。 宣言方法データ型 *ポインタ名 [サイズ];例えば、次のように宣言します。int *p[5]; // int型の要素アドレスを5つ格納

  2. C言語のポインタ配列とは?宣言・初期化・アクセス方法を実例付きで解説

    ポインタとは、別の変数のアドレス(メモリ上の場所)を格納するための変数です。C言語においてポインタは非常に重要な概念であり、メモリを直接操作することで効率的なプログラムを作成できます。 ポインタの特徴 メモリ空間を節約できる メモリ位置へ直接アクセスするため、実行速度が速い メモリを効率的に利用でき、動的な割り当て(確保)と解放が可能 リンクリストやツリーなどのデータ構造と組み合わせて使用できる ポインタの宣言と初期化 まず、次のような文を考えてみましょう。 int qty = 179; このとき、変数はメモリ上に値「179」とそのアドレスを持つ形で表現されます。 ポインタの宣言 ポ