暗黙の型変換

2020年1月20日

暗黙の型変換と聞いて、多くの C プログラマーは「ああ、それなら知ってるよ」と思うことでしょう。そして思い浮かべるのは、次のような場面ではないでしょうか。

int main(int argc, char *argv[])
{
  short n = argc;  // int から short への変換
}

確かに型が異なる二者間での初期化・代入・返却で発生するのは、わかりやすい暗黙の型変換です。

ですが、通常の C や C++ のプログラムの中で実際に発生している暗黙の型変換の頻度は、こんなものではありません。その様子は、あたかも「ほとんどすべての変数が int になりたがっている」かのようです。

結論

C や C++ のプログラマーは、初心者を脱する頃になると int を毛嫌いし始める傾向にあります。まるで int を撲滅すれば移植性が上がるとでも言うように。

ですが、特別な事情がない限り、整数の型には int か unsigned int のどちらかを選びましょう。特に、値が大きくないからという理由だけで char や short を選んではいけません。

それでも整数の型に int か unsigned int 以外を選ぶなら、これから説明する「汎整数拡張」をしっかり理解したうえで、ソースコード上で暗黙に展開されるそれらを脳内でトレースできなければなりません。私なら、その煩わしさに苛まれるよりも、少し気をつけて int を使うほうを選びます。

前提

話を簡単にするために、ここではプリミティブな整数型のサイズがそれぞれ次のように扱われる処理系を想定して話を進めます。

バイト数
int4
char1
short2
long4

汎整数拡張

C の規格では、整数に対して算術演算子またはビット演算子を適用しようとしたとき、コンパイラは先にすべての項の型を「昇格」させてから演算子を適用するというルールがあります。これを汎整数拡張といいます。

昇格

汎整数拡張では、すべての整数型は符号の有無にかかわらず、収容できる限り int に変換されます。つまり次の型はすべて、演算子を当てた瞬間 int に変換されてしまうということです。

  • signed char
  • unsigned char
  • short
  • unsigned short

符号拡張

符号あり整数の昇格では、符号の維持のため、最上位ビットを上位側に敷き詰めるような拡張がなされます。これを符号拡張といいます。以下に実例を挙げます。

昇格前の型昇格前の値昇格後の値
signed char0x400x00000040
signed char0x800xFFFFFF80
unsigned char0x800x00000080
signed short0x40000x00004000
signed short0x80000xFFFF8000
unsigned short0x80000x00008000

演算子

汎整数拡張が発生するトリガとなる演算子をすべて列挙すると、次のようになります。

  • 単項演算子
    • +a
    • -a
    • ~a
  • 二項演算子
    • a * b
    • a / b
    • a % b
    • a + b
    • a - b
    • a << b
    • a >> b
    • a & b
    • a ^ b
    • a | b

ここで再度、型の昇格は演算の「前に」行われるという点を思い出してください。それを考えれば、変数というものはいわば空気に触れただけで int に早変わりするような存在だということがお分かりいただけるのではないかと思います。

たとえば次のコードには一見、型変換が存在しないかのように見えます。

unsigned short bitoff(unsigned short bits, unsigned short mask)
{
  return bits & ~mask;
}

ですが、実際は汎整数拡張によって、コンパイラは次のコードに相当するような型変換を暗黙的に行っているのです。

unsigned short bitoff(unsigned short bits, unsigned short mask)
{
  int BITS = (int)bits;
  int MASK = (int)mask;
  return (unsigned short)(BITS & ~MASK);
}

このように、char や short を多用するプログラムでは、水面下で昇格と降格がひっきりなしに行われているということを、組み込みプログラマーは自覚しなければなりません。このことが、予想もしないバグにつながることもあるのですから。