[迷信] 0xe-0xe はゼロ

今回はかなり難しい話です。まずは、次のコードをご覧ください。

#include<stdio.h>

int main(void)
{
  if (0xe-0xe)
    puts("A");
  else
    puts("B");
  return 0;
}

このプログラムをコンパイルし実行すると、出力されるのは "A" でしょうか? それとも "B" でしょうか? 実際に試してみることなく、どんな振る舞いになるかがわかった方は、C/C++ に相当詳しい方です。

答えは "A" でもなければ "B" でもありません。処理系のバグ、または独自拡張がない限り、このソースコードはコンパイルできません。実際に GCC で試してみると、

invalid suffix "-0xe" on integer constant

というエラーメッセージが出力されました。理由が分からないと、このエラーメッセージを見ても、何のことかさっぱり分かりませんね。

それでは順を追って説明することにしましょう。いわゆる「リンク」に相当する部分を除けば、C も C++ も、次の翻訳過程を経てコンパイルされます。

  1. 物理文字からソース文字集合への変換
  2. 物理行から論理行への変換
  3. 前処理字句と空白類文字の分解
  4. 前処理指令の実行
  5. ソース文字集合から実行文字集合への変換
  6. 文字列リテラルの連結
  7. 字句の並びに対する解析

このうち 1.~6. は、いわゆる「前処理(preprocess)」に相当する部分であり、6. を終えた時点でひとつの翻訳単位が完成することになります。そして、7. がいわゆる狭義の「コンパイル」に相当する部分です。

ここで、3. に登場する「前処理字句(preprocessing token)」に注目してみましょう。C/C++ では、ソースファイルに記述されたコードは「字句(token)」に分解されます。しかし、字句への分解はいきなり行われるのではなく、より大雑把な字句である前処理字句に分解された後、必要な箇所で前処理字句から字句に変換されます。前処理字句には、識別子、前処理数、区切り子と演算子がありますが、今回の問題に関係するのは、このうちの前処理数です。

ここまで分かれば、後は前処理数の構文を調べるだけで何が起こったのかが見えてきます。

pp-number ::=
          digit
       |  '.' digit
       |  pp-number digit
       |  pp-number identifier-nondigit
       |  pp-number 'e' sign
       |  pp-number 'E' sign
       |  pp-number 'p' sign
       |  pp-number 'P' sign
       |  pp-number '.'

前処理数は、整数と浮動小数点数の区別も無ければ、10 進数、8 進数、16 進数の区別もありません。数字で始まるか、小数点で始まり、直後に数字が続くかすれば、空白類、区切り子、演算子以外が続く限り、前処理数の一部とみなされてしまうのです。

最初に例として挙げたコードを再びご覧ください。数字である 0 から始まり、 x, e, -, 0, x, e という前処理数の構成要素が続く 0xe-0xe は、翻訳過程 7. で整数値に変換されることになります。しかし、0xe まではよいものの、その後の -0xe は添え字(例えば long 型なら L を付けるといったもの)としては不正ですので、コンパイルエラーになってしまうのです。