1. 導入
C++の現場において、データサイズやビット演算を扱う際に避けて通れないのが「符号なし整数(unsigned)」です。しかし、直感的に引き算を行うと、予期せぬ巨大な数値に化けてしまい、深刻なバグやセキュリティリスクを招くことがあります。なぜこの挙動が起きるのか、どう対処すべきかを解説します。
2. 基礎知識
C++のunsigned型は、負の値を保持できません。コンピュータのメモリ上では、符号なし整数の引き算で結果がマイナスになる場合、「アンダーフロー(Underflow)」が発生します。このとき、値は型が許容する最大値から逆算される形で循環し、非常に大きな正の数として解釈されます。例えば32ビット環境で「0 – 1」を行った場合、結果は「4294967295(0xFFFFFFFF)」となります。これはループの終了判定や配列のインデックス計算で致命的なバグを引き起こす原因となります。
3. 実装/解決策
この罠を回避する最も確実な方法は、「引き算を行う前に大小比較を行う」ことです。減算の結果が負にならないことが論理的に保証されている場合を除き、常に条件分岐でガード節を設けるか、あるいは計算前に符号付き整数(int等)にキャストして安全を確認するアプローチが推奨されます。
4. サンプルプログラム
以下は、アンダーフローを安全に検知して処理する実用的なコード例です。
/ /
include
include
bool safe_subtract(unsigned int a, unsigned int b, unsigned int& result) {
// 引き算の結果がマイナスになるか事前にチェックする
if (a < b) {
return false; // アンダーフローが発生するため処理を中断
}
result = a - b;
return true;
}
int main() {
unsigned int val1 = 5;
unsigned int val2 = 10;
unsigned int result = 0;
if (safe_subtract(val1, val2, result)) {
std::cout << "計算成功: " << result << std::endl;
} else {
// 現場ではここでエラーハンドリングやログ出力を行う
std::cerr << "エラー: アンダーフローが発生しました。" << std::endl;
}
return 0;
}
5. 応用・注意点
現場での開発において特に注意が必要なのは、「for文の終了条件」です。
「for(unsigned int i = 10; i >= 0; --i)」のようなループを書くと、iが0の状態で減算が行われ、iは最大値に戻るため無限ループに陥ります。
回避策として、以下のいずれかを採用してください。
・ループ変数を符号付き整数(int)にする(インデックスが負にならないことが明らかな場合)
・ループ条件を「i > 0」にし、ループの最後にデクリメントを行う
・std::size_t型を使う場合でも、0との比較には細心の注意を払う
「unsignedだからマイナスにならない」という過信は捨て、常に「計算前にガードする」という防衛的なコーディングを心がけましょう。

コメント