【汎整数拡張】第2回 単項変換

前回は、以下のことを確認しました。

  1. 基本形が同じならば、signed/unsigendでキャストしようが、その内部表現(ビット列)は変化しない。
  2. 内部表現が同じであっても、その数学的値が同じとは限らない。

今回は、char/unsigned charをint/unsigned intにそれぞれ型変換した場合、その内部表現はどうなるかという実験です。

 19 int main(int argc, char **argv){
 20
 21     char c = -1;
 22     unsigned char uc = 255;
 23
 24     /*
 25         変換なし char/uchar
 26     */
 27     printf("> -1 : char\n");
 28     bit_dump(c, sizeof(c));
 29     printf("\n");
 30
 31     printf("> 255 : unsigned char\n");
 32     bit_dump(uc, sizeof(uc));
 33     printf("\n");
 34
 35     /*
 36         単項変換 char/uchar -> int
 37     */
 38     printf("> -1 : +(char)\n");
 39     bit_dump(+c, sizeof(+c));
 40     printf("\n");
 41
 42     printf("> 255 : +(unsigned char)\n");
 43     bit_dump(+uc, sizeof(+uc));
 44     printf("\n");
 45
 46     /*
 47         明示的キャスト
 48     */
 49     printf("> (int)char\n");
 50     bit_dump((int)c, sizeof((int)c));
 51     printf("\n");
 52
 53     printf("> (unsigned int)char\n");
 54     bit_dump((unsigned int)c, sizeof((unsigned int)c));
 55     printf("\n");
 56
 57     printf("> (int)unsigned char\n");
 58     bit_dump((int)uc, sizeof((int)uc));
 59     printf("\n");
 60
 61     printf("> (unsigned int)unsigned char\n");
 62     bit_dump((unsigned int)uc, sizeof((unsigned int)uc));
 63     printf("\n");
 64
 65     return 0;
 66 }

実行結果

> -1 : char
1111 1111

> 255 : unsigned char
1111 1111

> -1 : +(char)
1111 1111 1111 1111 1111 1111 1111 1111

> 255 : +(unsigned char)
0000 0000 0000 0000 0000 0000 1111 1111

> (int)char
1111 1111 1111 1111 1111 1111 1111 1111

> (unsigned int)char
1111 1111 1111 1111 1111 1111 1111 1111

> (int)unsigned char
0000 0000 0000 0000 0000 0000 1111 1111

> (unsigned int)unsigned char
0000 0000 0000 0000 0000 0000 1111 1111

int型より精度の低い型(char型など)が式中で評価された場合、その型はint型に型昇格します。int型で表現できない場合、unsigned int型に型昇格します。これを単項変換と呼称します。

例えば、char型オブジェクトの先頭に単項+演算子をつけると、char型オブジェクトは式の一部として評価されるため、int型に昇格します。その結果、内部表現のビット長は、int型のバイトサイズ(以下の場合は4バイト=32ビット)に伸長されます。

> -1 : +(char)
1111 1111 1111 1111 1111 1111 1111 1111

> 255 : +(unsigned char)
0000 0000 0000 0000 0000 0000 1111 1111

結果型のビット長が大きい場合、上位ビットに空きができるわけですが、その空きビットを0/1のいずれで埋めるか、という問題があります。

  1. char→intの型昇格においては、符号ビットの値と同じビットで埋める。
  2. unsigned char→intの型昇格においては、0で埋める。

これは、「型昇格前と型昇格後では、その数学的値は変化しない」と考えれば道理ですが…

そうすると、負数のchar→unsigned intへの型昇格ではどうなるか。そもそもunsigned intには符号の概念が無いので、負数をどうやって表現するのか、という問題が出てきます。これについては、以下の通りとなります。

> (int)char
1111 1111 1111 1111 1111 1111 1111 1111

> (unsigned int)char
1111 1111 1111 1111 1111 1111 1111 1111

char→intへの型昇格は先ほどの単項変換の通りだとして、char→unsigned intへの型昇格においては、

char → int → unsigned int

という二段階の型昇格が行われます。int→unsigned intへの型昇格においては、同じビット長なので内部表現は変化しない。結果として、char→unsigned intへの型変換は、char→intへの変換と同様となります。

unsigned char→int/unsigned intへの型変換は、以下の通り、空きビットをただ0で埋めるだけです。

> (int)unsigned char
0000 0000 0000 0000 0000 0000 1111 1111

> (unsigned int)unsigned char
0000 0000 0000 0000 0000 0000 1111 1111

空きビットに何がセットされるかをまとめたのが、以下の表です。
(行見出しは昇格前の型。列見出しは昇格後の型。-は空きビットが発生しないことを示す)

  char型 uchar型 int型 uint型
char型 - - 元の符号ビット 元の符号ビット
uchar型 - - 0 0

こうして見ると、型昇格後の空きビットに何がセットされるかは「昇格前の型(の符号修飾子)」に依存することが分かります。

char型変数に-1を代入すると、内部表現としては「1111 1111」となりますが、それをunsigned intに型昇格する際、その数学的値は「255」とは解釈されないことに注意したいです。charはいったんintに型昇格しますが、int→unsigned intの型昇格においては内部表現は変化しないため、その数学的値は

(char)-1 → (int)-1 → (unsigned int)2^64-1

と変化します。