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

C++プログラマーが知っておくべき代表的な未定義動作(UB)とは

C++には「未定義動作(Undefined Behavior:UB)」と呼ばれる特殊な挙動が存在します。これは、ある操作を実行した際に、言語仕様上その結果が一切保証されない状態を指します。コンパイル時にはエラーとして検出されないため見落としがちですが、実行時に予期せぬクラッシュや誤動作を引き起こす重大なバグの温床となります。C++をさまざまな用途で活用したいプログラマーなら、誰もがこれらの未定義動作を把握しておくべきです。

この記事では、典型的な未定義動作を含むC++コードをいくつか取り上げ、実際にどのような結果になるのかを確認していきます。多くの場合、これらのコードは実行時エラー(ランタイムエラー)を発生させます。

1. 整数のゼロ除算

整数値を0で割る操作は未定義動作です。

サンプルコード

#include <iostream>
using namespace std;

int main() {
    int x = 10, y = 0;
    int z = x / y; // ゼロ除算
    cout << "Done" << endl;
}

出力結果

ゼロ除算によるランタイムエラー

2. 未初期化変数の使用

初期化していない変数を読み取ろうとするのも未定義動作です。変数の値は不定であり、trueにもfalseにもなり得ます。

サンプルコード

#include <iostream>
using namespace std;

int main() {
    bool x; // 初期化されていない
    if(x == true)
        cout << "true value";
    else
        cout << "false value";
}

出力結果

false value(コンパイラによって結果は異なる可能性があります)

3. NULLポインタの参照外し

有効なアドレスを指していないポインタ(NULLポインタ)が指す先の値にアクセスしようとすると、未定義動作となります。

サンプルコード

#include <iostream>
using namespace std;

int main() {
    int *ptr = nullptr;
    cout << "The pointer value is: " << *ptr;
}

出力結果

NULLポインタへのアクセスによるランタイムエラー

4. 配列の範囲外アクセス

配列の要素数を超えてアクセスするのも未定義動作です。エラーにならず、無関係なメモリ領域の値が返されることもあります。

サンプルコード

#include <iostream>
using namespace std;

int main() {
    int array[10];
    for(int i = 0; i <= 10; i++) { // 範囲外までアクセス
        cout << array[i] << endl;
    }
}

出力結果

範囲外アクセスによるランタイムエラー。
ただし、コンパイラによってはエラーを返さず、任意のゴミ値を出力する場合もあります。

5. 符号付き整数のオーバーフロー

int型などの符号付き整数が表現できる最大値(INT_MAX)を超えると、未定義動作となります。多くの環境では最小値に折り返されますが、これは標準で保証された挙動ではありません。

サンプルコード

#include <iostream>
#include <climits>
using namespace std;

int main() {
    int x = INT_MAX;
    cout << "x + 1: " << x + 1;
}

出力結果

x + 1: -2147483648
(符号付きintの最小値へ折り返される例。ただし未定義動作のため保証はない)

6. 文字列リテラルの書き換え

"Hello World" のような文字列リテラルは読み取り専用領域に配置されるため、内容を変更しようとすると未定義動作(多くの場合、実行時エラー)になります。なお、現行のC++規格では char* への文字列リテラルの代入自体が不正となっています。

サンプルコード

#include <iostream>
using namespace std;

int main() {
    char *str = "Hello World";
    str[2] = 'x'; // 定数領域の書き換え
    cout << str;
}

出力結果

定数(文字列リテラル)の値を変更しようとしたことによるランタイムエラー

まとめ

未定義動作はコンパイルエラーにならないぶん厄介で、開発中は問題なく動いていても、別の環境や最適化レベルでは突然クラッシュするといった事態を招きます。「ゼロ除算」「未初期化変数」「NULLポインタ」「配列の範囲外アクセス」「符号付き整数のオーバーフロー」「文字列リテラルの変更」――これらは特によくあるパターンなので、コードレビューやデバッグの際には常に意識しておきましょう。

  1. Linuxのsudoコマンド徹底解説!知っておくべき基本と便利なテクニック

    Linux(特にUbuntu)を使い始めたばかりのユーザーは、いずれ「sudo」コマンドの存在に気づくことになります。多くのユーザーは「Permission denied(アクセス権がありません)」というエラーメッセージを回避するためだけにsudoを使っていますが、実はsudoにはそれ以外にもさまざまな便利な機能が備わっています。 sudoコマンドとは sudoについてよくある誤解の一つが、「一般ユーザーにroot権限を与えるためだけのコマンドだ」というものです。実際には、sudoコマンドを使えば任意のユーザーとしてコマンドを実行できます。デフォルトでは通常rootユーザーとして実行されます

  2. WhatsAppの文字装飾テクニック徹底ガイド|太字・斜体・取り消し線の使い方

    WhatsAppの文字トリックを使えば、メッセージを太字や斜体にできるってご存知でしたか? 10億人以上が利用するWhatsAppは、現在もっとも人気のあるメッセージアプリです。しかし、その便利な文字装飾機能を知らずに使っている方は少なくありません。この記事では、WhatsAppで文章を見やすく整えるための「太字」「斜体」「下線」などのテクニックを詳しく解説します。 誰でもすぐ試せる8つのWhatsApp文字テクニック それでは早速、メッセージをさまざまなスタイルで入力できる便利なテクニックを見ていきましょう。これらの装飾方法はAndroidでもiOSでも同じ操作で使えるので、どのデバイスで