次のコード(C言語)を実行すると、どのように出力されるだろうか。
#include <stdio.h> int main(){ unsigned char a = 0, b = ~a; if (b == ~a){ printf("b == ~a\n"); } else { printf("b != ~a\n"); } printf("%d\n", ~a); printf("%d\n", b); return 0; }
実際に出力させてみると、次のようになる。
たった今bに~aを代入したばかりなのに、b != ~aだとは!
b != ~a -1 255
n[255]を意図してn[~t](unsigned char t = 0;)と書いたのにそうならず、はまった。
これはn[-1](-1は0xffffffff)という意味になってしまう。
unsigned charなんだから、「~」演算子はアセンブラレベルなら8bit幅でnot blと
しているに決まっていると思い込んでいたのだが、
実際にアセンブリコードを吐かせてみると、32bitでnot ebxとされていた。
n[(unsigned char)~t]と書けばn[255]になるということはすぐ気づいいたが、
なぜそういう挙動なのかがわからず悩んだ。
だいいち、なぜ32bitなのか、その必然性がわからない。
汎整数拡張 - ロベールの小部屋
色々調べた結果、↑のページを発見。
printf("%d\n", ~a);で-1と表示されるような現象には割とよく出会うので、
「%dは32bit整数か何かで、それに合わせて変換されるのだろう」などと
漠然と思っていたが、的外れな理解だった。
(そもそも、それだとprintf("%d\n", b);と出力が異なることに説明がつかない)
unsigned char t = 0 に対し、~tとした時点で既にint型の-1に変換されているのだ。
これを再びunsigned charへキャストすれば255になるから、なかなか普段は気づかない。
32bitである必然性はintのサイズだった。
それにしても、ビット反転させるだけの演算でも(ていうかどんな演算でも)、
符号なしのunsigned charを符号付きのintに変換してから行う仕様だとは。
知識がないというのは怖い。
C#でも同様のコードを試してみたら、b = ~aの代入でエラーになった。
C#でも~aはintに変換されているが、暗黙的にはbyteへ戻せない。
バイト単位でビット反転させたいだけでも、明示的にキャストする必要があるのだ。
また、n[~(byte)0]がn[-1]の意味になる挙動もCと同じ。
キャストや配列の範囲チェックが厳しいので、Cよりはだいぶ間違いに気づきやすいが、
intへ変換してから計算するという仕様は一緒なのだね。
ちなみにVB6.0では、aがByte型の0のときNot aは255。