符号ありと符号なしの比較で -1 が巨大な数として扱われる
int型とunsigned int型を比較すると、int型の値は自動的にunsigned intへ変換されます。負の数は非常に大きな正の数として扱われ、直感に反する比較結果になります。
なぜエラーが出ないのか
出力: a is not less
(エラーなし)- Cは何も報告しません。コンパイルも実行も正常に完了するためです
出力: a is not less- 実際の挙動 — 期待した結果と食い違っている箇所
見つけ方- エラーが出ないので、出力を目で確かめるか、コードを目で追うしかありません。この種の誤りが最も発見が遅れます
このエラーが出る典型パターン
パターン1
1 #include <stdio.h> 2 3 int main(void) { 4 int a = -1; 5 unsigned int b = 1; 6 if (a < b ) { ^ 7 printf("a is less\n"); 8 } else { 9 printf("a is not less\n"); 10 } 11 return 0; 12 }
aとbを比較すると、int型のaは自動的にunsigned intへ変換されます。-1は非常に大きな正の数になるため、bより大きいと判定されてしまいます。bをintにキャストすれば直感通りの比較になります。
直し方: a < b を a < (int)b にします。
パターン2
1 #include <stdio.h> 2 3 int main(void) { 4 int score = -5; 5 unsigned int passLine = 3; 6 if (score < passLine ) { ^ 7 printf("fail\n"); 8 } else { 9 printf("pass\n"); 10 } 11 return 0; 12 }
scoreが明らかにpassLineより低いにもかかわらず、符号なしへの暗黙変換によって「合格」と誤判定されてしまいます。
直し方: score < passLine を score < (int)passLine にします。
パターン3
1 #include <stdio.h> 2 3 int main(void) { 4 int balance = -10; 5 unsigned int fee = 5; 6 if (balance < fee ) { ^ 7 printf("overdrawn\n"); 8 } else { 9 printf("ok\n"); 10 } 11 return 0; 12 }
残高がマイナスで手数料より明らかに少ないのに、符号なしへの変換によって「残高は十分」と誤判定されます。金額計算でunsignedを不用意に使うと危険な理由の一つです。
直し方: balance < fee を balance < (int)fee にします。
パターン4
1 #include <stdio.h> 2 3 int main(void) { 4 int diff = -3; 5 unsigned int threshold = 2; 6 if (diff < threshold ) { ^ 7 printf("below\n"); 8 } else { 9 printf("above\n"); 10 } 11 return 0; 12 }
diffは自動的にunsigned intへ変換され、非常に大きな正の数として比較されてしまいます。
直し方: diff < threshold を diff < (int)threshold にします。
パターン5
1 #include <stdio.h> 2 3 int main(void) { 4 int delta = -2; 5 unsigned int limit = 1; 6 if (delta < limit ) { ^ 7 printf("under\n"); 8 } else { 9 printf("over\n"); 10 } 11 return 0; 12 }
符号なしへの暗黙変換によって、負の数が非常に大きな数として扱われてしまいます。
直し方: delta < limit を delta < (int)limit にします。
よくある誤解
「-1は明らかに1より小さい」という直感はここでは通用しません。Cの通常の算術変換規則により、符号ありと符号なしの比較では符号ありの値がunsignedに変換されるため、-1は0xFFFFFFFF(約42億)という巨大な値になり、1より大きいと判定されます。
実務での勘所
この種の比較は、-Wextraに含まれる-Wsign-compareという警告オプションで検出できます。符号ありと符号なしの値を比較しているコードがあると、コンパイル時に警告してくれるため、実行するまで気づけないという事態を避けられます。ループのカウンタにunsigned(たとえばsize_t)を使うことが多い標準ライブラリ関数(strlenの戻り値など)との比較で、この警告は実務でも頻繁に役立ちます。