読者です 読者をやめる 読者になる 読者になる

C言語における空白文字

C言語における空白文字は、文字列の一部分としてと、トークンを明示的に区切る (long longlonglong は違う) こと以外には基本的に無意味というように思える。しかしプリプロセッサレベルでは、以下の場面で意味がある。

includeの中身

通常 <stdio.h> のようなコード断片は < stdio . h > に分解されるが、includeディレクティブ(または一部のpragma)の中でのみ特別に <stdio.h> という1つのトークンとして認識される (C11/§6.4.7)。このトークンに \, //, /*, ', " を含めた場合の挙動は未定義だが、空白を入れることは許されている (C11/§6.4.7)。したがって、

#include <a  b   c>

においては(規格書を読む限りは)空白文字が意味を持つ。

ただし、Cの規格では、includeのファイル名の解釈自体が処理系定義であり (C11/§6.10.2)、規格で規定されているヘッダ名に空白はない (C11/§7.1.2)。

defineの直後

以下の2つは異なる意味を持つ。

#define f(x, y) (y, x)
#define f (x, y) (y, x)

試しに以下のように実行してみるとわかる。

$ gcc -E -
#define f(x, y) (y, x)
#define g (x, y) (y, x)
f(1, 2)
g(1, 2)
# 1 "<stdin>"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "<stdin>"


(2, 1)
(x, y) (y, x)(1, 2)

このように、Cの規格では「マクロ関数定義の場合は、マクロ名と括弧の間に空白を入れない」と明確に規定する (C11/§6.10) ことで、関数マクロと通常のマクロを区別している。

文字列化

マクロの # で文字列化したときには、空白の存在/非存在が保たれる。(C11/§6.10.3.2)

$ gcc -E -
#define str2(x) #x
#define str(x) str2(x)
str(1+1)
str(1 + 1)
str(1  +  1)
str(1 /* hoge */ + /* fuga */ 1)
# 1 "<stdin>"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "<stdin>"


"1+1"
"1 + 1"
"1 + 1"
"1 + 1"

コメントを空白文字に置き換えることは、規格で明示されている (C11/§5.1.1.2) 。複数の空白を1つの空白文字にまとめるかどうかは、処理系定義である (C11/§5.1.1.2) 。 ただし、文字列化されるさいは、どの種類の空白も空白文字に置き換えられる (C11/§6.10.3.2)。

まとめ

C言語のコードは trigraph処理→改行処理→PP字句解析→プリプロセス→真正な字句への変換→構文解析→…… という流れで処理される (C11/§5.1.1.2)。ただでさえプリプロセスと構文解析の2段階の処理があってややこしいが、プリプロセスで使われる字句データと構文解析で使われる字句データに微妙に違いがある点も見落とせない。空白はその例のひとつで、構文解析では意味をもたないがプリプロセスでは意味をもつ。