Code Fix

中級

符号ありと符号なしの比較で -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 is not less期待: a is less

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  }
出力: pass期待: fail

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  }
出力: ok期待: overdrawn

残高がマイナスで手数料より明らかに少ないのに、符号なしへの変換によって「残高は十分」と誤判定されます。金額計算で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  }
出力: above期待: below

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  }
出力: over期待: under

符号なしへの暗黙変換によって、負の数が非常に大きな数として扱われてしまいます。

直し方: delta < limit を delta < (int)limit にします。

この問題を解いてみる →

よくある誤解

「-1は明らかに1より小さい」という直感はここでは通用しません。Cの通常の算術変換規則により、符号ありと符号なしの比較では符号ありの値がunsignedに変換されるため、-1は0xFFFFFFFF(約42億)という巨大な値になり、1より大きいと判定されます。

実務での勘所

この種の比較は、-Wextraに含まれる-Wsign-compareという警告オプションで検出できます。符号ありと符号なしの値を比較しているコードがあると、コンパイル時に警告してくれるため、実行するまで気づけないという事態を避けられます。ループのカウンタにunsigned(たとえばsize_t)を使うことが多い標準ライブラリ関数(strlenの戻り値など)との比較で、この警告は実務でも頻繁に役立ちます。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)