2013-10-10

なぜGCCのCプリプロセッサーはlinuxという名前のマクロ名を定義するのか

Why does the C preprocessor interpret the word "linux" as the constant "1"? - Stack Overflow
Why does the C preprocessor interpret the word “linux” as the constant “1”? | Hacker News

以下のCコードをコンパイルしようとするとエラーになる。

$ cat test.c
#include <stdio.h>
int main(void)
{       
    int linux = 5;
    return 0;
}
$ gcc test.c
test.c: In function ‘main’:
test.c:4:9: error: expected identifier or ‘(’ before numeric constant

なぜだろうか。stdio.hではそのような名前のマクロが定義されてはいない。試しにCプリプロセスだけをするオプション、-Eを使ってみると、以下のような結果となる。

$gcc -E test.c
...
int main(void)
{
    int 1 = 5;
    return 0;
}
$

なんと、linuxというトークンが、Cプリプロセッサーによって、1に置き換わっているではないか。道理でコンパイルエラーになるわけだ。しかし、なぜなのか。

これは、昔多くのCコンパイラーが、unix環境では、unixという名前のマクロを定義していたことによる。これにより、プログラムは#ifdefやifndefなどを使い、プログラムがunix環境でコンパイルされているかどうかで場合分けをすることができる。

この名残で似たように、linuxというマクロ名がある。これは、GNU/Linux環境で定義される。

この規格違反の挙動は、GCCに規格準拠に振る舞うようにオプションを指定することで変更できる。

$gcc -E -std=c11 -pedantic test.c
...
int main(void)
{
    int linux = 5;
    return 0;
}

なお、GNU/Linux環境では、unixとlinuxが同時に定義される。これは、おそらく互換性の問題だろう。GNU/Linuxは、unixと互換性があり、多くのunix用プログラムは、単にコンパイルするだけで動作する。問題は、多くの既存のUnix用プログラムのソースコードが、unixのようなマクロが定義されていることを当てにしていて、もし定義されていない場合、コンパイルができなかったり、動作しなかったりしてしまう。互換性があるのにも関わらず、近眼でハードコードされたソースコードの記述により、動作しなくなってしまうのだ。そのため、GNU/Linux環境でも、unixが定義されている。

これから書くプログラムは、このような規格違反で実装依存の独自拡張のマクロ名に依存してはならない、Cならば-std=c11 --pedantic-error、C++ならば、-stdにはc++11ないしは、いずれはc++14を指定すべきである。

No comments: