1990 ISO C 規格の「Rationale」(論理的根拠) 節に、次のような情報があります。「QUIET CHANGE」(メッセージなしの変更)。符号なし保存演算変換に依存するプログラムは、おそらくはメッセージを発行せずに、異なる動作を行います。これは、現在広く行われている慣習に対して委員会が行なったもっとも重大な変更であると考えられます。
この節では、この変更がコーディングにどのように影響するかを説明します。
K&R の『プログラミング言語 C』によると、unsigned は 1 つだけの型を指定していました。つまり、unsigned char、unsigned short、unsigned long はありませんでした。しかし、ほとんどの C コンパイラにはすぐにこれらの型が追加されました。unsigned long を実装せず、残りの 2 つだけを実装するコンパイラもあります。当然、式の中でこれらの新しい型がほかの型と併用されている場合、実装によって異なる型拡張規則が適用されました。
ほとんどの C コンパイラでは、より簡単な規則「符号なし保存」が使用されています。 つまり、unsigned 型を拡張する必要があるときは unsigned 型に拡張します。そして、unsigned 型が signed 型と混合されているときも、unsigned 型に拡張されます。
ISO C では、「値の保持」という規則も指定されています。この規則では、拡張結果の型は、オペランドの型の相対的なサイズによって異なります。unsigned char または unsigned short を拡張するとき、int がより小さい型の値をすべて表現できる大きさである場合は、拡張結果の型は int になります。それ以外の場合、unsigned int になります。この「値の保持」規則に従えば、ほとんどの式が無難な演算結果になります。
ISO C コンパイラは、移行モード (-Xt) または ISO 以前のモード (-Xs) では、符号なし保存拡張規則を適用します。準拠モード (-Xc) および ISO モード (-Xa) では、値保持拡張規則を使用します。
次のコードでは、unsigned char が int より小さいと仮定します。
int f(void) { int i = -2; unsigned char uc = 1; return (i + uc) < 17; } |
前述のコードを使用すると、-xtransition オプションを使用したときに、次の警告が発行されます。
line 6: warning: semantics of "<" change in ISO C; use explicit cast
加算の結果の型は int (値保持) または unsigned int (符号なし保存) です。しかし、どちらの場合でもビットパターンは同じです。2 の補数を使用するマシンでは、次のようになります。
i: 111...110 (-2) + uc: 000...001 ( 1) =================== 111...111 (-1 or UINT_MAX) |
このビット表現は、int では -1 に対応し、unsigned int では UINT_MAX に対応します。したがって、結果の型が int の場合、符号付き比較が使用され、「より小さいか」の答えは真になります。結果の型が unsigned int の場合、符号なしの比較が行われ、「より小さいか」の答えは偽になります。
キャストの加算を使用すると、2 つの動作のうち、どちらを希望するかを指定できます。
value preserving: (i + (int)uc) < 17 unsigned preserving: (i + (unsigned int)uc) < 17 |
コンパイラが異なれば同じコードに対する解釈も異なるため、この式は曖昧になる可能性があります。キャストの加算を使用することにより、コードが読みやすくなると同時に、警告メッセージも発行されなくなります。
同じ動作が、ビットフィールド値の拡張にも適用されます。ISO C では、int または unsigned int ビットフィールド内のビットの数が int 中のビットの数よりも少ない場合、拡張される型は int です。それ以外の場合、拡張される型は unsigned int です。ほとんどの古い C コンパイラでは、明示的な符号なしビットフィールドの場合、拡張される型は unsigned int です。それ以外の場合は int です。
この場合も、キャストを使用することにより、曖昧になることを防ぐことができます。
次のコードでは、unsigned short と unsigned char の両方が int よりも狭いと仮定します。
int f(void) { unsigned short us; unsigned char uc; return uc < us; } |
この例では、2 つの自動変数は int または unsigned int のどちらかに拡張されます。したがって、比較対象は符号なしになることも、符号付きになることもあります。しかし、どちらを選んでも結果は同じなので、警告は発行されません。
式と同様に、ある整数定数の型の規則も変更されました。K&R C では、接尾辞なしの 10 進定数の型は、その値が int に収まる場合だけ int でした。接尾辞なしの 8 進定数または 16 進定数の型は、その値が unsigned int に収まる場合だけ int でした。それ以外の場合、整数定数の型は long でした。したがって、値が結果の型に収まらないことがありました。1990 ISO/IEC C 規格では、定数の型は、次のリストのうち、値を格納できる最初の型となります。
接尾辞なし 10 進数: int、long、unsigned long
接尾辞なし 8 進数または 16 進数: int、unsigned int、long、unsigned long
接尾辞 U 付き: unsigned int、unsigned long
接尾辞 L 付き: long、 unsigned long
接尾辞 UL 付き : unsigned long
ISO C コンパイラで -xtransition オプションを使用するとき、定数の型規則によって式の動作が異なる場合は警告が発行されます。古い整数定数の型規則は、移行モード (-Xt) だけで適用されます。ISO モード (-Xa) と準拠モード (-Xc) では、新しい規則が適用されます。
接尾辞なしの 10 進定数の型規則は、1999 ISO C 規格に従って変更されています。「2.1.1 整数定数」を参照してください。
次のコードでは、int が 16 ビットであると仮定します。
int f(void) { int i = 0; return i > 0xffff; } |
16 進定数の型は int (2 の補数を使用するマシン上で - 1 の値を持つ) または unsigned int (65535 の値を持つ) のどちらかです。比較結果は、ANSI 以前モード (-Xs) と移行モード (-Xt) では真で、ANSI モード (-Xa) と準拠モード (-Xc) では偽です。
この場合も、キャストを適切に使用することにより、コードが読みやすくなり、警告も発行されなくなります。
-Xt, -Xs modes: i > (int)0xffff -Xa, -Xc modes: i > (unsigned int)0xffff or i > 0xffffU |