6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

C++の未定義動作(Undefined Behavior)を理解して、安全なコードを書くためのガイド。

「動いてるからOK」じゃないんだよ。UBはコンパイラが「何をしても良い」とされる動作。クラッシュ、データ破壊、セキュリティホールの原因になる。

1. バッファオーバーフロー

// ❌ UB: 配列の範囲外アクセス
int arr[5];
arr[10] = 42;  // UB!

// ✅ 安全: 境界チェック
array<int, 5> arr = {1, 2, 3, 4, 5};
try {
    cout << arr.at(2) << endl;  // OK
    arr.at(10);  // 例外発生
} catch (const out_of_range& e) {
    cout << "Out of range!" << endl;
}

// ✅ span で安全なビュー
span<int> s(arr);
cout << "span size: " << s.size() << endl;

2. 初期化されていない変数

// ❌ UB: 未初期化変数の使用
int x;
cout << x;  // UB!

// ✅ 安全: 必ず初期化
int x = 0;
int y{};  // 値初期化(0)
int z = int{};  // 明示的初期化

// ✅ メンバ変数も初期化
struct Safe {
    int value = 0;  // デフォルトメンバ初期化子
    string name{};
};

3. nullポインタのデリファレンス

// ❌ UB: nullポインタのデリファレンス
int* p = nullptr;
*p = 42;  // UB!

// ✅ 安全: チェック
int* p = nullptr;
if (p) {
    cout << "Value: " << *p << endl;
} else {
    cout << "Pointer is null" << endl;
}

// ✅ optional を使う
optional<int> opt;
if (opt.has_value()) {
    cout << "Optional: " << *opt << endl;
}
cout << "Optional value: " << opt.value_or(0) << endl;

4. 解放済みメモリ(ダングリングポインタ)

// ❌ UB: 解放済みメモリへのアクセス
int* p = new int(42);
delete p;
*p = 100;  // UB!

// ✅ 安全: スマートポインタ
unique_ptr<int> up = make_unique<int>(42);
// 自動解放、ダングリングの心配なし

shared_ptr<int> sp = make_shared<int>(100);

// ✅ weak_ptr で循環参照を避ける
weak_ptr<int> wp = sp;
if (auto locked = wp.lock()) {
    cout << *locked << endl;
}

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

// ❌ UB: 符号付き整数オーバーフロー
int max = numeric_limits<int>::max();
int overflow = max + 1;  // UB!

// ✅ 安全: オーバーフローチェック
int a = numeric_limits<int>::max();
int b = 1;

if (a > numeric_limits<int>::max() - b) {
    cout << "Addition would overflow!" << endl;
} else {
    cout << "Safe addition: " << (a + b) << endl;
}

// ✅ 符号なし整数はラップアラウンド(well-defined)
unsigned int ua = numeric_limits<unsigned int>::max();
unsigned int ub = ua + 1;  // 0になる(well-defined)

// ✅ より大きな型を使う
int64_t safe_a = a;
int64_t result = safe_a + b;

6. 0除算

// ❌ UB: 整数の0除算
int x = 10 / 0;  // UB!

// ✅ 安全: チェック
int a = 10, b = 0;
if (b != 0) {
    cout << "Result: " << (a / b) << endl;
} else {
    cout << "Division by zero avoided!" << endl;
}

// ✅ optional で結果を返す
auto safe_divide = [](int x, int y) -> optional<int> {
    if (y == 0) return nullopt;
    return x / y;
};

auto result = safe_divide(10, 2);  // 5
result = safe_divide(10, 0);       // nullopt

7. 型キャストの問題

// ❌ UB: 不正なreinterpret_cast(strict aliasing violation)
float f = 3.14f;
int* p = reinterpret_cast<int*>(&f);
*p = 42;  // UB!

// ✅ 安全: memcpy を使う(型パニング)
float f = 3.14f;
int i;
memcpy(&i, &f, sizeof(i));

// ✅ bit_cast (C++20)
auto bits = bit_cast<int>(f);

// ✅ variant で型安全な値を扱う
variant<int, float, string> v = 3.14f;
if (holds_alternative<float>(v)) {
    cout << get<float>(v) << endl;
}

8. 無効なイテレータ

// ❌ UB: 無効化されたイテレータの使用
vector<int> v = {1, 2, 3};
auto it = v.begin();
v.push_back(4);  // イテレータ無効化の可能性
*it = 10;  // UB!

// ✅ 安全: インデックスを使う
vector<int> v = {1, 2, 3};
size_t idx = 0;
v.push_back(4);
cout << "v[" << idx << "] = " << v[idx] << endl;

// ✅ reserve で再割り当てを防ぐ
vector<int> v2;
v2.reserve(10);
auto it = v2.begin();
v2.push_back(1);  // reserve した範囲内なら再割り当てなし

9. シフト演算

// ❌ UB: ビット幅以上のシフト
int x = 1 << 32;  // UB! (32ビット環境)

// ❌ UB: 負の値のシフト
int y = -1 << 2;  // UB!

// ✅ 安全: 範囲チェック
int shift_amount = 10;
if (shift_amount >= 0 && shift_amount < 32) {
    uint32_t result = 1u << shift_amount;
}

// ✅ 符号なし整数を使う
uint32_t x = 1u;
cout << "x << 20 = " << (x << 20) << endl;

10. オブジェクトのライフタイム

// ❌ UB: ローカル変数への参照を返す
int& bad_ref() {
    int x = 42;
    return x;  // UB!
}

// ✅ 安全: 値を返す
int safe_value() {
    int x = 42;
    return x;  // 値コピー
}

// ✅ スマートポインタで管理
unique_ptr<int> safe_ptr() {
    return make_unique<int>(100);
}

11. 評価順序

// ❌ UB (C++14以前): 同一式内で同じ変数を複数回変更
int i = 0;
int x = i++ + ++i;  // UB!

// ✅ 安全: 明示的に分離
int i = 0;
int a = i++;
int b = ++i;
int x = a + b;

安全なコードのガイドライン

ガイドライン
1. 変数は必ず初期化する
2. 生ポインタの代わりにスマートポインタを使う
3. 配列の代わりにstd::array/std::vectorを使う
4. .at()で境界チェックする
5. nullチェックをする、またはoptionalを使う
6. 整数演算の前にオーバーフローをチェック
7. 除算の前に0チェック
8. C++コアガイドラインに従う
9. 静的解析ツールを使う
10. -fsanitize=undefinedでUBを検出

ツールによる検出

# UBサニタイザー
g++ -fsanitize=undefined program.cpp -o program

# アドレスサニタイザー
g++ -fsanitize=address program.cpp -o program

# 静的解析
clang-tidy program.cpp

まとめ

未定義動作を避けることで、安全で堅牢なC++プログラムを作成できます。モダンC++の機能(スマートポインタ、optional、span等)を活用し、サニタイザーで検証しましょう!

6
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?