MCPP-MANUAL

== How to Use MCPP ==

for V.2.7.2 (2008/11)
松井 潔 (kmatsui@t3.rim.or.jp)

== 目次 ==

1. 概要
1.1. OSや処理系を選ばない portable なソース
1.2. 正確な Standard C モードに加えてその他の各種モードも
1.3. このマニュアルの表記法

2. 起動時のオプションと環境設定
2.1. mcpp 実行プログラムの2種類のビルドと5つの動作モード
2.2. 起動時のオプションの指定法
2.3. 共通のオプション
2.4. mcpp の動作モードによるオプション
2.5. 特定の処理系以外の処理系に共通のオプション
2.6. 処理系ごとのオプション
2.7. 環境変数
2.8. Multi-byte character の encoding
2.9. ワンパスコンパイラで mcpp を使うには
2.10. 統合開発環境で mcpp を使うには
2.10.1. Visual C++ の IDE で mcpp を使う方法
2.10.2. Mac OS X / Xcode.app で mcpp を使う方法

3. 拡張機能と互換性
3.1. #pragma MCPP put_defines, #pragma MCPP preprocess 等
3.1.1. ヘッダファイルの pre-preprocess
3.2. #pragma once
3.2.1. ヘッダファイルに #pragma once を書き込むツール
3.3. #pragma MCPP warning, #include_next, #warning
3.4. #pragma MCPP push_macro, #pragma __setlocale 等
3.5. #pragma MCPP debug, #pragma MCPP end_debug, #debug, #end_debug
3.5.1. #pragma MCPP debug path, #debug path
3.5.2. #pragma MCPP debug token, #debug token
3.5.3. #pragma MCPP debug expand, #debug expand
3.5.4. #pragma MCPP debug if, #debug if
3.5.5. #pragma MCPP debug expression, #debug expression
3.5.6. #pragma MCPP debug getc, #debug getc
3.5.7. #pragma MCPP debug memory, #debug memory
3.5.8. #pragma MCPP debug macro_call
3.6. #assert, #asm, #endasm
3.7. C99 の新機能(_Pragma() 演算子、可変引数マクロ等)
3.8. 処理系ごとの特殊な仕様
3.8.1. GCC, Visual C の可変引数マクロ
3.8.2. GCC の 'defined' の処理
3.8.3. Borland C の asm 文その他の特殊な構文
3.8.4. #import その他
3.9. GCC の問題と GCC との互換性
3.9.1. FreeBSD 2 / kernel ソースのプリプロセス
3.9.2. FreeBSD 2 / libc ソースのプリプロセス
3.9.3. GCC 2 / cpp の仕様の問題
3.9.4. Linux / glibc 2.1 ソースのプリプロセス
3.9.5. GCC 2 で mcpp を使うには
3.9.5.1. mcpp のウォーニングを整理するには
3.9.6. GCC 3.2 ソースのプリプロセス
3.9.7. GCC 3, 4 で mcpp を使うには
3.9.8. Linux / glibc 2.4 ソースのプリプロセス
3.9.9. Linux の stddef.h, limits.h, #include_next の問題
3.9.10. Mac OS X / Apple-GCC とシステムヘッダの問題
3.9.11. firefox 3.0b3pre ソースのプリプロセス
3.10. Visual C++ のシステムヘッダの問題
3.10.1. コメントを生成するマクロ ?
3.10.2. Identifier 中の '$'

4. 処理系定義の仕様
4.1. 終了時の status 値
4.2. Include directory のサーチパス
4.3. Header name の構築法
4.4. #if 式の評価
4.5. #if 式での文字定数の評価
4.6. #if sizeof (type)
4.7. White-space sequence の扱い
4.8. mcpp 実行プログラムのデフォルトの仕様

5. 診断メッセージ
5.1. 診断メッセージの形式
5.2. Translation limits
5.3. Fatal error
5.3.1. mcpp 自身のバグ
5.3.2. 物理的エラー
5.3.3. Translation limits と内部バッファのエラー
5.3.4. #pragma MCPP preprocessed に関するエラー
5.4. Error
5.4.1. 文字とトークンに関するエラー
5.4.2. 完結しないソースファイルのエラー
5.4.3. Preprocessing group 等の対応関係のエラー
5.4.4. ディレクティブ行の単純な構文エラー
5.4.5. #if 式の構文エラー等
5.4.6. #if 式の評価に関するエラー
5.4.7. #define のエラー
5.4.8. #undef のエラー
5.4.9. マクロ展開のエラー
5.4.10. #error, #assert
5.4.11. #include の失敗
5.4.12. その他のエラー
5.5. Warning (class 1)
5.5.1. 文字、トークンおよびコメントに関するウォーニング
5.5.2. 完結しないソースファイルのウォーニング
5.5.3. ディレクティブ行に関する各種のウォーニング
5.5.4. #if 式に関するウォーニング
5.5.5. マクロ展開に関するウォーニング
5.5.6. 行番号に関するウォーニング
5.5.7. #pragma MCPP warning, #warning
5.6. Warning (class 2)
5.7. Warning (class 4)
5.8. Warning (class 8)
5.9. Warning (class 16)
5.10. 診断メッセージ索引

6. バグ報告等

1. 概要

mcpp は Martin Minow の DECUS cpp を元に kmatsui(松井 潔)が全面的に書き直したCプリプロセッサです。mcpp という名前は Matsui cpp という意味です。これはソースで提供するもので、各処理系で使うにはその処理系に合わせてソースに若干の変更を加えた上でコンパイルして mcpp の実行プログラムを作る必要があります。*1

このマニュアルでは、すでに特定の処理系に移植された実行プログラムの仕様を説明しています。さらに詳細を知りたい人、ソースから何らかの処理系に移植してみたい人は、ソースと mcpp-porting.html というドキュメントを参照してください。
これらのソース、ドキュメントはすべてオープンソース・ソフトウェアとして提供します。
マニュアルの内容に入る前に、まず mcpp の特徴を紹介しておきます。

注:

*1 mcpp V.2.6.3 からは、コンパイルずみの何種類かのバイナリ・パッケージも次のサイトで提供するようにした。

http://mcpp.sourceforge.net/


1.1. OSや処理系を選ばない portable なソース

Linux, FreeBSD, Windows 等の多くのOSをサポートしている portable なプリプロセッサであり、そのソースは Standard C (ANSI/ISO/JIS C) の処理系または Standard C++ の処理系でさえあればコンパイルできる広い portability を持っています。ライブラリ関数は古典的なものしか使っていません。
各処理系に移植するためには、多くの場合、ヘッダファイル中のいくつかのマクロ定義を書き替えてコンパイルするだけですみます。最悪の場合でもソースファイルに数十行書き足す程度です。

Multi-byte character(漢字)の処理は日本の EUC-JP, shift-JIS, ISO2022-JP、中国の GB-2312、台湾の Big-5、韓国の KSC-5601 (KSX 1001) に対応しており、UTF-8 も使えます。Shift-JIS, ISO2022-JP, Big-5 の場合、コンパイラ本体が漢字を認識しない処理系では、mcpp がそれを補います。


1.2. 正確な Standard C モードに加えてその他の各種モードも

Standard C 準拠の動作モードのほかに、K&R 1st. のモードや "Reiser" model cpp のモードもあり、さらには自称 post-Standard 仕様のモードまであります。C++ のプリプロセッサとして動作する実行時オプションもあります。
Standard C モードは既存の多くのプリプロセッサと違って、規格を完全に実装しているつもりです。C90, C95, C99, C++98 のすべてに対応しています。Standard C プリプロセスの reference model となるものを目指して作ってあります。これらの規格のバージョンは実行時オプションで指定することができます。*1

ほかにいくつかの有用な拡張機能も持っています。マクロの展開機序や #if 式の評価機序をトレースする #pragma MCPP debug もあります。ヘッダファイルを "pre-preprocess" しておくこともできます。
いくつかの有用な実行時オプションも備えています。ウォーニングのレベルを指定するオプションや、include directory を指定するオプション等です。
ソースにどんな間違いがあっても mcpp は暴走したり見当外れなメッセージを出したりせず、正確でわかりやすい診断メッセージを出して適切な処理をします。移植上で問題となる点についても警告を発します。
高品質でありながら、コードサイズは比較的小さく、メモリ消費も比較的少なくてすみます。
詳細なドキュメントも付属しています。

mcpp の欠点を強いて挙げれば、速度がやや遅いことです。GCC 3.*, 4.* / cc1 に比べると2倍から3倍の時間がかかります。しかし、Borland C 5.5 / cpp と同じくらいの速度で、ヘッダファイルの pre-preprocess の機能を使うともう少し速くなるので、特に遅いほうではありません。正確であること、portable なソースであること、少ないメモリでも動作すること等のためには、この程度の処理時間はやむをえないと考えています。

なお、プリプロセッサの Standard C 準拠度をテストするための検証セットである "Validation Suite for Standard C Preprocessing"、その解説およびそれを使ってテストした各種プリプロセッサの採点簿 cpp-test.html を mcpp とともに公開しています。これを見ると、「Standard C 準拠」と称する既存のプリプロセッサにいかに多くの問題があるかがわかります。

mcpp は V.2.3 の開発の途中で、検証セット V.1.3 とともに、情報処理推進機構(IPA) の平成14年度「未踏ソフトウェア創造事業」に新部 裕・プロジェクトマネージャによって採択され、2002/07 - 2003/02 の間は IPA の資金援助と新部PMの助言のもとに開発が進められました。英語版ドキュメントもこのプロジェクトの中で、有限会社・ハイウェルに翻訳を委託し、それに私が修正とテキスト整形を加えてできあがったものです。*2
mcpp はさらに平成15年度にも「未踏ソフトウェア創造事業」に伊知地 宏 PM によって継続して採択され、V.2.4 への update 作業が進められました。
その後も mcpp と検証セットはさらに改良の作業が続けられています。

注:

*1 C言語の規格としては ISO/IEC 9899:1990 (JIS X 3010-1993) が長く使われてきたが、1999 年には ISO/IEC 9899:1999 が採択された。ここでは前者を C90、後者を C99 と呼ぶ。前者は ANSI X3.159-1989 が移行したものなので、一般には ANSI C または C89 と呼ばれることもある。また、ISO/IEC 9899:1990 + Amendment 1995 を C95 と呼ぶことがある。C++ の規格は ISO/IEC 14882:1998 およびその正誤訂正版である ISO/IEC 14882:2003 で、この両者をここでは C++98 と呼ぶ。

*2 「未踏ソフトウェア創造事業」(Exploratory Software Project) の概要は次のところで知ることができる。

http://www.ipa.go.jp/jinzai/esp/

mcpp V.2.3 以降のソースおよびドキュメントと検証セット V.1.3 以降は次のところに置いてきたが、

http://www.m17n.org/mcpp/

2006/04 に次のところに移った。

http://mcpp.sourceforge.net/

mcpp の古いバージョンである cpp V.2.2 および検証セット V.1.2 はベクター社のサイトの次のところにある。dos/prog/c というディレクトリに入れられているが、MS-DOS 専用ではない。ソースは UNIX, WIN32/MS-DOS 等に対応している。

http://www.vector.co.jp/soft/dos/prog/se081188.html
http://www.vector.co.jp/soft/dos/prog/se081189.html
http://www.vector.co.jp/soft/dos/prog/se081186.html

これらのアーカイブファイル中のテキストファイルは、Vector のものは Windows に合わせて、改行コードは [CR]+[LF]、漢字は shift-JIS で encode してある。SourceForge のものは V.2.5 までは UNIX 系に合わせて改行コードは [LF]、漢字は EUC-JP である。V.2.6 からは [CR]+[LF] / shift-JIS の zip 版と[LF] / EUC-JP の tar.gz 版の2種類のアーカイブファイルを置くようにした。


1.3. このマニュアルの表記法

このマニュアルはかつてはテキストファイルでしたが、V.2.6.2 からは html ファイルに変わりました。
このマニュアルでは次のようにフォントを使い分けています。


2. 起動時のオプションと環境設定

2.1. mcpp 実行プログラムの2種類のビルドと5つの動作モード

mcpp には、どの処理系(コンパイラ)からも独立して単独で動く compiler-independent-build と、特定の処理系のプリプロセッサに取って代わって動く処理系専用 build (compiler-specific-build) とがあります。前者はプリプロセスだけをするためのもので、その出力を何らかのコンパイラに与えても正しくコンパイルされるとは限りません。後者は特定の処理系でコンパイルとリンクまで行うためのもので、mcpp の出力はそのコンパイラの仕様に合わせてあります。*1, *2

前者ではオプション等の仕様は mcpp をコンパイルした処理系のいかんにかかわらずほぼ一定で、OS による相違が少しあるだけです。後者では共通の仕様とともに、処理系との互換のための仕様が多くあり、その部分は処理系ごとに異なっています。Compiler-independent 版のオプションは処理系専用版でもほぼ使えますが、処理系のオプションとの衝突を避けるために別のオプションになっているものもあります。
なお、このドキュメントで「GCC 用」「Visual C 版」等と表記しているのはいずれもそれぞれ GCC 専用 build, Visual C 専用 build の意味です。

Compiler-independent 版でも処理系専用版でも、mcpp にはいくつかの動作モードがあり、それぞれ異なるプリプロセス仕様で動作します。モードには次の5つあります。

これらのモードを指定するオプションは次の通りです。 モードを何も指定しないと -@std を指定したことになります。

STD, COMPAT, POSTSTD を合わせて Standard モード、KROLDPREP を合わせて pre-Standard モードと呼ぶことにします。また、COMPATSTD とほとんど同じなので、特に断らない限り STD には COMPAT も含め、必要な場合だけ COMPAT に言及します。*3

Standard と pre-Standard のモードとでは、マクロ展開方法の違いが多くあります。C90 と pre-C90 との違いだと思って間違いありません。最も大きな違いは、関数様マクロ(引数付きマクロ)の展開で、引数にマクロが含まれている場合、Standard モード では引数を先に完全に展開してから元マクロの置換リスト中のパラメータと置き換えるのに対して、pre-Standard では展開せずにパラメータと置換し、再スキャン時に展開することです。
また、Standard モード では直接にも間接にもマクロの再帰的展開は原則としてしません。pre-Standard では再帰的なマクロ定義があると、展開時に無限再帰を引き起こしてエラーとなります。

行末にある \ の扱いもモードによって異なり、Standard モード では trigraph 処理の後、tokenization の前に <backslash><newline> の sequence を削除しますが、pre-Standard モード では文字列リテラルの中にある場合と #define 行にある場合に限ってこれを削除します。

いわゆる tokenization(トークン分割、トークンの切り出し)もモードによって微妙に違います。
Standard モード では「token base での動作」という建前に忠実に tokenization を行います。具体的には、Standard モード では、マクロを展開するとその前後に space を挿入して、前後の token との意図しない連結が発生するのを防ぎます。pre-Standard モード は伝統的・便宜的・暗黙的な tokenization と、「character base でのテキスト置換」によるマクロ展開方法の痕跡を残しています。これらの点については、 cpp-test.html#2 をご覧ください。
Standard モード では preprocessing number という数値トークンを規定通りに扱います。pre-Standard では、数値トークンはCの整数定数トークンおよび浮動小数点数トークンと同じです。整数定数での接尾子 'U', 'u', 'LL', 'll'、浮動少数点定数での接尾子 'F', 'f', 'L', 'l' はトークンの一部として認識しません。
ワイド文字の文字列リテラルと文字定数は Standard モード でしか単一のトークンとして認識されません。

Digraph, #error, #pragma, _Pragma() operator は Standard モード でないと使えません。-S <n> オプション(strict-ansi モード)と -+ オプション(C++ プリプロセッサとして動作する)も Standard モード でしか使えません。事前定義マクロ __STDC__, __STDC_VERSION__ は Standard モード の時に定義され、pre-Standard の時は定義されません。
#if defined, #elif は pre-Standard では使えません。#include, #line の引数にマクロを使うことは pre-Standard ではできません。事前定義マクロ __FILE__, __LINE__, __DATE__, __TIME__ は pre-Standard の時は定義されません。
他方で、#assert, #asm (#endasm), #put_defines, #debug は pre-Standard モードでしか使えません。
#if 式は Standard モード では long long / unsigned long long で評価しますが、pre-Standard モード では (signed) long だけで評価します。#if 式で sizeof (type) が使えるのは pre-Standard だけです。

Trigraph と UCN (universal-character-name) は、STD モードでしか使えません。

診断メッセージの出方もモードによって少し違っています。

以上に述べたこと以外で K&R 1st. に Standard C と異なる明確な規定のないことがらについては、pre-Standard モード では C90 の規定に従います。

OLDPREP モードの KR モードとの違い、および POSTSTD, COMPAT 各モードの STD モードとの違いは次のようなものです。

なお、このほか lang-asm モードというものもあります。 これはアセンブラソースに C のコメント、ディレクティブ、マクロが挿入された変則的なソースを処理するモードです。 POST_STD はこのモードになることはできませんが、それ以外のモードは一定のオプションを指定することでこのモードになります。 その仕様については 2.5 を見てください。

こういうことで mcpp の実行プログラムにはいくつもの仕様があるので、マニュアルを注意して読んでください。この章ではまず mcpp の共通のオプションを説明し、次に動作モードによって異なるオプションを述べ、次に特定の処理系専用版を除いて共通のオプションを述べ、そのあとで処理系専用版の処理系ごとに異なるオプションを記載します。

注:

*1 このほか subroutine-build というものもある。何らかの他のメインプログラムから mcpp がサブルーチンとして呼び出されるものである。しかし、その仕様は mcpp をコンパイルした時の設定によって compiler-specific-build または compiler-independent-build のどちらかと同じになるので、このマニュアルでは特にとりあげない。 Subroutine-build については mcpp-porting.html#3.12 を参照のこと。

*2 SourceForge のサイトで提供するバイナリ・パッケージはすべて compiler-independent-build である。

*3 V.2.5 までは Standard モードと pre-Standard モードとで実行プログラムが別になっていたが、V.2.6 から1つに統合された。

*4 これは GCC, Visual C++ 等の主要な処理系との互換性のためのオプションである。'COMPAT' は "compatible" の意味である。


2.2. 起動時のオプションの指定法

以下の記載では、<arg> という記法は arg がユーザの入力すべき任意の引数であることを示し、[arg] は arg が省略可能な引数であることを示します。どちらにしても <, >, [, ] の文字そのものは入力してはいけません。

mcpp を起動する書式は次の形です。ただし、mcpp という名前は mcpp のインストール時の指定によっては別の名前になります。

mcpp [-<opts> [-<opts>]] [in_file] [out_file] [-<opts> [-<opts>]]

out_file(出力パス)が省略された時は(-o オプションが指定されない限り)標準出力に出力します。in_file(入力パス)も省略された時は標準入力から入力します。診断メッセージは(-Q オプションが指定されない限り)標準エラー出力に出力します。
これらのどれかのファイルがオープンできない時は、エラーメッセージを出して終了します。

引数を必要とするオプションに引数がない場合はエラーとなります(-M オプションだけは別)。
引数を必要とするオプションでは -I<arg>, -I <arg> のどちらも有効です(オプション文字と引数の間に space はあってもなくても良い)。
引数のないオプションは -Qi, -Q -i のどちらも有効です(1つの '-' の後につなげても別々に '-' を付けても良い。ただし、-M はつなげてはいけない)。
同一のオプションが複数回指定された場合、-D, -U, -I, -W オプションはそれぞれが有効です。-S, -V, -+ は2回目以降は無視されます。-2, -3 はそのたびに仕様が反転します。その他のオプションは最後に指定されたものが有効です。

大文字と小文字は区別されます。
いわゆるスイッチキャラクタは Windows でも - であり、/ ではありません。
不正なオプションを指定すると usage 文が表示されるので、mcpp -? 等とすることで、使えるオプションを確かめることができます。Usage 文のほかにもいくつかのエラーメッセージがありますが、その内容はいずれも自明のものであるので、説明は省略します。


2.3. 共通のオプション

mcpp の動作モードや処理系によらない共通のオプションは次の通りです。

-M* オプションは makefile 用の依存関係行を出力するものです。複数のソースファイルがある場合、すべてのソースについてこれらの -M* オプションを付けて実行して、その出力をマージすると、makefile で必要な依存関係記述行がそろいます。これらのオプションは GCC のものに合わせていますが、少し違いがあります。*1

注:

*1 GCC と違うのは次の点である。

  1. -MG オプションはない。オプション指定の仕方が複雑すぎるからである(したがって、どういう仕様かの説明は略)。 しかし、-M オプションでも、インクルードファイルが見つからない場合はエラーにはなるが、依存関係行は出力されるので、それで代用できる。
  2. GCC-specific-build は GCC に合わせてあるが、それ以外では、-MM, -MMD オプションで除外されるヘッダの範囲が広い。

2.4. mcpp のモードによるオプション

mcpp にはいくつかの動作モードがあります。その仕様は 2.1 を参照してください。
このマニュアルでは各種のモードの仕様が並べて記載されているので、少々見にくくなっていますが、がまんしてください。また、以下の説明で出てくる DIGRAPHS_INIT, TRUE, FALSE, etc. の大文字名(__ で始まらないもの)で italic で表示されているものはすべて system.H で定義されるマクロです。これらのマクロはあくまでも mcpp 自身をコンパイルする時に使われるだけで、できあがった mcpp 実行プログラムにはこれらのマクロは残ってはいません。勘違いしないようにしてください。

Standard モードでは次のオプションが使えます。

STD モードでは次のオプションが使えます。

注:

*1 C++ で __STDC__ が定義されているのはトラブルの元であり、良い仕様ではない。GCC のドキュメントによると、ヘッダファイルの多くが __STDC__ が定義されていることを期待しているので、C++ でもこれを定義しておく必要がある、とのことである。しかし、これはヘッダファイルの書き方が悪いと言わざるをえない。C90, C99, C++ に共通の部分には、#if __STDC__ || __cplusplus と書くべきなのである。

*2 C++ Standard では C99 と違って、UCN は大々的な扱いを受けており、中途半端な実装はできない。C 1997/11 draft でもそうであった。しかし、Unicode をそこまで導入することには、実装の負担が大きすぎること等の問題があって疑問だからである。

*3 // は C90(Standard モードの場合)でもコメントとして扱うが、ウォーニングを出す。

*4 これは GCC との互換性のためである。

*5 GCC-specific-build の mcpp をインストールすると、mcpp によるプリプロセス出力が cc1 (cc1plus) に -fpreprocessed というオプションを付けて渡されるようになる。 このオプションは入力がプリプロセスずみであることを cc1 に伝えるものであるが、それでもコメントは処理される。 したがって、-K オプションの結果を cc1 に -fpreprocessed オプションを付けて渡して差し支えない。 また、gcc (g++) コマンドに -save-temps というオプションを付けると、プリプロセス結果の *.i (*.ii) ファイルが残されるので、それを refactoring tool で処理することができる。

*6 -x assembler-with-cpp オプションでは C/C++ のソースではない GCC 用の *.S ファイルがソースファイルとして使われるが、この *.S ファイルのプリプロセスでは、-K オプションを使ってコメントを挿入するとカラム位置がずれるため、アセンブルできなくなるからである。 また、-C オプションによるコメントも -K オプションで挿入されたコメントと紛らわしいことがあるので、同時には使えない。

*7 このオプションを使っても、特殊な場合には正確な対応が維持されないことがある。 <backslash><newline> による行連結と行をまたぐコメントによる行連結が連続する場合や、コメントが 256 行以上続く場合などである。 なお、このオプションを指定してもしなくても、'\v', '\f' は space に変換される。


2.5. 特定の処理系以外の処理系に共通のオプション

UNIX 系のシステムでは次の2つのオプションが使えます。 処理系独立版・GCC 専用版に共通です。 ただし、GCC 専用版では GCC が対応していないとエラーになります。

GCC はオプションが非常に多いので、GCC 専用版ではそれとの衝突を避けるためにいくつかのオプションは他の処理系とは違うものにしています。なお、GCC でコンパイルした mcpp でも compiler-independent 版のオプションは他の処理系でコンパイルしたものと同じです。以下のオプションは GCC 専用版以外に共通のものです。


2.6. 処理系ごとのオプション

特定の処理系でプリプロセッサとして mcpp を使うには、処理系のプリプロセッサのある場所に適当な名前で置いておきます。その時に、処理系付属のプリプロセッサを消してしまうことのないよう、あらかじめ別名のファイルにコピーしておいてください。
Linux, FreeBSD, CygWIN での設定については 3.9.5 を参照してください。 GCC 3.*, 4.* での設定については 3.9.7, 3.9.7.1 を参照してください。MinGW での設定についても 3.9.7.1 を参照してください。

処理系付属のコンパイラドライバからは通常の方法では mcpp に渡す方法のないオプションもあります。
Gcc では -Wp というオルマイティオプションを使うと、どんなオプションでもプリプロセッサに渡すことができます。例えば、

gcc -Wp,-W31,-Q23

とすると、プリプロセッサに -W31 -Q23 というオプションが渡されます。プリプロセッサに渡したいオプションを -Wp, に続けて , で区切って並べます。*1, *2

他の処理系でも、もしコンパイラドライバのソースがあれば、この種のオルマイティオプションを追加したほうが良いでしょう。例えば、-P<opt> と指定すると P をとった -<opt> がプリプロセッサに渡されるようにしておくと、どんなオプションでも使えるようになるので便利です。

もう1つの方法は、先に mcpp でプリプロセスして、その出力ファイルをソースファイルとしてコンパイラに渡すように makefile を書くことです。この方法については 2.9, 2.10 を参照してください。

mcpp の特定の処理系ごとのオプションは以下の通りです。Compiler-independent 版にはもちろんこれらのオプションはありません。

LCC-Win32 版では次のオプションが使えます。

Visual C 版では次のオプションが使えます。

Mac OS X 上の mcpp では GCC-specific-build でも compiler-independent-build でも、次のオプションが使えます。

Mac OS X 上の GCC-specific-build では次のオプションが使えます。

GCC (GNU C) 版では、以下の(この 2.6 セクションの終わりまでの)オプションが使えます。なお、GCC 用では __STDC__ は 1 にしているので、-S1 オプションは指定してもしなくても同じです。

まず、mcpp のモードによらないオプションは次のとおりです。

GCC 版の Standard モードでは次のオプションが使えます。

GCC 版の STD モードでは次のオプションが使えます。

GCC 版の pre-Standard モードでは次のオプションが使えます。

CygWIN / GCC 版では次のオプションが使えます。

次のオプションはいずれもエラーにはしませんが、何も対応しません(ウォーニングを出す場合もある)。

GCC V.3.3 以降ではプリプロセッサがコンパイラに吸収されて独立したプリプロセッサが存在しなくなったため、gcc を -no-integrated-cpp オプションを付けて呼び出しても、プリプロセッサのオプションではないオプションがプリプロセッサに渡されてくることがあります。GCC V.3.3 以降用の mcpp では、次のようなもののうち mcpp の認識しないものはこの種のにせオプションとして無視します。

注:

*1 -Wa はアセンブラ用の -Wl はリンカ用のオルマイティオプションである。 UNIX / System V / cc のマニュアルを見ると、やはりこれらのオプションがある。GCC / cc の -W<x> オプションはこれとの互換性のためのものなのであろう。

*2 GCC V.3 では cpp が cc1 (cc1plus) に吸収されてしまった。そのため、-Wp で指定したオプションは通常は cc1 (cc1plus) に渡されてしまう。プリプロセスを cc1 ではなく cpp (cpp0) にさせるためには、gcc の呼び出しに -no-integrated-cpp というオプションを指定する必要がある。

*3 ただし、GCC V.3.3 以降では大量のマクロが事前定義されるようになったが、これらは事前定義マクロとしては扱わない。すなわち、-dD オプションでもこれらのマクロを出力する。

*4 #pragma MCPP put_defines (#put_defines) の出力は -dM オプションとほぼ同様である。ただし、次の点が違っている。

  1. put_defines では標準事前定義マクロとして規定されているものも、コメントの形で出力する。
  2. put_defines ではそのマクロ定義のあるファイル名と行番号もコメントの形で出力され、また読みやすいように形を整えて出力するが、-d* オプションでは GCC と同じ形式で出力する。この形式を想定している makefile も見掛けるからである。

*5 3.9.6.3 を参照のこと。


2.7. 環境変数

Compiler-independent 版の mcpp では include ディレクトリは UNIX 系 OS での /usr/include, /usr/local/include 以外はデフォルトでは設定されていないので、他のディレクトリも必要であれば、環境変数や実行時オプションで指定しなければなりません。Compiler-independent 版の環境変数は C では INCLUDE、C++ では CPLUS_INCLUDE です。ファイルのサーチはデフォルトではソースファイルのあるディレクトリを基準とします( 4.2 を参照のこと)。しかし、Linux では include ディレクトリに混乱があるので、特別な対策が必要です。それについては 3.9.9 を参照してください。

GCC 専用版でデフォルトで設定されている system include ディレクトリについては、noconfig/*.dif ファイルを見てください。また、include ディレクトリのサーチ順と環境変数の名前については、 4.2 を見てください。

環境変数 LC_ALL, LC_CTYPE, LANG については、 2.8 を見てください。


2.8. Multi-byte character の encoding

mcpp は multi-byte character の多様な encoding に対応しています。

EUC-JP 日本の extended UNIX code (UJIS)
shift-JIS 日本の MS-Kanji
GB-2312 中国の EUC-like な encoding (簡体字)
Big-Five 台湾の encoding (繁体字)
KSC-5601 韓国の EUC-like な encoding (KSX 1001)
ISO-2022-JP1国際規格の日本語
UTF-8 unicode の encoding の1種

そして、実行時に次のようないくつかの方法で実際に使う encoding を指定します。優先順位はこの順のとおりです。

  1. ソース中で #pragma __setlocale( "<encoding>") で指定された encoding (Visual C 用では #pragma setlocale( "<encoding>") )。これを使うと、1本のソースファイルの中でも複数の encoding を使うことができる。
  2. 実行時オプション -e <encoding> または -finput-charset=<encoding> で指定された encoding。
  3. 環境変数 LC_ALL, LC_CTYPE, LANG で指定された encoding(優先順位はこの順)。
  4. mcpp をコンパイルする時に設定されたデフォルトの encoding。
#pragma __setlocale、-e オプション、環境変数で指定できる <encoding> は原則として共通で、次のとおりです。右辺の <encoding> は左辺の encoding を指定します。<encoding> は大文字・小文字の区別をしません。また、'-', '_' は無視します。さらに '.' があると、そこまでをすべて無視します。したがって、たとえば EUC_JP, EUC-JP, EUCJP, euc-jp, eucjp, ja_JP.eucJP はすべて同じものとして扱われます。また、* は任意の文字を意味します(iso8859-1, iso8859-2 等は iso8859* にマッチする)。

EUC-JP eucjp, euc, ujis
shift-JIS sjis, shiftjis, mskanji
GB-2312 gb2312, cngb, euccn
BIG-FIVE bigfive, big5, cnbig5, euctw
KSC-5601 ksc5601, ksx1001, wansung, eucKR
IS0-2022-JP1iso2022jp, iso2022jp1, jis
UTF-8 utf8, utf
なし c, en*, latin*, iso8859*

C, en* (english), latin*, iso8859* のどれかを指定すると、multi-byte character は認識されなくなります。ASCII ではない ISO-8859-* の Latin 系の single-byte character を使う時は、これを指定します。#pragma __setlocale( "") と空の名前を指定すると、デフォルトの encoding に戻ります。

このほか、Visual C++ 用の #pragma setlocale に限って、次のものも使えます。これらは Visual C++ との互換性のために用意しているものです。Visual C++ ではコンパイラがこちらでないと認識しないので、こちらを使ったほうが良いでしょう('-' は mcpp では省略できるが、Visual C++ のコンパイラに対しては省略できない)。Visual C++ では C, english も使えます。

shift-JISjapanese, jpn
GB-2312 chinese-simplified, chs
BIG-FIVE chinese-traditional, cht
KSC-5601 korean, kor

Visual C++ では、Windows がどの国語用であるかによってデフォルトの multi-byte character encoding が変わることになっています。また、Windows の「地域と言語のオプション」の指定によっても変わりますが、この機能は中途半端でやっかいです。しかし、#pragma setlocale による指定はそれらに優先します。

GCC は shift-JIS, ISO2022JP, BIG-FIVE のように multi-byte character が 0x5c の値を持つバイトを含む encoding はうまく処理できない場合があるので、GCC 版では mcpp がそれを補います。*1

注:

*1 GCC を configure する時に --enable-c-mbchar というオプションを付けると、その GCC では環境変数 LANG を C-EUCJP, C-SJIS, C-JIS のどれかにセットすることによって encoding を指定できることになっている。 しかし、この configuration は 1998 からあるもののようであるが、実際には使われておらず、使っても正しく動作しない。 GCC 版の mcpp ではこれら LANG=C-SJIS 等の環境変数をサポートしていたが、V.2.7 で廃止した。
GCC では LANG のほかに LC_ALL, LC_CTYPE でも encoding を指定できることになっているが、実際には診断メッセージが変わるだけである。


2.9. ワンパスコンパイラで mcpp を使うには

Visual C, Borland C, LCC-Win32 のようにプリプロセッサがコンパイラから独立していないいわゆる「ワンパスコンパイラ」が多くなっています。処理速度を上げるためだと思われます。しかし、プリプロセスに要する時間は現在のハードウェアでは小さなものになっています。また、そもそもプリプロセスというものは実行時環境や処理系からはほぼ独立した共通のフェーズであることに大きな意味があるので、「ワンパスコンパイラ」が多くなるのは決して良いことだとは思えません。プリプロセスの処理系依存の仕様も増える結果になります。

ともあれワンパスコンパイラでは、プリプロセッサを mcpp に置き換えることができません。したがって、mcpp を使うには、まず mcpp でソースをプリプロセスし、その出力をコンパイラに渡しますが、コンパイラによって再度ムダにプリプロセスがされることになります。ムダですが、やむをえません。それでも、mcpp を使うことはソースチェックのために有効であり、処理系付属のプリプロセッサにはない機能を使うこともできます。
ワンパスコンパイラで mcpp を使うには、この手順を makefile に書く必要があります。そのサンプルとしては、mcpp 自身のコンパイルに使う visualc.mak, borlandc.mak, lcc_w32.mak 等の makefile のリコンパイル用の設定を見てください。

なお、GCC 3, 4 ではコンパイラがプリプロセス機能を内蔵するようになったものの、外部プリプロセッサを使うオプションも用意されているので、それを活用することで mcpp を問題なく使うことができます(3.9.7 参照)。


2.10. 統合開発環境で mcpp を使うには

GUI のいわゆる「統合開発環境」(IDE) というものは処理系独自の仕様であり、内部的なインタフェースも通常は公開されていないので、その中で mcpp を使うには困難があります。その上、コンパイラがワンパスコンパイラであると、そこに mcpp を使うフェーズを挿入するのはさらに困難です。

ここでは Windows 上の Visual C++ 2003, 2005, 2008 の IDE で mcpp を使う方法を説明します。Borland C 版や LCC-Win32 版は、コマンドラインで使ってください。

また、Mac OS X の Xcode.app / Apple-GCC で mcpp を使う方法も説明します。

2.10.1. Visual C++ の IDE で mcpp を使う方法

Visual C++ の IDE は内部的なインタフェースが公開されておらず、しかもコンパイラがワンパスコンパイラなので、通常の「プロジェクト」では mcpp を使うことができません。しかし、mcpp を使う makefile を書いておけば、それを取り込んで「メイクファイルプロジェクト」を作成することができます。そして、ソースの編集や検索や、さらにソースレベルデバッグ機能を含む IDE の大半の機能を使うことができます。

「メイクファイルプロジェクト」を作るには次のようにします。この方法は 「Visual C++ .net 2003 ドキュメント」および「Visual C++ 2005 Express Edition」「Visual C++ 2008 Express Edition」の「ヘルプ」の「メイクファイルプロジェクトの作成」に書いてあるものです。

  1. IDE のデバッグ機能を使う権限を持つユーザとしてログインする。*1
  2. mcpp を使う makefile を書いておく(noconfig/visualc.mak を参照)。*2
  3. Visual Studio を起動する。*3
  4. 「新しいプロジェクト」をクリックし、 現れた「新しいプロジェクト」のウィンドウで「メイクファイル プロジェクト」を選び、「プロジェクト名」と「場所」を指定して「OK」をクリックする。
  5. すると、「メイクファイル アプリケーション ウィザード」のウィンドウが開くので、「アプリケーションの設定」をクリックし、そこで「ビルドコマンドライン」「出力」「クリーンコマンド」(または「消去コマンド」)「リビルドコマンド」の欄を入力する。これらの用語はわかりにくいが、mcpp 自身の compiler-independent 版のコンパイルを例にとると次のようなことである(生成する mcpp の実行プログラムの名前を mcpp.exe とする)。
    「ビルドコマンドライン」:   nmake
    「出力」                :   mcpp.exe
    「クリーンコマンド」    :   nmake clean
    「リビルドコマンド」    :   nmake PREPROCESSED=1
    
    mcpp の Visual C 専用版をコンパイルする場合は COMPILER=MSC というオプションを付け加えて次のようにする。
    「ビルドコマンドライン」:   nmake COMPILER=MSC
    「出力」                :   mcpp.exe
    「クリーンコマンド」    :   nmake clean
    「リビルドコマンド」    :   nmake PREPROCESSED=1 COMPILER=MSC
    
    「メイクファイルプロジェクト」では make install に相当するコマンドがないので、「ビルドコマンドライン」「リビルドコマンド」で指定されるコマンドでは install も実行されるように makefile を書いておく必要がある。*4
    mcpp をコンパイルするのでなければ、「ビルドコマンドライン」と「リビルドコマンド」とは通常は同一で良い。
    これらを入力したら、「完了」をクリックする。
  6. すると、 「ソリューションエクスプローラ」にプロジェクトが現れるので、その「ソースファイル」というフォルダをクリックして、そして、メニューの「プロジェクト」から「既存項目の追加」を選び、ソースファイルをすべて選択して「OK」する。すると、「ソリューションエクスプローラ」にソースファイル名が現れる。

これで、「編集」「ビルド」「リビルド」「デバッグ」等の機能がすべて使えるようになります。

注:

*1 VC 2003, 2005 でデバッグ機能を使うためには、WindowsXP Pro, Windows2000 では "Debugger users" というグループにユーザを所属させる必要がある。WindowsXP HE ではそういうグループはないので、管理者としてログインしなければならない。
VC 2008 ではユーザグループの制限はなくなった。

*2 ソースレベル・デバッグ機能を使うためには cl.exe の呼び出しに -Zi オプションを付加して、デバッグ情報が生成されるように makefile を書いておく必要がある。

*3 「スタート」->「プログラム」から起動すると、インクルードディレクトリ等の環境変数が設定されない。これを設定するには先に「Visual Studio コマンドプロンプト」を開いて、ソースファイルのあるディレクトリに移動し、そこから VC 2003 では

devenv *.sln /useenv

として、VC 2005, 2008 express edition では

vcexpress *.sln /useenv

として起動しなければならない。

*4 インストールするディレクトリにはユーザが書き込みの権限を持っていなければならない。 処理系の bin, lib 等のディレクトリに書き込む時は、そのパーミッションを管理者権限で変更しておく必要がある。 "Power users" または "Authenticated users" というグループにユーザを登録し、このグループに該当のディレクトリへの「書き込み」「変更」の権限を持たせるのが良い。 もう1つの方法は、「共有ディレクトリ」のような、ユーザが書き込み権限を持つディレクトリに処理系をインストールすることである。

2.10.2. Mac OS X / Xcode.app で mcpp を使う方法

Mac OS X の IDE である Xcode.app は mcpp をインストールした状態で問題なく使うことができます。*1

Xcode.app はなぜか /usr/bin ではなく /Developer/usr/bin にある gcc (g++) を使います(/Developer は Xcode のデフォルトのインストールディレクトリ)。 したがって、mcpp を使うには、その gcc (g++) 用の GCC-specific-build をインストールする必要があります。 そのためには次のようにします(${mcpp_dir} は mcpp のソースのあるディレクトリ)。

export PATH=/Developer/usr/bin:$PATH
configure ${mcpp_dir}/configure --enable-replace-cpp
make
sudo make install

PATH の設定以外は /usr/bin のコンパイラにインストールする場合と同じなので、クロスコンパイラへのインストールや universal binary をインストールする方法などについては、INSTALL-jp を見てください。

こうして mcpp をインストールしておけば、あとは mcpp のための特別な設定はせずに Xcode.app が使えます。 Xcode.app は Apple-GCC 特有な *.hmap という名前の "header map file" なるものを生成しますが、これも mcpp で処理されます。 ただし、mcpp は precompiled header の処理はしないので、#include *.pch は通常のヘッダファイルとして読み込まれます。 また、mcpp は Objective-C, Objective-C++ の処理はしないので、*.m, *.mm のソースファイルのプリプロセスは mcpp を通らずに cc1obj, cc1objplus に直接、渡されます。

mcpp 独自のオプションを使う場合は、Xcode.app の画面トップのメニューバーの「プロジェクト」から「プロジェクト設定を編集」を選んでクリックし、そこで現れるプロジェクトエディタのウィンドウで「ビルド」ペインを選んで、「その他の C フラグ」の項目を編集します。 オプションは次の例のように -Wp, に続けてコンマで区切りながら指定します。

-Wp,-23,-W3

注:

*1 ここで取り上げるのは Mac OS X Leopard / Xcode 3.0 である。


3. 拡張機能と互換性

mcpp にはいくつかの固有の拡張機能があります。また、各処理系付属のプリプロセッサにはそれぞれの拡張機能がありますが、それらの一部は mcpp では使えません。ここではこうした拡張機能と互換性の問題を説明します。

なお、Standard モードでは #pragma 行は原則としてそのまま出力します。mcpp 自身が処理するものについても同様です。同じ #pragma がコンパイラ本体にとっても意味を持つ可能性があるためです。
しかし、#pragma MCPP で始まる行は mcpp 専用のものなので出力しません。GCC 版では、#pragma GCC に poison, dependency, system_header のどれかが続く行も出力しません。また、#pragma once, #pragma push_macro, #pragma pop_macro もコンパイラ本体にとっては無用なので出力しません。 他方で、#pragma GCC visibility * はコンパイラとリンカのためのものなので、出力します。*1

EXPAND_PRAGMA == TRUE でコンパイルされた mcpp では #pragma 行の引数はマクロ展開の対象となります(実際には EXPAND_PRAGMA == TRUE は Visual C, Borland C 版だけである)。ただし、#pragma に STDC, MCPP, GCC のどれかが続く行は展開しません。

#pragma sub-directive は implementation-defined ですが、そのため同じ名前の sub-directive が処理系によって異なる意味を持つ恐れがあります。名前の衝突を避ける工夫が必要です。また、EXPAND_PRAGMA == TRUE の場合は、#pragma sub-directive の名前自身がマクロ展開されては困るので、ユーザの名前空間と重ならないようにする仕組みも必要です。mcpp 固有の sub-directive が #pragma MCPP で始まりマクロ展開されないのはこのためです。C99 で規定された #pragma STDC や GCC 3 の #pragma GCC の方法を採り入れたものです。
ただし、#pragma once は多くの処理系に実装されて名前が衝突する恐れはなくなっているので、この名前のまま実装しています。また、#pragma __setlocale はコンパイラ本体に対しても必要なので MCPP という名前は付けず、"__" を先頭に付けてユーザの名前空間と重ならないようにしています。

*1 mcpp の GCC-specific-build では、#pragma GCC で始まる pragma のうち system_header だけサポートしている。poison, dependency はサポートしない。


3.1. #pragma MCPP put_defines, #pragma MCPP preprocess 等

#pragma MCPP put_defines, #pragma MCPP preprocess, #pragma MCPP preprocessed は Standard モードのもので、#put_defines, #preprocess, #preprocessed は pre-Standard モードのものです。以下では #pragma を例にとって説明します。

#pragma MCPP put_defines ディレクティブに出会うと mcpp は、その時点で定義されているすべてのマクロを #define 行の形で出力します。もちろん、#undef されたものは出てきません。__STDC__ 等の #define, #undef の対象にできないものは、一応 #define 行の形をとって、しかしコメントマークで囲んで出力されます(__FILE__, __LINE__ はマクロ呼び出し時に動的に定義される特殊なマクロなので、ここで出力される置換リストは無意味なものである)。
pre-Standard モードおよび POSTSTD モードでは、function-like マクロ定義のパラメータ名は記憶しません。そこでこのディレクティブでは、パラメータ名は第1パラメータから順に機械的に a, b, c, ... という名前で表示します。27 個目以降のパラメータには a1, b1, c1, ..., a2, b2, c2, ... という名前を使います。

mcpp を入力ファイルも出力ファイルも指定せずに起動して、キーボードからいきなり

#pragma MCPP put_defines

と打ち込むと、事前定義マクロをすべて知ることができます。それぞれのマクロ定義のあるソースファイル名と行番号を表示するコメントも出力されます。-S1, -N 等のオプションを付けて起動すると、それぞれ事前定義マクロが違ってくることがわかります。

#pragma MCPP preprocess というディレクティブに出会うと mcpp は、

#pragma MCPP preprocessed

という行を出力します。これは、このソースファイルはプリプロセス済みであることを示すものです。

#pragma MCPP preprocessed というディレクティブに出会うと mcpp は、そのソースファイルは mcpp によってプリプロセス済みであると判断して、#define 行が出てくるまでは入力をそのまま出力にコピーします。そして、#define 行が出てくると、あとはすべて #define 行であると判断して、マクロを定義します。コメント中にあるソースファイル名と行番号の情報も記憶します。*1, *2
#pragma MCPP preprocessed の有効範囲はそのディレクティブのあるソースファイルのその行以降だけです。そのソースファイルが #include されたものである場合は、include 元に戻ると通常のプリプロセスに戻ります。

注:

*1 実際の処理はもう少し複雑である。#pragma MCPP preprocessed があると入力行の大半をそのまま出力にコピーするが、標準事前定義マクロは #define 行がコメントマークに囲まれているので、その行は捨てる。また、特定の処理系用の版では #line 行は処理系のコンパイラ本体が受け取れる形式に変換して出力する。

*2 したがって、pre-preprocess してもマクロ定義の場所の情報は失われない。

3.1.1. ヘッダファイルの pre-preprocess

上記のディレクティブを利用すると、ヘッダファイルの「プリプリプロセス」をすることができます。「プリプリプロセス」をしておくと、本番のプリプロセス時間がかなり短縮されます。その方法は、上記の仕様ですでにわかったかと思いますが、念のために mcpp 自身のソースを例にとって説明します。

mcpp のソースには8本の *.c ファイルがあり、そのうちの7本はどれも "system.H" と "internal.H" を include しています。そして、他のヘッダは include していません。もっと正確に言うと、ソースではこうなっています。

#if PREPROCESSED
#include    "mcpp.H"
#else
#include    "system.H"
#include    "internal.H"
#endif

そして、system.H は noconfig.H または configed.H といくつかの標準ヘッダを include しています。mcpp.H は私の提供するソースにはありません。これが、これから生成する "pre-pre-processed" header なのです。

mcpp.H を生成するには(もちろん noconfig.H 等の設定がすんでから)、

mcpp > mcpp.H

として mcpp を起動します(GCC 等では、-b オプションも付ける)。
そして、キーボードから

#pragma MCPP preprocess
#include "system.H"
#include "internal.H"
#pragma MCPP put_defines

と打ち込み、end-of-file を入力して mcpp を終了します。

これで mcpp.H ができあがりました。これは system.H, internal.H をプリプロセスしたものの末尾に #define 行の集合を付け加えたものです。これを include すれば、system.H, internal.H を include したのとまったく同じ効果を得ることができます。そして、これは標準ヘッダを含む元のヘッダファイルの総計の数分の1のサイズになっています。#if とコメントが消えているからです。これを7本の *.c ファイルで include するのは、system.H, internal.H を7回 include するのに比べて、はるかに短い時間ですみます。#pragma MCPP preprocess を使うことでさらに時間が短縮されます。

本番のコンパイルでは -DPREPROCESSED=1 というオプションを付けます。
この手順は何かのファイルに書いておいて、makefile でそれを参照するのが良いでしょう。mcpp のソースに付けた makefile と preproc.c には、それが書いてあるので、そちらを見てください。
Visual C, Borland C, LCC-Win32 のような1パス・コンパイラでは独立したプリプロセッサの使い道は制限されますが、その場合でもこの機能は有用です。

このヘッダファイルの pre-preprocess の機能は、GCC / cpp の -dD オプションの機能を真似たものです。ただし、次の点が違っています。

  1. GCC は行番号情報を #line 123 "filename" ではなく # 123 "filename" の形で出力する。このため、それを GCC で再処理することはできるが、Standard C のプリプロセッサではできない。
  2. GCC の古い cpp では #define 行は出現したところで出力されるが、#undef 行は出力されない。したがって、これを再処理すると元ソースの意図と異なる結果になることがあった。
  3. GCC にはない #pragma MCPP preprocess を使うことで、さらに速度が速くなる。
Pre-preprocess の機能としては、mcpp のほうが間違いがなく実用的です。

3.2. #pragma once

#pragma once は Standard モードで使えます。
GCC, Visual C, LCC-Win32 および Wave という単体プリプロセッサでも #pragma once は使えます。
ヘッダファイルを1回しかインクルードしたくない時に使います。ヘッダファイルの中に

#pragma once

と書いておくと、そのファイルをインクルードする #include 行が何回出てきても、最初の1回しかインクルードしません。

通常は、処理系付属の標準ヘッダでは

#ifndef __STDIO_H
#define __STDIO_H
/* stdio.h の中身   */
#endif

等の皮でくるんで多重定義を防いでいますが、それと似た機能です。しかし、マクロを使う方法ではヘッダファイルを読まないですますことはできません(スキップする部分でも、#if, #endif 等が出てくるのを監視するために、全部読まなければならない。行頭の # がディレクティブ行(# に preprocessing directive が続く行)の指示であるかどうかを確かめるためにはコメントも処理しなければならないし、そのためには文字列リテラルも判断しなければならない等で、結局、全部読んで tokenization の大半までやらなければならないのである)。
#pragma once は、ファイルへのアクセスさえもしないですますものです。その結果、多重 include がある場合の処理速度がやや速くなります。

Header name が同じであるかどうかは、サーチしたパスのディレクトリ部分も含めて文字の比較で判断します。ただし、Windows では大文字・小文字は区別しません。したがって、"/DIR1/header.h" と "/DIR2/header.h" は別のものとして扱い、"header.h" と "HEADER.H" とは Windows では同じもの、UNIX 系では別のものとして扱います。ディレクトリは絶対パスに変換して記憶します。"foo/../" といった冗長な部分は削除して正規化します。UNIX 系では symbolic link はリンク先に変換します。したがって、同じファイルであるかどうかは確実に判定されます。*1, *2, *3

この #pragma once は GCC V.1.* / cpp の #pragma once のアイデアを借用したものです。GCC V.2.*, V.3.* でもこの機能は残っていますが、obsolete なものとされています。#pragma once がなくても、ヘッダファイルの全体が #ifndef _MACRO, #define _MACRO, #endif で囲まれていれば、cpp がこれを記憶し、1回しか include しないという仕様に変更されています。
しかし、GCC V.2, V.3 の仕様は、GCC の使用を前提としない市販の処理系などでは使えないことがあります。標準ヘッダの書き方が違っているからです。また、実装も GCC V.2, V.3 の仕様のほうが面倒です。そこで、mcpp では #pragma once だけを実装しています。

他のプリプロセッサでも同じヘッダファイルを使う場合はこれだけに頼るわけにはゆきません。マクロを使う方法と併用して、ヘッダファイルを次のような皮でくるんでおくのが良いでしょう。

#ifndef __STDIO_H
#define __STDIO_H
#pragma once
/* stdio.h の中身   */
#endif

ただし、<assert.h> には #pragma once は書いてはいけません(その理由は cpp-test.html#5.1.2 参照)。C++ の <cassert>, <cassert.h> 等も同様です。

もう一つの問題は、GCC / GLIBC の最近のシステムでは <stddef.h> のように、他の system header から繰り返し #include されるヘッダファイルがあることです。多くの system header が __need_NULL, __need_size_t, __need_ptrdiff_t, etc. のマクロを定義しては <stddef.h> を #include します。そのたびに、<stddef.h> では NULL, size_t, ptrdiff_t, etc. が定義されてゆきます。<errno.h>, <signal.h> 等も同様です。<stdio.h> でさえも、他の system header が __need_FILE, __need___FILE 等のマクロを定義しては #include <stdio.h> し、そのたびに FILE 等が定義されてゆく場合があります。これらのファイルには #pragma once は書き込むわけにはいきません。*4

注:

*1 正規化された結果は #pragma MCPP debug path で見ることができる。3.5.1 参照。 #pragma MCPP put_defines や診断メッセージのファイル名の表示でも、これが使われる。
しかし、#line 行の path-list は一般には正規化されているわけではなく、include directory だけ正規化した状態で表示される。 ただし、-K オプションでは正規化して表示される。 このモードを利用する他のツールの処理を用意にするためである。

*2 CygWIN では /bin と /usr/bin、/lib と /usr/lib はそれぞれ同じディレクトリで、/ が Windows 上の例えば C:/dir/cygwin だとすると /cygdrive/c/dir/cygwin もそれと同じディレクトリであるが、mcpp ではこれらはすべて同じディレクトリとして扱う。path-list はすべて /cygdrive/c/dir/cygwin/dir-list/file の形に変換する。

*3 MinGW では / と /usr とは実際には同じディレクトリで、/ が Windows 上の C:/dir/msys/1.0 だとすると /c/dir/msys/1.0 もそれと同じディレクトリで、/mingw が C:/dir/mingw だとすると /c/dir/mingw もそれと同じディレクトリである。mcpp ではこれらはそれぞれ同じディレクトリとして扱う。path-list はすべて c:/dir/msys/1.0/dir-list/file, c:/dir/mingw/dir-list/file の形に変換する。

*4 少なくとも Linux / GCC 2.9x, 3.*, 4.* / glibc 2.1, 2.2, 2.3 ではそうなっている。FreeBSD 4, 5, 6 では glibc は使われていないので、こういう複雑なシステムヘッダにはなっていない。

3.2.1. ヘッダファイルに #pragma once を書き込むツール

これを書き込むのはヘッダファイルの数が少なければ大したことではありませんが、数が多いと手ではうっとおしい作業になります。そこで、これを自動的に書き込む簡単なツールを用意しました。

tool/ins_once.c は古い GCC のシステム用です。Borland C 5.5 でも標準ヘッダの書き方は同じルールに従っているので、これを使うことができます。Glibc 2 のようなシステムでは上記のように例外が多いので、使わないほうが無難です。
ただし、これが使えるシステムでも、ヘッダファイルの中には GCC の慣習に従っていないものも散見されます。そうしたヘッダでは GCC の、1回しか読み込まないという機能も動作しません。
そこで、ins_once.c をコンパイルして、UNIX なら /usr/include, /usr/local/include 等のディレクトリで、まず

chmod -R u+w *

とした上で、

ins_once -t *.h */*.h */*/*.h

とします。そうすると、#ifndef または #if !defined で始まらないヘッダファイルが報告されます。それらのヘッダを手で修正してください。それから、

ins_once *.h */*.h */*/*.h

とすると、各ヘッダファイルの最初に出現する #directive が #ifndef または #if !defined であった場合は、その直後に #pragma once 行が書き込まれます。(これができるのは root と特定のユーザだけのはず。さらに必要なら chmod -R u-w * として、access permission を元に戻しておく)。

ins_once には次のようなオプションがあります。システムに合わせて適当なオプションを選択してください。

ins_once は複数回実行しても同じファイルにダブって書き込むことはないように、簡単なチェックはしています。しかし、厳密なものではありません。

この ins_once は間に合わせなので、tokenization はほとんどやっていません。FreeBSD 2.0, 2.2.7, Borland C 5.5 の各ヘッダファイルでは期待通りの動作をしましたが、特殊なヘッダファイルがあると誤動作するかもしれません。ins_once は必ずバックアップをとってから実行してください。
ワイルドカードは shell に展開させてください(バッファがオーバーフローする場合は、何回かに分けて実行する)。


3.3. #pragma MCPP warning, #include_next, #warning

これらのディレクティブは GCC との互換性のために用意されているものです。GCC には #include_next, #warning という規格違反のディレクティブがあります。規格違反ですが、これを使っているソースも稀にあります。Glibc 2 のシステムでは、システムヘッダファイルにこれを使っているものさえあります。そこで mcpp では、これらのソースをコンパイルできるようにするため、GCC 用に限って #include_next, #warning を実装しています。ただし、Standard モードではウォーニングの対象となります。Standard モードでは #pragma MCPP warning も実装しています。これは GCC 用に限りません。

#include_next   <header.h>

は include directory をサーチする際に、この include 元のファイルのあるディレクトリをスキップして、その次のサーチ順のディレクトリからサーチを始めます。

CygWIN, MinGW では、ヘッダ名の大文字・小文字の区別は無視します。

#pragma MCPP warning    any message

#warning    any message

では、any message をそのまま warning として標準エラー出力に出力します。しかし、これは #error と違ってエラーにはなりません。


3.4. #pragma MCPP push_macro, #pragma __setlocale 等

これらは Visual C に mcpp を移植した時に実装し、ついでに他の処理系でも使えるようにしたものです。

#pragma MCPP push_macro( "MACRO"), #pragma MCPP pop_macro( "MACRO") は、その時点での MACRO のマクロ定義をスタックに "push" したり "pop" したりするものです。
Visual C では #pragma push_macro( "MACRO"), #pragma pop_macro( "MACRO") も使えます。
push_macro ではそのマクロの定義が退避され、pop_macro で元に戻されますが、push してもそのマクロ定義はまだ有効です。これを無効にするためには #undef するか、または別の定義で再定義する必要があります。push_macro では同じ名前のマクロを何重にも push することができます。

#pragma __setlocale( "<encoding>") は multi-byte character encoding を <encoding> に変更します。__setlocale の引数は文字列リテラルでなければなりません。<encoding> については 2.8 を見てください。これを使うと、1つの translation unit の中でも複数の encoding を使うことができます。
Visual C++ では #pragma setlocale であり、#pragma __setlocale は使えません。Encoding の指定は mcpp だけでなく、コンパイラにも伝える必要がありますが、コンパイラが認識できるのは #pragma setlocale だけだからです。

#pragma __setlocale を認識できるコンパイラ本体はいまのところ、ありません。


3.5. #pragma MCPP debug, #pragma MCPP end_debug, #debug, #end_debug

#pragma MCPP debug, #pragma MCPP end_debug は Standard モードのものです。pre-Standard モードでは #debug, #end_debug となります。

#pragma MCPP debug <args> ディレクティブはソース中の任意の行に書くことができます。<args> でデバッグ情報の種類を指定します。1つの #pragma MCPP debug ディレクティブで複数の <arg> を指定することができます。必ず1つ以上の <arg> 指定が必要です。このディレクティブがあると、そこからデバッグ情報の出力が始まります。そして、#pragma MCPP end_debug <args> で、<args> のデバッグ情報出力が解除されます。#pragma MCPP end_debug では、<args> を省略することができます。その場合は、設定されていたすべてのデバッグ情報出力が解除されます。mcpp でサポートしない引数が <args> にあった時は、ウォーニングを出しますが、その前にあった正しい引数は有効です。
デバッグ情報はすべて、プリプロセスの本来の出力と同じパスに出力されます。これは本来の出力と同期させるためです。したがって、このディレクティブがあると通常はコンパイルできません。 ただし、#pragma MCPP debug macro_call では情報はコメントに埋め込んで出力されるので、それを再プリプロセスしてコンパイルすることができます。

プリプロセスの結果に疑問がある場合、その部分を

#pragma MCPP debug token expand
/* デバッグしたい部分   */
#pragma MCPP end_debug

というふうにはさんで使います。

元来は mcpp 自身のデバッグ用のものですが、プリプロセスの過程をトレースしたい時に使えます。元来の目的が目的なので、ソースを見ないと理解できないところもあり、少々うるさくもありますが、がまんしてください。

<arg> の種類は次の通りです。

path include ファイルのサーチパスを表示する
token token を1つずつ切り分けて、その種類を表示する
expand マクロ呼び出しの展開過程をトレースする
macro_callマクロ定義およびマクロ呼び出しに際して、そのソース上の行とカラム位置をコメントに埋め込んで出力する
if #if (#elif, #ifdef, #ifndef) の真偽を表示する
expression#if 式の評価をトレースする
getc プリプロセスを 1 byte ずつトレースする
memory mcpp の使っているヒープメモリの状況を表示する

3.5.1. #pragma MCPP debug path, #debug path

このディレクティブに出会うと mcpp は、まず設定されている include ディレクトリのサーチパスを優先順位の高いものから順に表示します(ただし、最初にサーチされるカレントディレクトリおよびソースのディレクトリは省略)。

さらに、#include 行があると、そのヘッダファイルを include するために mcpp が実際にサーチしたディレクトリが(カレントディレクトリ等も含めて)すべて表示されます。
#pragma once のあるヘッダファイルを再度 #include した場合は、その旨を表示します。
また、mcpp は path-list 中の "foo/../" といった冗長な部分を削除して正規化しますが、その結果、元の path-list と違った path-list となった場合は、その旨を表示します。
UNIX 系のシステムでは mcpp は symbolic link は link 先に変換しますが、この場合もその旨を表示します。

3.5.2. #pragma MCPP debug token, #debug token

まず、読み込んだソース行を表示した上で、mcpp が token を1つ読むたびに、その token と種類を表示します。Token とは正確に言えば preprocessing-token (pp-token) のことです。ソースを読む時ばかりではなく、mcpp がマクロ展開などで内部的に読み返す pp-token も、そのつど(繰り返して)表示されます。

ただし、1 byte の pp-token のうち次のものは表示されません。これはプログラムのつごうによるものです。

  1. プリプロセスディレクティブ行の開始の '#'
  2. function-like マクロ定義のパラメータリスト開始の '('
  3. function-like マクロ定義のパラメータを区切る ','
  4. function-like マクロ呼び出しの引数リスト開始の '('

Pp-token の種類は次の通りです。

(NAM) identifier
(NUM) preprocessing-number
(OPE) operator or punctuator
(STR) string literal
(WSTR)wide string literal
(CHR) character constant
(WCHR)wide character constant
(SPE) $, @ 等の特殊な pp-token
(SEP) token separator (white space)

これらのうち (SEP) は改行コード以外は通常は表示されません。 改行コード等のコントロールコードは <^J>, <^M> 等と表示されます。

3.5.3. #pragma MCPP debug expand, #debug expand

マクロ呼び出しの展開過程をトレースします。

Standard モードの #pragma MCPP debug では次の通りです。
マクロ呼び出しがあると、まずそのマクロの定義が表示されます。さらに、引数が読み込まれ、置換リスト中のパラメータと置き換えられ、再走査されるようすが、逐一表示されます。マクロ定義がネストされていれば、それが順次再走査されて展開されていきます。引数中にマクロがあれば、この過程が再帰的に(パラメータとの置換の前に)トレースされます。

表示は mcpp 自身のいくつかの関数への出入りのたびに、その関数名とともに行われます。これらの関数は次のような役割をするルーチンです。Standard モードのソースを参照すると、さらによく理解できます。

expand_macroマクロ展開の入り口ルーチン
replace マクロを1レベル展開する
collect_args引数を集める
prescan 置換リストを走査して #, ## 演算子の処理をする
substitute パラメータを引数で置換する
rescan 置換リストを再走査する

これらのうち、expand_macro 以外は互いに間接再帰の関係にあります。

replace, collect_args では、mcpp が内部的にスタックに積んでいる展開途中のデータも表示されます。これらのデータでは、mcpp の内部的なコードが次のような記号で表示されます。

<n> n 番目のパラメータ
<TSEP> pp-token を区切るために mcpp が挿入した token separator
<MAGIC> 同名マクロの再置換を禁止するコード
<RT_END>置換リストの終わりを示すコード
<SRC> Identifier がソースファイルから取り込まれたことを示すコード

このうち <SRC> は STD モードでだけ使われ、POSTSTD モードでも COMPAT モードでも使われません。

#pragma MCPP debug token も指定したほうが、わかりやすいでしょう。

#pragma MCPP debug macro_call または -K オプションも指定した場合は、macro notification がコメントに書き込まれて出力されますが、replace() 以下のルーチンではまだコメントは書き込まれず、何種類かの magic character(内部的なコード)が書き込まれたり削除されたりします。 これは次のように表示されます。

<MACm> 1つのマクロ呼び出しの中に含まれる m 番目のマクロの呼び出し
<MAC_END> 直前の MACm で始まるマクロ呼び出しの終わり
<MACm:ARGn>m 番目のマクロの呼び出し中の n 番目の引数
<ARG_END> 直前の MACm:ARGn で始まる引数の終わり

さらに -v オプションも指定した場合は、MAC_END, ARG_END についても開始マーカと同じ番号が symmetrical に表示されます。

pre-Standard モードの #debug expand では Standard モードとは内部ルーチンが大幅に違っています。説明は略します。

3.5.4. #pragma MCPP debug if, #debug if

#if, #elif, #ifdef, #ifndef の行を表示し、その評価が真であるか偽であるかを報告します。スキップされる #if section 内では、報告されません。

3.5.5. #pragma MCPP debug expression, #debug expression

#if, #elif 行の式の評価を詳細にトレースします。

これは DECUS cpp 自身のデバッグ用にオリジナル版以来あるもので、私はほとんど手を加えていません。内部的な関数名ばかりか、変数名とその値までズラズラと出てきます。mcpp のソースを追いながらでないと、変数は理解できません。
しかし、複雑な式の値が評価用のスタックに積み降ろしされていくようすは、ソースを見なくても何とか理解できるでしょう。

3.5.6. #pragma MCPP debug getc, #debug getc

mcpp 内の get_ch() という1バイト読み込み関数が呼び出されるたびに、詳細なデータを出力します。ただし、Standard モードでは pp-token をスキャンする時は、その1バイト目しかこのルーチンは呼び出されません。

#debug getc では token をスキャンする最中もこのルーチンが呼び出されるので、とんでもない量のデータが吐き出されます。

いずれにしても、膨大なデータが出力されます。使う必要はまずありません。

3.5.7. #pragma MCPP debug memory, #debug memory

このディレクティブがあると、その時点で mcpp が内部的に使っている malloc(), realloc(), free() によるヒープメモリの状況を1回だけ報告します。これは私の作った kmmalloc や他の何種類かの malloc() を使っている場合だけの機能です( mcpp-porting.html#4.extra 参照)。他の malloc() の場合はエラーにはしませんが、何も報告しません。
このディレクティブが解除されないまま mcpp が終了すると、その時に再度ヒープメモリの状況が報告されます。mcpp が out of memory で終了した場合も同様です。

3.5.8. #pragma MCPP debug macro_call

Macro notification mode を開始します。 このモードでは、マクロが定義されるたびに、またマクロが展開されるたびに、そのソース上の行とカラム位置がコメントに埋め込まれて出力されます。 マクロが引数を持つ場合は、各引数についてもその位置が報告されます。 ただし、マクロ展開によってトークンが連結された場合は、連結前のトークンに関するマクロ情報は失われます。
さらに、#undef, #if (#elif, #ifdef, #ifndef), #endif についても、簡単な情報が出力されます。
このモードは -K オプションで指定することもできます。

このモードは C/C++ の refactoring tool のために実装されたものです。 C/C++ にはプリプロセスというフェーズのあることが refactoring tool にとって困難な問題を引き起こしますが、このモードを使えばプリプロセス後の出力からソースを再構成することができるので、tool を作成しやすくなります。*1

#pragma MCPP debug expand と似ていますが、expand のほうはマクロの展開過程をトレースするためのもので、詳細な情報が出力されるものの、それをコンパイルすることはできません。 それに対して、macro_call はソース上の正確な位置を伝えるためのもので、情報をコメントに埋め込むので、出力をそのまま再プリプロセスしてコンパイルすることができるのが特徴です。

注:

*1 この仕様は主として Taras Glek の提案によるものである。 彼自身は次のところで mozilla のソースの refactoring に取り組んでいる。

http://blog.mozilla.com/tglek/

3.5.8.1. #define に関するコメント

例えば、ソースの冒頭に次のようなマクロ定義があると、

#define NULL 0L
#define BAR(x, y) x ## y
#define BAZ(a, b) a + b

次のようなコメントが出力されます。

/*mNULL 1:9-1:16*/
/*mBAR 2:9-2:25*/
/*mBAZ 3:9-3:24*/

このフォーマットは

/*m[NAME] [start line]:[start column]-[end line]:[end column]*/

を意味します。 行とカラムはともに 1 から始まります。 -K オプションを指定した場合は事前定義マクロも出力されますが、事前定義マクロには位置情報はありません。

3.5.8.2. #undef に関するコメント

#undef  BAZ

という行では、次のようなコメントが出力されます。

/*undef 10*//*BAZ*/

/*undef [lnum]*//*[NAME]*/ というフォーマットで、[lnum] はその行の行番号を、[NAME] はそのマクロ名を示します。

3.5.8.3. マクロ展開に関するコメント

マクロが呼び出されるたびに、それを挟んでその開始と終了を示すマーカが出力されます。 このマーカのフォーマットは HTML に似たネスト可能なもので、/*<...*/ がマクロ展開の開始を示し、/*>*/ がその終了を示します。 マクロ開始のフォーマットはマクロ定義のフォーマットの /*m/*< に変えた次の形をとります。

/*<[NAME] [start line]:[start column]-[end line]:[end column]*/

マクロが引数をとる場合は、ソース上の引数の位置を示すマーカと、引数の展開の開始と終了を示すマーカも出力されます。 引数の位置を示すフォーマットは /*!...*/ という形をしています。 引数中にマクロがある場合は、再帰的にそのマクロの情報が出力されます。 ただし、そのマクロがソース上にあったものでなければ、位置情報は出力されません。 どのマクロの何番目の引数であるかを示すために、引数の id が次の形で示されます。

[func-like-macro-name]:[nesting level]-[argument number]

これによって BAZ(BAZ(a,b), c) といったネストされた同名のマクロとその引数も互いに区別することができます。 引数の番号は 0 から始まります。 そして、これに次の形の位置情報が続きます。

[start line]:[start column]-[end line]:[end column]

また、引数の展開の開始を示すマーカは次の形です。

/*<[func-like-macro-name]:[nesting level]-[argument number]*/

引数の展開の終了を示すマーカはマクロ展開の終わりのマーカと同じ /*>*/ です。

すなわち、次のソースは

foo(NULL);
foo(BAR(some_, var));
foo = BAZ(NULL, 2);
bar = BAZ(BAZ(a,b), c);

次のような結果を出力します。

foo(/*<NULL 4:5-4:9*/0L/*>*/);
foo(/*<BAR 5:5-5:20*//*!BAR:0-0 5:9-5:14*//*!BAR:0-1 5:16-5:19*/some_var/*>*/);
foo = /*<BAZ 6:7-6:19*//*!BAZ:0-0 6:11-6:15*//*!BAZ:0-1 6:17-6:18*//*<BAZ:0-0*//*<NULL 6:11-6:15*/0L/*>*//*>*/ + /*<BAZ:0-1*/2/*>*//*>*/;
bar = /*<BAZ 7:7-7:23*//*!BAZ:0-0 7:11-7:19*//*!BAZ:0-1 7:21-7:22*//*<BAZ:0-0*//*<BAZ 7:11-7:19*//*!BAZ:1-0*//*!BAZ:1-1*//*<BAZ:1-0*/a/*>*/ + /*<BAZ:1-1*/b/*>*//*>*//*>*/ + /*<BAZ:0-1*/c/*>*//*>*/;

さらに -v オプションが指定されている時は、マクロ展開の終わりのマーカでも引数の展開の終了を示すマーカでも、開始マーカにあるのと同じマクロ名と引数の id が出力されます。 すなわち、次のようになります。

foo(/*<NULL 4:5-4:9*/0L/*NULL>*/);
foo(/*<BAR 5:5-5:20*//*!BAR:0-0 5:9-5:14*//*!BAR:0-1 5:16-5:19*/some_var/*BAR>*/);
foo = /*<BAZ 6:7-6:19*//*!BAZ:0-0 6:11-6:15*//*!BAZ:0-1 6:17-6:18*//*<BAZ:0-0*//*<NULL 6:11-6:15*/0L/*NULL>*//*BAZ:0-0>*/ + /*<BAZ:0-1*/2/*BAZ:0-1>*//*BAZ>*/;
bar = /*<BAZ 7:7-7:23*//*!BAZ:0-0 7:11-7:19*//*!BAZ:0-1 7:21-7:22*//*<BAZ:0-0*//*<BAZ 7:11-7:19*//*!BAZ:1-0*//*!BAZ:1-1*//*<BAZ:1-0*/a/*BAZ:1-0>*/ + /*<BAZ:1-1*/b/*BAZ:1-1>*//*BAZ>*//*BAZ:0-0>*/ + /*<BAZ:0-1*/c/*BAZ:0-1>*//*BAZ>*/;

この例でもわかるように、マクロ展開終了のマーカも引数展開終了のマーカも、それぞれその前にある最後の同じネストレベルの開始マーカに対応しています。 したがって、-v オプションを指定しなくても、自動的に対応関係を判断することができます。

3.5.8.4. #if (#elif, #ifdef, #ifndef) に関するコメント

#if (#elif, #ifdef, #ifndef) 行に関しては、その行にあるマクロ等の情報が出力されます。 例えば、このソースを bar.h とし、

#define NULL 0L
#define BAR(x, y) x ## y
#define BAZ(a, b) a + b

こちらを foo.c とすると、

#include "bar.h"
#ifdef  BAR
#ifndef BAZ
#if 1 + BAR( 2, 3)
#endif
#else
#if 1
#endif
#if BAZ( 1, BAR( 2, 3))
#undef  BAZ
#endif
#endif
#endif

foo.c は次のような結果を出力します。

#line 1 "/dir/foo.c"
#line 1 "/dir/bar.h"
/*mNULL 1:9-1:16*/
/*mBAR 2:9-2:25*/
/*mBAZ 3:9-3:24*/
#line 2 "/dir/foo.c"
/*ifdef 2*//*BAR*//*i T*/
/*ifndef 3*//*BAZ*//*i F*/


/*else 6:T*/
/*if 7*//*i T*/
/*endif 8*/
/*if 9*//*BAZ*//*BAR*//*i T*/
/*undef 10*//*BAZ*/
#line 11 "/dir/foo.c"
/*endif 11*/
/*endif 12*/
/*endif 13*/

すなわち、まず /*if [lnum]*/ というフォーマットで、ディレクティブ名に続いて現在の行番号が表示されます。 そして、その行にマクロがあれば、それが1つずつ /*[NAME]*/ というフォーマットで表示されます。 最後に、/*i T*/ または /*i F*/ で、その行の評価が true であるか false であるか、すなわちその #if で始まるブロックがコンパイルされるブロックかスキップされるブロックかが示されます。 地の文と違って、マクロの展開結果は表示されません。 #if 1 のようにマクロのない行については、マクロを表示する /*[NAME]*/ がないだけです。

#elif, #ifdef, #ifndef についても同様に /*elif [lnum]*/, /*ifdef [lnum]*/, /*ifndef [lnum]*/ で始まり、マクロが定義されていれば /*[NAME]*/ が続き、/*i T*/ または /*i F*/ で終わります。

スキップされるブロック中のディレクティブについては何も表示されません。

3.5.8.5. #else, #endif に関するコメント

#else 行では上の例のように、/*else [lnum]:[C]*/ というフォーマットで情報が表示されます。 [lnum] は行番号、[C] は T または F で、その #else - #endif ブロックがコンパイルされるブロックかそれともスキップされるブロックかを示します。

#endif 行では上の例のように、/*endif [lnum]*/ というフォーマットで、現在の行番号が表示されます。 これはもちろん、まだ #endif に対応づけられていない最後の #if (#ifdef, #ifndef) に対応します。

3.5.8.6. #line の出力

なお、macro notification mode では #line 行のファイル名の出力がデフォルトの場合と違い、#include 行の指定を full-path-list に正規化したものが出力されます(3.2 参照)。 Refactoring tool の作成を容易にするためです。


3.6. #assert, #asm, #endasm

#assert は pre-Standard モードでだけ使えます。GCC 版では実装されません。Standard C の #error に対応する機能です。Standard C で

#if ULONG_MAX / 2 < LONG_MAX
#error  Bad unsigned long handling.
#endif

とするところを

#assert LONG_MAX <= ULONG_MAX / 2

と書けます。引数を #if 式として評価し、真(non-zero)であれば何もせず、偽(0)であれば

Preprocessing assertion failed

という言葉に続いてその行(行接続とコメント処理をした後の行)を表示します。 これはエラーとしてカウントしますが、処理は中止しません。

この #assert は System V や GCC の #assert とは、まったく別のものです。

#asm, #endasm の2つのディレクティブ行ではさまれたブロックはアセンブラソースとして扱われます。pre-Standard モードでだけ使えます。ただし、これは Microware C / 6809 用に書かれたものなので、他の処理系にも移植するには、system.c の do_old(), do_asm(), put_asm() に書き足す必要があります。
#asm ブロックについては、trigraphs の変換と <backslash><newline> の削除はしますが、コメントの処理や token チェックや文字チェックはせず、行頭の space も削除せず、たまたまマクロと同じ名前があってもマクロ展開せず、ソースの行をそのまま出力します。その他のディレクティブ行は #asm ブロック内では意味を持ちません。

この #asm, #endasm は Standard C では認められないものです。まず、#pragma sub-directive 以外の拡張ディレクティブが規格外ですが、そればかりか、#pragma asm, #pragma endasm と名前を変えても解決しません。と言うのは、Standard C ではソースはすべてCの token sequence で成り立っている必要がありますが(厳密に言えば preprocessing token sequence)、アセンブラプログラムはCの token sequence ではないからです。Standard C でアセンブリコードを使うには、それを文字列リテラルという token に埋め込む方法しかありません。そして、それを処理する組み込み関数をコンパイラ本体に実装して、

asm(
    " leax _iob+13,y\n"
    " pshs x\n"
);

といった形で呼び出すのです。

やや長いコードだとこんなことはやっていられないので、 その場合はその部分を別の関数にして、ライブラリ関数を書く時のように、別のファイルでアセンブラプログラムそのものを書いて、アセンブルパスで処理し、それをリンクして使うことになります。これは窮屈な制限のように思えるかもしれませんが、portable なCプログラムを書くにはアセンブラの部分は完全に分離する必要がありますから、むしろ #asm を使わずに別ファイルで書くようにしたほうが良いでしょう。


3.7. C99 の新機能(_Pragma() 演算子、可変引数マクロ等)

これは Standard モードでだけ使えます。
-V199901L オプションで __STDC_VERSION__ を 199901L 以上にすると、C99 の次の機能が有効になります。
C++ でも -V199901L オプションで __cplusplus を 199901L 以上にした場合は同様です。1, 7 以外の仕様は C++ Standard にはありませんが、Standard モードでは C99 との互換性を高めるために、このオプションを用意しています。
ただし、可変引数マクロは Standard モードでは C90 および C++ でも使えるようにしてあります。*1

  1. // から行末までをコメントとして扱う。
  2. 可変引数マクロが使える。
  3. Preprocessing-number の中に e+, E+, e-, E- と同様に p+, P+, p-, P- という sequence も認める。これは浮動小数点数のビットパターンを 0x1.FFFFFEp+128 というふうに、16進で表記するためのものである。
  4. _Pragma() operator が有効になる。
  5. EXPAND_PRAGMA というマクロを TRUE に定義してコンパイルされた mcpp では、#pragma 行の引数は、STDC, MCPP, GCC のどれかで始まるのでない限りマクロ展開の対象となる(デフォルトでは EXPAND_PRAGMA == FALSE であり、マクロ展開しない。展開するのは Visual C, Borland C 版だけである)。
  6. #if 式は long long のある処理系では long long / unsigned long long で評価する。
  7. 識別子・文字定数・文字列リテラル・pp-number の中にある \unnnn, \Unnnnnnnn の形の UCN という escape sequence を通す。これは Unicode の文字の値を意味する。#if 式では UCN の値は16進表記として評価する。(ただし、POSTSTD モードでは UCN は使えない)。

可変引数マクロというのは、次のようなものです。

#define debug(...)  fprintf(stderr, __VA_ARGS__)

というマクロ定義があると、

debug( "X = %d\n", x);

というマクロ呼び出しは次のように展開されます。

fprintf(stderr, "X = %d\n", x);

すなわち、パラメータ・リスト中の ... が1個以上のパラメータを意味し、置換リスト中の __VA_ARGS__ がそれに対応します。そして、マクロ呼び出し時には ... に対応する引数が複数あっても、それらを , を含めて連結したものが一つの引数のように扱われます。

_Pragma 演算子は _Pragma( "foo bar") と書くと #pragma foo bar と書いたのと同じ効果を持つ演算子です。引数は文字列リテラルまたはワイド文字列リテラル1個でなければなりません。ワイド文字列であれば接頭子 L を削除し、文字列リテラルを囲む " を削除し、文字列リテラルの中の \", \\ をそれぞれ ", \ に置き換えたものが #pragma の引数として扱われます。
#pragma はソースの論理行1行の初めから終わりまでに書かなければならず、引数が(少なくとも C90 では)マクロ展開されないのに対して、_Pragma() 演算子はソースのどこに書いても独立した論理行に #pragma を書いたのと同じ効果を持ち、マクロの置換リスト中に書くこともでき、マクロ展開の結果として生成された _Pragma() operator も有効です。この柔軟性を利用することで、広い portability を持った pragma directive を書くことができ、処理系による #pragma の違いを1つのヘッダファイルで吸収することもできます。(サンプルとしては "Validation Suite" の pragmas.h, pragmas.t を参照のこと)。*2

なお、C99 では #if 式の型はその処理系の最大の整数型となっています。long long / unsigned long long は必須とされているので、#if 式の型は long long / unsigned long long またはそれ以上ということになります。C90, C++98 では #if 式は long / unsigned long で評価することになっています。しかし、mcpp は C90, C++98 でも long long / unsigned long long で評価し、long / unsigned long の範囲を超える値に対してはウォーニングを出します。*1

注:

*1 これは GCC, Visual C 2005, 2008 等との互換性のためである。他の処理系でも、 C99 の仕様を一挙に実装するのは難しいので、__STDC_VERSION__ を 199409L 等としたままこうした一部の仕様から実装してゆくことが予想される。

*2 C99 では #pragma の引数が STDC で始まる場合はマクロ展開されないが、そうでない場合は implementation-defined である。


3.8. 処理系ごとの特殊な仕様

mcpp の compiler-specific-build にはそれぞれの処理系に固有の仕様がいくつか実装されていますが、そのうち実行時オプションでも #pragma でもない特殊なものをこの節で説明します。

3.8.1. GCC, Visual C の可変引数マクロ

GCC は V.2 のころから 3.9.1.6 にあるような独自の仕様の可変引数マクロを持っています。 これをこのマニュアルでは GCC2 仕様の可変引数マクロと呼びます。 また GCC V.3 では 3.9.6.3 のような新しい仕様が実装されました。 これをここでは GCC3 仕様の可変引数マクロと呼びます。 GCC 2.95 では C99 仕様の可変引数マクロも実装されましたが、glibc や Linux のシステムヘッダでは C99 仕様のものはおろか GCC3 仕様のものさえ使われず、いまだに GCC2 仕様が使われています。

GCC-specific-build の mcpp は V.2.6.3 から STD モードに限って GCC3 仕様の可変引数マクロを実装し、V.2.7 からは GCC2 仕様のものも実装しました。 これは Linux 等で使う際の不自由を避けるために実装したものです。 ただし、ウォーニングが出ます。 GCC 仕様は portability がないだけでなく、文法的に汚いので、新しく書くソースでは使うべきではありません。 ことに GCC2 仕様がそうです。

Visual C は 2003 までは可変引数マクロは実装していませんでしたが、2005 で実装されました。 これは C99 の仕様に GCC3 仕様と似た修正を加えたもので、可変引数が欠如していた場合は、その直前のトークンがコンマであればそれを削除します。 ただし、GCC と違って '##' という記号は使いません。 この仕様は Visual C のドキュメントによると、次の例のような結果になります。 この3番目の例ではコンマは削除しないとされています。 しかし、Visual C 2005, 2008 は実際にはこのコンマも削除してしまいます。

#define EMPTY
#define VC_VA( format, ...)     printf( format, __VA_ARGS__)
VC_VA( "var_args: %s %d\n", "Hello", 2005);     /* printf( "var_args: %s %d\n", "Hello", 2005); */
VC_VA( "absence of var_args:\n");               /* printf( "absence of var_args:\n");   */
VC_VA( "empty var_args:\n", EMPTY);             /* printf( "empty var_args:\n", );  */  /* trailing comma   */

mcpp は V.2.7 から Visual C-specific-build の STD モードにこの仕様を実装しました。 ただし、ウォーニングが出ます。 上記の3つ目の例ではコンマは削除せず、仕様通りに実装しています。

3.8.2. GCC の 'defined' の処理

マクロ中に 'defined' というトークンが出てくるケースでは、GCC はそのマクロが #if 行にある場合は、他の場合と異なった恣意的な処理をします。 この問題については 3.9.4.6 で検討しているので、参照してください。

mcpp の GCC-specific-build の STD モードでは V.2.7 から GCC と同じ処理をするようにしました。 Linux の一部のシステムヘッダなどでこの種の間違ったマクロが使われているのに対処するためです。 ただし、ウォーニングが出ます。 自分の書くプログラムでは正しい #if 式を書くようにしてください。

3.8.3. Borland C の asm 文その他の特殊な構文

Borland C には asm というキーワードがあって、

asm {
    mov x,4;
    ...;
}

といった形でアセンブリコードを記述するようになっていますが、これはC言語の文法からはずれた極めて変則的なものです。この中にたまたまマクロと同じ名前があると、それはマクロ展開されてしまいます。Borland C そのものでも mcpp でも、その点は同じです。アセンブラプログラムは別の .asm ファイルで書くのが本当でしょう。 mcpp はこれについては特別な扱いは何もしません。

Visual C++ にも __asm という同様のキーワードがあります。
GCC には asm( " mov x,4\n") というまっとうな形の組み込み関数が用意されています。

3.8.4. #import その他

3.8.4.1. Mac OS X / GCC の #import

GCC には Objective-C の #import というディレクティブがあり、C/C++ でもこれを #pragma once を暗黙に宣言した #include として使えます。 Mac OS X の C/C++ ソースには時にこれを使うものがあるようです。

mcpp V.2.7 以降は Mac OS X に限って、GCC 版でもコンパイラ独立版でも、これを実装しています。

3.8.4.2. Visual C の #import, #using

Visual C には #import と #using という特殊なディレクティブがあります。 プリプロセスディレクティブの形をしていますが、内容はコンパイラとリンカに対する指示です。 #import は GCC のものとは関係ありません。

mcpp の Visual-C-specific-build はこれらの行をそのまま出力します。


3.9. GCC の問題と GCC との互換性

GCC 専用版の mcpp では、GCC / cpp (cc1) との互換性を実用上あまり不便がない程度に確保していますが、非互換な面も多々あります。
まず実行時オプションについては、2 章に見るようにいろいろ違いがあります。-A オプション等は実装していません。また、ディレクティブでは #assert, #ident 等は実装していません。*1
しかし、幸いなことに、これらのことが原因でコンパイルできないというソースはごく少ないようです。

むしろ実際に問題となるのは、古いプリプロセッサの特殊な仕様をあてにしたソースです。これらの多くは GCC で -pedantic を指定するとウォーニングが出ます。mcpp は Standard モードのものではエラーチェックを規格通りに実装しているので、ほぼ GCC の -pedantic がデフォルトとなっています。GCC はデフォルトではそうした規格違反ソースを黙って通すため、それをあてにしたソースが一部に見られます。そうしたソースを規格に合致するように書くのはきわめて簡単なことで、わざわざ規格違反の書き方をする必然性は何もありません。単に移植性を損なうだけで、悪くするとバグの温床となるので、見つけしだい直しておきましょう。*2

注:

*1 これらはいずれも必要なら #pragma で実装すべきものである。 #include_next, #warning も同様であるが、GCC のシステムでは実際に時々使われているので、mcpp でも GCC 版に限って実装した。ただし、ウォーニングの対象になる。

*2 3.9 から 3.9.3 は 1998 年に書かれたものであるが、その後は状況が変わってきている。 古い仕様に依存するソースは少なくなり、それに代わって GCC 独自の拡張機能や実装の細部に依存するものが増えてきている。 3.9.4 以降、ことに 3.9.8 以降で取り上げるのは主としてそうした問題である。 (2008/03)

3.9.1. FreeBSD 2 / kernel ソースのプリプロセス

以下に、FreeBSD 2.2.2-R (1997/05) の kernel ソースを例に問題点を挙げておきます。ディレクトリ名はいずれも /sys (/usr/src/sys) 中のものです。これらのうち 7, 8 は必ずしも規格違反ではなく、mcpp でも期待通りの処理をします。しかし、あぶなっかしい書き方なのでウォーニングが出ます。6 は拡張機能で、C99 でも同じ機能が用意されていますが、GCC / cpp とは記法が異なります。

3.9.1.1. 行をまたぐ文字列リテラル

i386/apm/apm.c, i386/isa/npx.c, i386/isa/seagate.c, i386/scsi/aic7xxx.h, dev/aic7xxx/aic7xxx_asm.c, dev/aic7xxx/symbol.c, gnu/ext2fs/i386-bitops.h, pc98/pc98/npx.c にはこういう書き方でアセンブラソースが埋め込まれています。

asm("
    asm code0
#ifdef PC98
    asm code1
#else
    asm code2
#endif
    ...
");

文字列リテラルを閉じる " が行末までになかった場合は行末で閉じられていると解釈するのが GCC / cpp のデフォルトの仕様ですが、それを使っているのです(さらにコンパイラ本体では asm() の中身全体が行をまたぐ文字列リテラルと解釈されるらしい)。

アセンブラソースは .s ファイルとして切り離しておくのが良いスタイルだと思われますが、どうしても .c ファイルに埋め込みたければ、こんなあぶなっかしいことをしなくても、次のようにすればすみます。これであれば Standard C 準拠のプリプロセッサでも問題ありません。

asm(
    "    asm code0\n"
#ifdef PC98
    "    asm code1\n"
#else
    "    asm code2\n"
#endif
    "    ...\n"
);

3.9.1.2. #else junk, #endif junk

ddb/db_run.c, netatalk/at.h, netatalk/aarp.c, net/if-ethersubr.c, i386/isa/isa.h, i386/isa/wdreg.h, i386/isa/tw.c, i386/isa/b004.c, i386/isa/matcd/matcd.c, i386/isa/sound/sound_calls.h, i386/isa/pcvt/pcvt_drv.c, pci/meteor.c, pc98/pc98/pc98.h にはこういう行があります。

#endif MACRO

これはこうしておきましょう。

#endif /* MACRO */

3.9.1.3. #ifdef 0

i386/apm/apm.c にはなんと

#ifdef 0

という奇怪な行があります。

もちろん、

#if 0

の間違いです。実際には使われていない、 デバッグもされていないソースなのでしょう。

3.9.1.4. マクロの二重定義

gnu/i386/isa/dgb.c では次の行が何かのヘッダファイルと矛盾する二重定義になります。

#define DEBUG

Standard C では二重定義は violation of constraint で、実際には処理系によって、エラーにした上で初めの定義を有効とするものと、GCC 2 / cpp のようにデフォルトでは黙ってあとの定義を有効とするものとあります。確実にあとの定義を有効にするには、直前に

#undef DEBUG

を入れるべきです。

3.9.1.5. #warning

i386/isa/if_ze.c, i386/isa/if_zp.c には #warning があります。Kernel ソース中で唯一の規格違反ディレクティブです。Standard C に合わせるためには、この行をコメントアウトするしかありません。

mcpp の GCC 版では #warning が使えるので、このまま通ります。

3.9.1.6. 可変引数マクロ

gnu/ext2fs/ext2_fs.h, i386/isa/mcd.c には次のような可変個引数のマクロが定義されています。

#define MCD_TRACE(fmt, a...)                \
{                                           \
    if (mcd_data[unit].debug) {             \
        printf("mcd%d: status=0x%02x: ",    \
            unit, mcd_data[unit].status);   \
        printf(fmt, ## a);                  \
    }                                       \
}

#   define ext2_debug(fmt, a...)    { \
        printf ("EXT2-fs DEBUG (%s, %d): %s:", \
            __FILE__, __LINE__, __FUNCTION__); \
        printf (fmt, ## a); \
        }

これは GCC / cpp 独自の拡張仕様で、他の処理系では通用しません。この ## a のところは単に a とする書き方もあります。## があると、マクロ呼び出しで a... に対応する引数がなかった場合は、その直前のコンマを削除します。
C99 では可変個引数マクロが追加されていますが、記法が異なり、これらの例は次のように書くことになります。

#define MCD_TRACE( ...)                     \
{                                           \
    if (mcd_data[unit].debug) {             \
        printf("mcd%d: status=0x%02x: ",    \
            unit, mcd_data[unit].status);   \
        printf( __VA_ARGS__);               \
    }                                       \
}

#   define ext2_debug( ...)         { \
        printf ("EXT2-fs DEBUG (%s, %d): %s:", \
            __FILE__, __LINE__, __FUNCTION__); \
        printf ( __VA_ARGS__); \
        }

C99 では ... に対応する呼び出し時の引数は1個以上必要なのに対して、GCC / cpp では a... に対応する引数は0個でもかまわないというのが、やっかいな相違点です。mcpp ではこれに対処するため、... に対応する引数が1個もない場合は、warning は出すもののエラーにはしないようにしています。したがって、次のような書き方もできます。このほうが書き換えは一対一対応でできるので、簡単です。しかし、この書き方では、カラ引数の直前のコンマが残るので、たとえば printf( fmt, ) 等という展開結果になってしまうことがあります。その場合は、マクロ定義を上記の C99 仕様の書き方にするか、またはマクロ呼び出しでカラ引数を使わないようにするしかありません。カラ引数の代わりには NULL や 0 のような無害なトークンを使って、MCD_TRACE(fmt, NULL) 等と書くことになります。*1

#define MCD_TRACE(fmt, ...)
{
    if (mcd_data[unit].debug) {
        printf("mcd%d: status=0x%02x: ",
            unit, mcd_data[unit].status);
        printf(fmt, __VA_ARGS__);
    }
}

#   define ext2_debug(fmt, ...)         {
        printf ("EXT2-fs DEBUG (%s, %d): %s:",
            __FILE__, __LINE__, __FUNCTION__);
        printf (fmt, __VA_ARGS__);
        }

注:

*1 GCC 2.95.3 以降では C99 の構文の可変引数マクロも実装されているので、こちらを使うほうが良い。GCC の可変引数マクロは引数がゼロ個でも良いという柔軟性があるが、その記法は良くない。args... というパラメータでは args... とはくっついていなければならないが、こういう pp-token は存在しない。置換リストでトークン連結演算子と同じ記法を別の用途に使っているのも、感心しない。C99 の記法で、ゼロ個の可変引数も許容するという仕様が妥当であろう。
なお、GCC 3 では可変引数マクロについて、GCC 2 以来の仕様と C99 の仕様との折衷的な書き方が追加された。それについては 3.9.6.3 を参照のこと。

3.9.1.7. マクロ呼び出しのカラ引数

nfs/nfs.h, nfs/nfsmount.h, nfs/nfsmode.h, netinet/if_ether.c, netinet/in.c, sys/proc.h, sys/socketvars.h, i386/scsi/aic7xxx.h, i386/include/pmap.h, dev/aic7xxx/scan.l, dev/aic7xxx/aic7xxx_asm.c, kern/vfs_cache.c, pci/wd82371.c, vm/vm_object.h, vm/device/pager.c にはこういうマクロ呼び出しがあります。/usr/include/nfs/nfs.h でも同様です。

LIST_HEAD(, arg2)
TAILQ_HEAD(, arg2)
CIRCLEQ_HEAD(, arg2)
SLIST_HEAD(, arg2)
STAILQ_HAED(, arg2)

第一引数がカラなのです。カラ引数は C99 では公認されましたが、C90 では undefined です。ネストされたマクロ呼び出しでたまたま引数がカラになった場合のこと等を考えると、カラ引数が規定されているほうが良いと言えますが、ソース中にカラ引数を書くことにはこれらの場合は必然性がなく、感心しません。引数が1つのマクロではカラ引数と引数の欠落との区別がつかないという syntax のあいまいさがあることも、忘れてはなりません。

こう書くほうが良いでしょう。これであれば Standard C 準拠のどのプリプロセッサでも問題ありません。

#define EMPTY

LIST_HEAD(EMPTY, arg2)
TAILQ_HEAD(EMPTY, arg2)
CIRCLEQ_HEAD(EMPTY, arg2)
SLIST_HEAD(EMPTY, arg2)
STAILQ_HAED(EMPTY, arg2)

ところで、これらのヘッダファイルの中には、これらのマクロの定義もなければ他のどのヘッダも #include されていないというものがあります(nfs ディレクトリのもの)。これらのマクロの定義は sys/queue.h にあり、*.c プログラムがそちらを先に #include することを期待しているのです。あぶなっかしい書き方のヘッダ群です。

なお、kern/kern_mib.c には次のようなマクロ呼び出しがあります。

SYSCTL_NODE(, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)

しかし、このカラ引数は EMPTY とするわけにはいきません。このマクロの定義は sys/sysctl.h にあり、次のようになっているからです。

#define SYSCTL_NODE(parent, nbr, name, access, handler, descr)
    extern struct linker_set sysctl_##parent##_##name;
    SYSCTL_OID(parent, nbr, name, CTLTYPE_NODE|access,
        (void*)&sysctl_##parent##_##name, 0, handler, "N", descr);
    TEXT_SET(sysctl_##parent##_##name, sysctl__##parent##_##name);

すなわち、これらの引数はマクロ展開されないのです(この中にある SYSCTL_OID というマクロでも、やはり第一引数等はマクロ展開されない)。これはカラ引数のままにしておくしかありません。*1

注:

*1 C99 ではカラ引数は合法とされている。また、この SYSCTL_NODE(), SYSCTL_OID() のようなマクロのことを考えると、EMPTY というマクロを使う方法は万能ではないので、カラ引数のままにしておくのも一理あることである。EMPTY を使っても、マクロ呼び出しがネストされていると、やはりカラ引数が発生してしまうという問題もある。しかし、ソースの readability を考えると、EMPTY を使えるところでは使ったほうが良いように思われる。

3.9.1.8. Function-like マクロの名前に置換される object-like マクロ

i386/include/endian.h にはこういうマクロ定義があります。/usr/include/machine/endian.h でも同様です(同種の定義が4つある)。

#define __byte_swap_long(x)     (replacement text)
#define NTOHL(x)                (x) = ntohl((u_long)x)
#define ntohl                   __byte_swap_long

問題は ntohl の定義です。これは object-like マクロでありながら function-like マクロの名前に展開され、そのため再走査で続くテキストを巻き込み、あたかも function-like マクロのように展開されます。この展開方法は K&R 1st. 以来の暗黙の仕様で、Standard C ではなぜか合法となったものです。しかし、私が別のところで論じているように、この仕様こそがマクロ展開を無用に複雑にし、規格書にまで混乱をもたらしている元凶であり、「バグのような仕様」なのです。*1

この例は実態は function-like マクロであるものを省略して object-like マクロとして書いているものですが、function-like マクロらしく次のように書いたほうが良いでしょう。これであれば何の問題もありません。

#define ntohl(x)                __byte_swap_long(x)

i386/isa/sound/os.h にも同種のマクロ定義があります。

#define INB                     inb
#define INW                     inb

これはこうしておきましょう。

#define INB(x)                  inb(x)
#define INW(x)                  inb(x)

注:

*1 ISO 9899:1990 の Corrigendum 1:1994 ではこうした例は undefined とされた。そして、C99 では Corrigendum のこの項は別のものと置き換えられた。しかし、規格書はこれに関しては混乱している。詳細は cpp-test.html#2.7.6 を参照。

3.9.1.9. .S ファイルの「プリプロセス」

Kernel ソースには何本かの .S ファイル、すなわちアセンブラソースがあります。ところがこれらには #include や #ifdef があって、「プリプロセス」が必要なのです。FreeBSD 2.2.2-R ではこれらのソースは cc を -x assembler-with-cpp オプションを付けて呼び出すという方法で処理しています。そうすると cc は /usr/libexec/cpp を -lang-asm オプションをつけて呼び出し、さらに as を呼び出します。

こういう .S ファイルはもちろん規格外の変則ソースです。これが意図通りに「プリプロセス」されるためには、マクロとたまたま一致する「名前」がアセンブラソースに含まれていてはいけません。そして、「プリプロセス」では、「トークン」間の white space の有無はそのまま保存されなければならず、行頭の space も削除せずに保存されなければなりません。行の最初の「トークン」がアセンブラのコメントである # である場合はプリプロセッサの側で特殊な処理が必要です。使えるプリプロセッサがかなり制限され、バグの混入にも余計な神経を使わなければならないので、決して良いことだとは思えません。*1

複数のマシンに対応させる等のためにプリプロセスする必要があるのであれば、.S ファイルではなく .c ファイルとして、次の形で書くのが良いでしょう。4.4BSD-Lite では実際にこの書き方になっています。

asm(
    "    asm code0\n"
#ifdef Machine_A
    "    asm code1\n"
#else
    "    asm code2\n"
#endif
    "    ...\n"
);

注:

*1 FreeBSD 2.0-R ではこれらのファイル名は *.S ではなく *.s となっていて、Makefile ではその処理には cc ではなく cpp を呼び出して、次に as を呼び出すようになっていた。そして、cpp を呼び出すと /usr/bin/cpp が動くが、これは shell-script で、/usr/libexec/cpp -traditional を呼び出す。このほうが、script を書き換えることで使うプリプロセッサを変えることができるので、便利であった。

3.9.2. FreeBSD 2 / libc ソースのプリプロセス

FreeBSD 2.2.2R の /usr/src/lib/libc の全ソースもコンパイルしてみましたが、特に問題はありませんでした。大半は 4.4BSD-Lite からそのまま来ているからでしょうか。これだけの規模できれいなソースがそろっているのは珍しいことで、特筆に値します。

ただ1個所、gen/getgrent.c に次のような行が見つかりました。もちろん、行末の ; は余計です。

#endif;

3.9.3. GCC 2 / cpp の仕様の問題

さて、以上に見たように、これらのソースを規格に合致した、より移植性の高い、より安全なスタイルで書くことには、何の面倒もデメリットもありません。にもかかわらず、なぜこうしたソースがいまだに書かれているのでしょうか?

FreeBSD 2.0-R と 2.2.2-R の kernel ソースを比べても、この種のものはあまり減っていません。新しいほど規格合致性が高くなっているとは限らないところが問題なのです。この種のあやしげなスタイルのソースは 4.4BSD-Lite から存在しているものはわずかです。4.4BSD は Standard C と POSIX に準拠して書き直されたからでしょう。ところが、FreeBSD への実装で一部のソースにこうした古いスタイルが復活してしまったのです。上記の ntohl のように 4.4BSD-Lite では ntohl(x) の形になっているものが FreeBSD ではわざわざ ntohl の形に書き替えられているものさえあります。いったん一掃されたものが、なぜ復活するのでしょうか?

私はここには、GCC / cpp がこれらを黙って通してしまうことの悪影響が現れていると思います。-pedantic の動作がデフォルトであれば、こうしたスタイルのソースがわざわざ「新しく」書かれることはなかったでしょう。かつては -pedantic-errors をデフォルトにしたのではコンパイルできないソースが多くて、実用的ではなかったのかもしれません。gcc の man page には -pedantic オプションについて、「これを使う理由は何もない。このオプションの存在理由は pedants を満足させることだけである」とあります(*1)。しかし Standard C が決まって8年もたついまとなっては、-pedantic-errors とまではいかなくても -pedantic をデフォルトにすべき時に来ていると思われます。

FreeBSD 2.0-R ではコメントのネストが時々見られましたが、2.2.2-R では一掃されています。一掃されたのは、GCC / cpp が認めなくなったからです。これは -pedantic とは関係ありませんが、プリプロセッサのソースチェックの威力はそれほど大きいのです。

注:

*1 この 3.9.3 が書かれたのは 1998 のことである。その後、gcc の man や info では、さすがにこの表現は削除された。しかし、仕様が特に変わったわけではない。

3.9.4. Linux / glibc 2.1 ソースのプリプロセス

glibc (GNU LIBC) 2.1.3 (2000/02) のソースをコンパイルしてみました。これには FreeBSD の libc とは違ってかなり多くの問題があります。中には GCC / cpp の undocumented な仕様を利用しているものさえあり、その仕様を突き止めるだけでかなりの時間を費やしてしまったことが何回かありました。

3.9.4.1. 行をまたぐ文字列リテラル

sysdeps/i386/dl-machine.h, stdlib/longlong.h には

#define MACRO asm("
    instr 0
    instr 1
    instr 2
")

といった形の行をまたぐ文字列リテラルがいくつもあります。 中にはかなり長大なものもあります。

また、make によって生成される compile/csu/version-info.h にも行をまたぐ文字列リテラルが現れます。
これはもちろん規格違反のソースですが、GCC ではこれを改行コードの入った文字列リテラルとして扱います。
mcpp では -lang-asm (-x assembler-with-cpp, -a) オプションを指定すると、こうした行をまたぐ文字列リテラルを

#define MACRO asm("\n    instr 0\n    instr 1\n    instr 2\n")

という形に変換して処理します(3.9.1.1 のように途中にディレクティブのはさまっているものは、これでは対応できず、ソースを書き直すしかない)。

3.9.4.2. #include_next, #warning

catgets/config.h, db2/config.h, include/fpu_control.h, include/limits.h, include/bits/ipc.h, include/sys/sysinfo.h, locale/programs/config.h, sysdeps/unix/sysv/linux/a.out.h には #include_next が現れます。

また、sysvipc/sys/ipc.h には #warning があります。

これらは規格では認められていないディレクティブですが、glibc 2 のシステムではことに #include_next は不可欠のものとなってしまっているので、mcpp でも GCC 用では #include_next と #warning は実装しています。

#include_next の問題は規格違反だということだけではありません。Include directories とそのサーチ順は環境変数等のユーザ側の設定によって変わる場合があるので、それによって結果が違ってくる危険があります。

Glibc の include ディレクトリのファイルには、glibc を install すると /usr/include ディレクトリにコピーされるものもあります。すなわち、システムのヘッダファイルとして使われるものなのです。こうしたヘッダファイルに#include_next が使われていることは、システムヘッダがかなりつぎはぎ状態になってきていることを表しています。大整理が必要な時期にきているようです。

3.9.4.3. 可変引数マクロ

elf/dl-lookup.c, elf/dl-version.c, elf/ldsodefs.h, glibc-compat/nss_db/db-XXX.c, glibc-compat/nss_files/files-XXX.c, linuxthreads/internals.h, locale/loadlocale.c, locale/programs/linereader.h, locale/programs/locale.c, nss/nss_db/db-XXX.c, nss/nss_files/files-XXX.c, sysdeps/unix/sysdep.h, sysdeps/unix/sysv/linux/i386/sysdep.h, sysdeps/i386/fpu/bits/mathinline.h

以上のファイルには GCC / cpp の仕様の可変個引数マクロの定義と呼び出しがあります。*1

注:

*1 これは GCC2 からある仕様であるが、そのほか GCC3 では C99 と GCC2 との折衷的な仕様が追加された。 それについては 3.9.6.3 を参照のこと。

3.9.4.4. マクロ呼び出しのカラ引数

catgets/catgetsinfo.h, elf/dl-open.c, grp/fgetgrent_r.c, libio/clearerr_u.c, libio/rewind.c, libio/clearerr.c, libio/iosetbuffer.c, locale/programs/ld-ctype.c, locale/setlocale.c, login/getutent_r.c, malloc/thread-m.h, math/bits/mathcalls.h, misc/efgcvt_r.c, nss/nss_files/files-rpc.c, nss/nss_files/files-network.c, nss/nss_files/files-hosts.c, nss/nss_files/files-proto.c, pwd/fgetpwent_r.c, shadow/sgetspent_r.c, sysdeps/unix/sysv/linux/bits/sigset.h, sysdeps/unix/dirstream.h

以上のファイルにはマクロ呼び出しのカラ引数が現れます。ことに math/bits/mathcalls.h にはカラ引数が 79 個もあります。このヘッダファイルは /usr/include/bits/mathcalls.h に install されて、/usr/include/math.h から #include されるものです。EMPTY というマクロを使っても、マクロ呼び出しがネストされているので、やはり大量のカラ引数が発生します。もっときれいなマクロの書き方はできないものでしょうか。

3.9.4.5. Function-like マクロの名前に置換される object-like マクロ

argp/argp-fmtstream.h, ctype/ctype.h, elf/sprof.c, elf/dl-runtime.c, elf/do-rel.h, elf/do-lookup.h, elf/dl-addr.c, io/ftw.c, io/ftw64.c, io/sys/stat.h, locale/programs/ld-ctype.c, malloc/mcheck.c, math/test-*.c, nss/nss_files/files-*.c, posix/regex.c, posix/getopt.c, stdlib/gmp-impl.h, string/bits/string2.h, string/strcoll.c, sysdeps/i386/i486/bits/string.h, sysdeps/generic/_G_config.h, sysdeps/unix/sysv/linux/_G_config.h

以上のファイルには function-like マクロの名前に置換される object-like マクロの定義があります。中には、math/test-*.c にあるもののように、function-like マクロが object-like マクロに置換されて、さらにそれが function-like マクロの名前に置換されるものもあります。そういう書き方をする必然性があるのでしょうか?

3.9.4.6. 'defined' に展開されるマクロ

sysdeps/generic/_G_config.h, sysdeps/unix/sysv/linux/_G_config.h, malloc/malloc.c には、例えば次のように defined という pp-token に展開されるマクロ定義があります。

#define HAVE_MREMAP defined(__linux__) && !defined(__arm__)

これは、

#if HAVE_MREMAP

というディレクティブがあると

#if defined(__linux__) && !defined(__arm__)

となることを期待しているものです。

しかし、まず #if 行中でマクロ展開の結果に defined という pp-token が出てくるのは、規格では undefined です。そのことは別としても、なおこのマクロは変です。

HAVE_MREMAP というマクロはいったん

defined(__linux__) && !defined(__arm__)         (1)

と置換され、次に identifier である defined, __linux__, __arm__ がそれぞれマクロであるかどうかが調べられ、マクロであれば展開されます。したがって、defined はマクロとして定義されているはずはないので(もし定義されていれば、それ自体がすでに undefined)、仮に __linux__ が 1 に定義されていて、__arm__ が定義されていなければ、このマクロは最終的に次のように展開されます。

defined(1) && !defined(__arm__)

defined(1) はもちろん #if 式の syntax error です。

ところが GCC では、#if 行でなければこうなるのですが、#if 行に限って (1) でマクロ展開をやめてしまい、これを #if 式として評価します。Undefined であるのでそれも間違いとは言えませんが、マクロ展開が #if 行とそうでない場合とで異なるのは、一貫しない仕様です。少なくともその仕様には portability がありません。*1

問題のマクロは次のように書けば、何も問題ないのです。

#if defined(__linux__) && !defined(__arm__)
#define HAVE_MREMAP 1
#endif

こういう危なっかしいソースは早く一掃されてほしいものです。 *2

注:

*1 GCC 2 / cpp は #if 行では内部的に defined を特殊なマクロとして扱っている。 そのため、次のようなトークン列をマクロ展開のために再走査するのであるが、その結果がこれをマクロ展開せずに、#if 式として評価することになるのである。すなわち、マクロ展開と #if 式の評価とが分離せずに混交しているのである。

defined(__linux__) && !defined(__arm__)

これは GCC / cpp のプログラム構造にもかかわる問題である。GCC 2 / cpp では rescan() というマクロ再走査ルーチンが事実上のメインルーチンとなっていて、これがソーステキストを初めから終わりまで読みながら処理してゆく。そして、プリプロセスディレクティブの処理ルーチンもこの中から呼び出されるのである。何でもマクロで実装するのはマクロプロセッサの伝統的なプログラム構造であるが、この構造が、マクロ展開と他の処理との混交を引き起こす背景になっていると考えられる。

*2 glibc 2.4 ではこのマクロは直されている。 ところが、他のマクロで同種のものが新たにいくつも出てきている。

3.9.4.7. .S ファイルの「プリプロセス」

*.S という名前のファイルはプリプロセスを要するアセンブラのソースですが、その中には #include, #define, #if 等のプリプロセスディレクティブが出てきます。さらに、Make によって生成される compile/csu/crti.S というファイルには、

#APP

とか

#NO_APP

という行まで現れます。 これらの行は無効なプリプロセスディレクティブと構文上、区別がつきません。GNU ではこれらの行はそのままプリプロセス後に残って、アセンブラのコメントとして扱われるようです。

また、## 演算子による pp-token の連結が invalid な pp-token を生成してしまうソースもあります。GCC / cpp はこれを黙ってそのまま出力します。

mcpp では GCC / cpp との互換性のためにやむなく、-lang-asm (-x assembler-with-cpp, -a) オプションを付けると、こうした illegal なディレクティブや ## によって生成された invalid な pp-token をエラーにせず、ウォーニングを出すもののそのまま出力するようにしました。

こうしたソースは本来、アセンブラ用のマクロプロセッサで処理すべきものだと思われます。GNU にも gasp というアセンブラ用マクロプロセッサがあるようですが、なぜかほとんど使われていないようです。

3.9.4.8. rpcgen と -dM オプションの仕様の問題

GCC は -dM というオプションで起動するとマクロ定義だけを出力しますが、make check で使われる stdlib/isomac.c はこれを利用しています。isomac.c の問題は、マクロ定義ファイルの形式として GCC の出力形式だけを想定していて、コメントも空行もエラーになってしまうことです。

glibc の make では rpcgen というプログラムが使われることがあります。このプログラムの問題は、プリプロセッサの行番号情報の出力形式としてやはり GCC の

#123 "filename"

という形式だけを想定していて、

#line 123

#line 123 "filename"

もエラーになってしまうことです。

mcpp では GCC 版では GCC の形式をデフォルトにしました。しかし、rpcgen がこういう特殊な形式を前提にして、標準的な形式に対応していないというのは、お粗末な仕様です。

3.9.4.9. -include, -isystem, -I- オプション

glibc 2.1 の makefile では、-include オプションがしばしば使われています。時には -isystem オプションや -I- オプションも使われます。-include はソースの冒頭で #include すればすむもので、-isystem, -I- はシステムヘッダを更新する場合しか必要性の感じられないものです。
mcpp では GCC 用の実装に限って、この3つのオプションも実装しましたが、あまり必要のないオプションは整理してもらいたいものです。*1

注:

*1 GCC / cpp にはこのほかに -iprefix, -iwithprefix, -iwithprefixbefore, -idirafter といった include directory とその順序を指定するオプションがいくつもある。また、long-file-name と MS-DOS 上の 8+3 形式のファイル名との対応表の使用を指定する -remap オプションもある。これらは CygWIN システムの specs ファイル等で使われることがあるが、include directory は環境変数で指定しておけばすむことであり、8+3 形式のファイル名への対応がいまさら CygWIN で必要だとも思えない。

3.9.4.10. Undocumented な事前定義マクロ

これは glibc の問題ではなく、GCC の問題です。

__VERSION__, __SIZE_TYPE__, __PTRDIFF_TYPE__, __WCHAR_TYPE__

以上の名前はドキュメントには見当たりませんが、GCC / cpp では事前定義マクロとなっています。__VERSION__ の値は Vine Linux 2.1 (egcs-1.1.2) では "egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)" となっています。他の3つは Linux / i386 をはじめ多くのシステムではそれぞれ unsigned int, int, long int となっているようです。しかし、FreeBSD, CygWIN では少しずつ違っています(なぜ違う必要があるのかわからないが)。
こういうことがどうしてドキュメントにないのでしょうか。

3.9.4.11. Undocumented な環境変数

もっとも奇怪なのは SUNPRO_DEPENDENCIES という undocumented な環境変数です。sysdeps/unix/sysv/linux/Makefile には

SUNPRO_DEPENDENCIES='$(@:.h=.d)-t $@' \
$(CC) -E -x c $(sysinclude) $< -D_LIBC -dM | \
... \
etc.

という script があります。これは SUNPRO_DEPENDENCIES という環境変数でファイル名を指定し、cpp がソース中のマクロ定義とソースファイルの依存関係行をその指定されたファイルに出力するというものなのです。

この動作を理解するには、GCC / cpp のソース (egcs-1.1.2/gcc/cccp.c) を読むしかありませんでした。
このほか、DEPENDENCIES_OUTPUT という環境変数もあり、同様の意味を持っています。SUNPRO_DEPENDENCIES のほうは system headers の依存関係行も出力するのに対して、DEPENDENCIES_OUTPUT はそうではないという違いがあります。

mcpp では GCC 対応版に限って、この2つの環境変数に対応させましたが、こういう「裏仕様」のようなものは早く廃止してほしいものです。

3.9.4.12. その他の問題

Linux (i386) / GCC ではこのほか、specs ファイルの指定によって、cpp の呼び出しに -Asystem(unix) -Acpu(i386) -Amachine(i386) というオプションが付加されます。しかし、これを利用するソースは少なくとも glibc 2.1.3 の Linux / x86 版にはないようです。
Glibc からインストールされるシステムヘッダがつぎはぎだらけの非常に複雑なものになってきていることは、大きな問題です。ちょっとした設定の違いによって処理結果が違ってくる恐れがあります。

他方で、FreeBSD 2.2.2 / kernel ソースで見られた #else junk, #endif junk やマクロの二重定義は glibc 2.1.3 では見られませんでした。Glibc 2.1 のソースが FreeBSD 2 / kernel のソースより整理されている面もいくらかあります。

しかし全体としては、glibc 2.1 には GCC の特殊な仕様に依存しているソースが稀ならずあり、他の処理系への移植は困難になっています(数千本のソースファイルの中ではごく一部であるが)。プログラムの可読性やメンテナンス性のためにも、こうした GCC local な仕様への依存は好ましくありません。GCC V.3 ではこれらの裏技的仕様を廃止し、それに依存するソースを一掃することを期待したいと思います。

3.9.5. GCC 2 で mcpp を使うには

mcpp を glibc 2.1 のコンパイルに使うには、まず一部のソースの修正が必要です。*1
一つは可変個引数のマクロの定義と呼び出しです。上記 3.9.4.3 にある 14 個のファイルについて、3.9.1.6 にあるような形で修正します。もちろん、元のファイルも残しておいたほうが良いでしょう。
もう一つは、3.9.4.6 にある3つのファイルの、置換リストに "defined" が出てくるマクロの修正です。また、/usr/include/_G_config.hsysdeps/unix/sysv/linux/_G_config.h が install されてできる同一のファイルですが、こちらも修正しておいたほうが良いでしょう。

mcpp の起動には、Makefile や specs ファイルで付加されるオプションのほかに、行をまたぐ文字列リテラルやアセンブラ用のコメントを含む *.S ファイルのために -lang-asm (-x assembler-with-cpp) が必要です。このオプションは他のファイルのプリプロセスにも付けておいて、通常はかまいません。

GCC / cpp を使ったり mcpp を使ったり、デフォルトで付加するオプションを変更したりするためには、次のようにするのが良いでしょう。

Super-user になって、cpp の存在するディレクトリ(ここでは /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66 とする)に行きます。ここに GCC / cpp が cpp という名前で存在し、mcpp が mcpp という名前で install されているとします。まず、次のような内容の mcpp.sh という名前のファイルを作ります。*2

#!/bin/sh
/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/mcpp -Q -lang-asm "$@"

-Q オプションはなくてもかまいませんが、大量の診断メッセージを記録するには付けたほうが良いでしょう。
さらに、次のようなコマンドを打ち込みます。

chmod a+x mcpp.sh
mv cpp cpp_gnuc
ln -sf mcpp.sh cpp

こうしておくと、gcc が cpp を呼び出した時に、それにリンクされている mcpp.sh が実行され、mcpp に上記のオプションを(gcc の付加するオプションの前に)付加して呼び出します。

デフォルトのオプションを変更する時は、mcpp.sh を変更するなり、mcpp を直接呼び出すなりします。そして、GCC / cpp を使う時は

ln -sf cpp_gnuc cpp

とします。

注:

*1 mcpp V.2.7 からは GCC-specific-build にこれらの仕様が実装されたので、ソースを修正しなくてもプリプロセスはできるようになった。

*2 mcpp を configure してインストールした場合は自動的に適切な設定がされる。 あとは -Q -lang-asm オプションを追加するだけですむ。

3.9.5.1. mcpp のウォーニングを整理するには

mcpp を使う場合のもう一つの問題は、大量のウォーニングが出力されることです。-Q オプションでリダイレクトしても、glibc のような大規模なソースを処理すると mcpp.err が総計数百 MB 以上になるので、すべてに目を通すわけにはいきません。
しかし、この内容を見ると、同じウォーニングが繰り返し出ていることがわかります。同じ *.h ファイルが多くのソースから #include されるために、同じウォーニングが繰り返し繰り返し出るのです。これを整理して見るには、次のようにします。
まず、エラーをチェックします。

grep 'fatal:' `find . -name mcpp.err`
grep 'error:' `find . -name mcpp.err`

次に、ウォーニングを整理します。

grep 'warning:' `find . -name mcpp.err` | sort -k3 -u > mcpp-warnings-sorted

ウォーニングの出所をすべて見るためには、次のようにします。

grep 'warning:' `find . -name mcpp.err` | sort -k3 | uniq > mcpp-warnings-all

特定の種類のウォーニングを見るには、たとえば次のようにします。

grep 'warning: Replacement' `find . -name mcpp.err` | sort -k3 | uniq | less

こうして見当をつけたうえで、該当する mcpp.err を less で見て内容を確認し、必要ならソースを見ます。
さらに必要なら、ソースの問題の個所を #pragma MCPP debug expand, #pragma MCPP end_debug 等で挟んで再度プリプロセスして、その出力を見ます。この時には、プリプロセスの出力と診断メッセージとが同じファイルに出るように、次のようにします(make する場合は、上記の shell-script を一時書き換える)。

mcpp <-opts> in-file.c > in-file.i 2>&1

3.9.6. GCC 3.2 ソースのプリプロセス

Linux および FreeBSD で、GCC 2.95.* で GCC 3.2R (2002/08) のソースをコンパイルしてみました。そして、生成された gcc を使って mcpp をコンパイルし、次にプリプロセスにそれを使って GCC 3.2 で GCC 3.2 のソースをリコンパイルしてみました。

GCC の make はいくつかの段階を経て bootstrap されてゆくようになっています。すなわち、最初の段階で生成された gcc, cc1, etc. を使って自分自身をリコンパイルし、そうやって再生成されたものを使ってまた自分自身をリコンパイルし、といった経過をたどります。gcc は bootstrap の途中では xgcc という名前で存在しています。

また、GCC 2 では cc1, cc1plus からは独立していた cpp が、GCC 3 では cc1, cc1plus に吸収されてしまいました。しかし、独立したプリプロセッサである cpp0 も存在しており、gcc や g++ に -no-integrated-cpp というオプションを付けるとこれにプリプロセスをさせることができるようになっています。したがって、mcpp にプリプロセスをさせるためには、gcc (xgcc), g++ の呼び出しを shell-script に置き換えて、mcpp => cc1 または mcpp => cc1plus という順序で実行されるようにしなければなりません。*1

GCC のシステムでは、システムヘッダやそのサーチ順の設定が非常に複雑になってきています。また、GCC 3 では GCC が内部的に使う C++ の shared library の仕様が GCC 2 とは変わったようです。これらのためか、コンパイルするだけでもうまくゆかないことがあります。また、コンパイルとテストには多くの他のソフトウェアも必要で、それらのバージョンが古いと、うまくゆかないことがあります。私のところではハードウェアの問題でうまくコンパイルできないこともありました。

FreeBSD 4.4R では GCC 3.2 はコンパイルできませんでした。FreeBSD を 4.7R に upgrade し、packages を 4.7 用のものに入れ替えて、ようやくコンパイルすることができました。*2
私のところでは2台のパソコンに同じ VineLinux 2.5 が入っていますが、その片方 (K6/200MHz) では GCC 2.95.3 を使ってのコンパイルはできたものの、生成された GCC 3.2 / cc1 が segmentation fault を繰り返し起こしてしまい、自分自身のリコンパイルができませんでした。その後、K6 を AthlonXP に取り替えてリコンパイルしたところ、今度は segmentation fault は発生しませんでした。問題はハードウェアだったのかもしれません。
また、FreeBSD では K6 のパソコンで GCC 2.95.4 を使って GCC 3.2 をコンパイルした時は、make -k check ではほとんどすべて通ったのですが、さらに生成された GCC 3.2 で GCC 3.2 自身をリコンパイルしたところ、今度は、g++, libstdc++-v3 が make -k check で testsuite の2割近くが通らないという現象も起こりました。しかし、AthlonXP に取り替えてからはうまくゆくようになりました。これもハードウェアの問題だったのかもしれません。

また、VineLinux で GCC 3.2 自身と mcpp を使ってリコンパイルした場合は、生成された gcc は make -k check を通りましたが、g++, libstdc++-v3 は testsuite の2割近くが通りませんでした。*3, *4, *5
どちらにしても、生成された gcc, g++, cc1, cc1plus 等の問題ではなく、ヘッダファイルかライブラリか何かの設定の微妙な問題のようです。
mcpp は GCC とは完全に互換ではありませんが、かなり高い互換性をもっているので、取り替えて使ってほぼ問題はないと思われます。

GCC 3.2 のコンパイルに使ったシステムは次のものです。

OS make library CPU
VineLinux 2.5GNU makeglibc 2.2.4Celeron/1060MHz
VineLinux 2.5GNU makeglibc 2.2.4K6/200MHz
VineLinux 2.5GNU makeglibc 2.2.4AthlonXP/2.0GHz
FreeBSD 4.7R UCB makelibc.so.4 K6/200MHz
FreeBSD 4.7R UCB makelibc.so.4 AthlonXP/2.0GHz

コンパイルしたのは C と C++ だけです。

注:

*1 これを bootstrap の各段階ごとにやらなければならないのである。 Makefile は手を入れるにはあまりに大きく複雑なので、画面に張り付いていて、stage が変わったところで ^C で中断して、script に置き換えるという不細工な方法をとった。

*2 しかも、多くの packages の間の依存関係があるので、バージョンがまちまちだと混乱が生じる。私のところではこのために、一時は kterm が起動しないという状態に陥ったこともある。

*3 make -k check する時は mcpp は使ってはいけない。診断メッセージが GCC とは異なるからである。

*4 make -k check する時は環境変数 LANG, LC_ALL を C として、英語環境にしないといけない。

*5 Testsuite が通らない直接の原因はすべて、i686-pc-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.5.0.0 というライブラリで pthread_getspecific, pthread_setspecific 等 pthread_* という関数がリンクできないというものである。正しく生成されたこのライブラリを入れてやれば、make -k check は通る。FreeBSD ではこの問題は起こらない。何かの設定の微妙な問題のようである。

3.9.6.1. 行をまたぐ文字列リテラル

このあまりにも古い書き方は GCC 3.2 のソースにはありません。この仕様は GCC 3.2 でようやく obsolete とされました。ソース中にこれがあると、期待通りに処理はされますが、ウォーニングが出ます。

3.9.6.2. #include_next, #warning

Make の途中で生成される build/gcc/include の limits.h, syslimits.h には #include_next があり、GCC 3.2 を install すると、lib/gcc-lib/i686-pc-linux-gnu/3.2/include の limits.h, syslimits.h にコピーされます。

#warning は見当たりませんでした。

3.9.6.3. 可変引数マクロ

可変引数マクロはいくつかありますが、大半は testsuite のもので、テスト用に書かれたサンプルにすぎません。そして、GCC 2 以来の記法はまだサポートされてはいるものの、__VA_ARGS__ を使った C99 のものが多くなっています。

また、GCC 3 では GCC 2 の仕様と C99 の仕様との折衷的な書き方が追加されています。次の形です。*1

#define eprintf( fmt, ...)   fprintf( stderr, fmt, ##__VA_ARGS__)

これは GCC 2 仕様の次の書き方に対応します。

#define eprintf( fmt, args...)   fprintf( stderr, fmt, ##args)

これは、... に対応する引数がなかった時は、その直前のコンマを削除するという仕様です。たとえば、次のように展開されます。

eprintf( "success!\n")  ==>  fprintf( stderr, "success!\n")

この例を見ると便利な仕様のようですが、マクロ定義の置換リスト中のコンマはパラメータを区切るものとは限らないこと、トークン連結演算子である ## に別の働きをさせていること、規則に例外を作って複雑にするものであること、等の問題があります。*2, *3, *4

注:

*1 このマニュアルでは、GCC 2 以来の可変引数マクロを GCC2 仕様、GCC 3 で追加されたものを GCC3 仕様と呼ぶことにする。

*2 GCC 2.* では GCC2 仕様の可変引数マクロの定義の args... 等では args... とはくっついていなければならなかったが、GCC 3 では間に space が入っていても良くなった。

*3 ただし、-ansi オプションが指定された場合(-std=c*, -std=iso* オプションでも同様)、可変引数が存在しなくてもコンマは削除されない。 しかし、'##' は黙って消え去る。

*4 mcpp では V.2.6.3 から GCC-specific-build の STD モードに限って、GCC3 仕様の可変引数マクロも実装された。 さらに V.2.7 からは GCC2 仕様のものにも対応した。

3.9.6.4. マクロ呼び出しのカラ引数

マクロ呼び出しのカラ引数は、#include されるシステムヘッダのもの(/usr/include/bits/mathcalls.h, /usr/include/bits/sigset.h)を別とすると、GCC 3.2 のソース自体では、gcc/libgcc2.h にだけ見られます。*1

注:

*1 この2つのヘッダファイルは glibc を install することでシステムヘッダに入ってゆくものである。FreeBSD では glibc を使っていないので、これらのシステムヘッダは存在しない。

3.9.6.5. Function-like マクロの名前に置換される object-like マクロ

gcc/fixinc/gnu-regex.c, libiberty/regex.c には、function-like マクロの名前に置換される object-like マクロの定義が見られます。また、#include される /usr/lib/bison.simple も同様です。これらはすべて alloca に関するものです。たとえば、libiberty/regex.c にはこういうマクロ定義があります。

#define REGEX_ALLOCATE  alloca
#define alloca( size)   __builtin_alloca( size)

これは、こう書けば問題ないのですが、なぜこんなところを省略するのでしょうか。

#define REGEX_ALLOCATE( size)   alloca( size)

この regex.c では alloca は場合によっては次のように定義されるようになっており、スタイルが一貫していません。

#define alloca  __builtin_alloc

また、regex.c には #include "regex.c" という行があり、自分自身をインクルードするようになっています。複雑怪奇なソースです。

3.9.6.6. 'defined' に展開されるマクロ

これは GCC 3.2 のソースには見当たりません。

ドキュメントによると、この種のマクロの処理は GCC 2 / cpp と同じであるものの、「portability がない」というウォーニングを出すことになっています。ただ、テストしてみると、3.9.4.6 の例ではウォーニングが出ません。

3.9.6.7. .S ファイルの「プリプロセス」

GCC 3 / cpp のドキュメントには、次のように書かれています。

Wherever possible, you should use a preprocessor geared to the language you are writing in. Modern versions of the GNU assembler have macro facilities.

しかし、GCC 3.2 自身のソースには、*.S ファイルが gcc/config ディレクトリに数本あります。

3.9.6.8. rpcgen と -dM オプションの仕様の問題

GCC 3.2 の make では rpcgen も -dM オプションも使われませんでした。しかし、rpcgen や -dM オプションの仕様は特に変わってはいないようです。

3.9.6.9. -include, -isystem, -I- オプション

これらのオプションはしばしば使われており、-isystem オプションで system include directory が同時に数個指定される場合さえあります。こういう、システムヘッダそのものを更新するソフトウェアのコンパイルでは、やむをえないことなのでしょうか。環境変数で一括して指定したほうが、わかりやすいと思うのですが。

一方で、GCC 3 / cpp のドキュメントでは、-iwithprefix, -iwithprefixbefore オプションについては、「使わないほうが良い (discouraged)」と書かれています。GCC には include directory を指定するオプションがやたらにありますが、整理する方向に入ってきたのでしょうか。*1

注:

*1 しかし、GCC 3.2 の Makefile は -iprefix オプションを付けている。 ところが -iwithprefix, -iwithprefixbefore は使われないのである。-iprefix オプションは、その後にこの2つのオプションのどちらかがあって初めて意味を持つのであるが。

3.9.6.10. Undocumented な事前定義マクロ

GCC 2 では __VERSION__, __SIZE_TYPE__, __PTRDIFF_TYPE__, __WCHAR_TYPE__ 等の事前定義マクロについては、ドキュメントに記載がなく、-dM オプションでも知ることができませんでしたが、GCC 3 ではドキュメントに意味が記載され、具体的な値も -dM で知ることができるようになりました。

3.9.6.11. Undocumented な環境変数

GCC 2 ではドキュメントに記載のなかった SUNPRO_DEPENDENCIES という環境変数については、GCC 3 ではドキュメントに記載されるようになりました(しかし、なぜこんなものが必要なのかはわからない)。

3.9.6.12. その他の問題

GCC 3 では次のような #pragma が実装されています。

#pragma GCC poison
#pragma GCC dependency
#pragma GCC system_header

GCC 3.2 のソースでも、このうち poison と system_header が使われています。しかし、mcpp ではこれらはサポートしていません。仕様の説明は省きますが、必要性があまり感じられないからです。*1

GCC 3 では #assert 等の assertion directives は「推奨しない (deprecated)」とされました(しかし、gcc はデフォルトで -A オプションを発行するが)。

また、GCC 2 では -traditional オプションは同一の cpp で実装されており、そのため非常に古い仕様と C90 の仕様が混在する奇怪な仕様となっていましたが、GCC 3.2 ではプリプロセッサが通常の cpp0 と tradcpp0 とに分けられました。-traditional オプションは gcc に対してだけ有効で、cpp0 にはありません。gcc -traditional はプリプロセスに tradcpp0 を呼び出します。tradcpp0 は C90 以前の真に traditional なプリプロセッサに近いものとなっています。そして、tradcpp0 のほうは今後、深刻なバグの修正以外はメンテナンスしないとされています。

GCC 2 / cpp の奇妙な仕様はだいぶ修正されてきたように見えます。

注:

*1 mcpp V.2.7 からは GCC-specific-build で #pragma GCC system_header をサポートした。

3.9.7. GCC 3, 4 で mcpp を使うには

以上に見てきたように、GCC 3.2 のソースは少なくともプリプロセス上は、glibc 2.1.3 などに比べるとかなりきれいなものになっています。Traditional な書き方はほぼ一掃され、意味のないオプションは使われなくなってきています。
また、GCC 3.2 / cpp0 そのものも、traditional な仕様を obsolete なものとして扱い、token-based な原則を明確にするなど、GCC 2 / cpp に比べると格段に優れたものとなっています。ドキュメントも undocumented な部分が大幅に減りました。まだ不徹底な面も多々ありますが、方向としては良い方向に向かっていると思われます。

ただ、GNU のシステムではシステムヘッダが複雑化する一方で、何がどうなっているのか容易に把握できないようになっています。これが GNU のシステムのトラブルの最大の要因となってくるのではないでしょうか。

もう一つ残念なのは、プリプロセスがコンパイラ本体に吸収されてしまったことです。そのため、mcpp を使うには gcc や g++ を -no-integrated-cpp というオプションを付けて呼び出す必要があります。複雑な makefile や多くの makefile を持つ大きなソースファイル群をコンパイルする場合や、何かのプログラムから gcc が自動的に呼び出される場合は、gcc, g++ の呼び出しを shell-script に置き換えて、このオプションが自動的に付加されるようにしなければなりません。

具体的には、gcc, g++ の置かれているディレクトリ(私の Linux の例では /usr/local/gcc-3.2/bin)に次のような script をそれぞれ gcc.sh, g++.sh という名前で置きます。

#!/bin/sh
/usr/local/gcc-3.2/bin/gcc_proper -no-integrated-cpp "$@"
#!/bin/sh
/usr/local/gcc-3.2/bin/g++_proper -no-integrated-cpp "$@"

そして、このディレクトリで次のようにします。

chmod a+x gcc.sh g++.sh
mv gcc gcc_proper
mv g++ g++_proper
ln -sf gcc.sh gcc
ln -sf g++.sh g++

また、cpp の置かれているディレクトリ(私の Linux の例では /usr/local/gcc-3.2/lib/gcc-lib/i686-pc-linux-gnu/3.2)で、GCC 2 の場合と同様に、cpp0 の呼び出しで mcpp が実行されるようにしておきます( 3.9.5 参照)。*1
こうしておくと、gcc や g++ からまず mcpp が呼び出され、その後に cc1, cc1plus が -fpreprocessed というプリプロセス済みであることを示すオプションを付けて呼び出されるようになります。

なお、システムの標準と異なるバージョンの GCC をインストールした場合は付加的な include directory の設定が必要なことがありますが、mcpp ではこれらも mcpp のコンパイル時に組み込むので、通常は環境変数で設定する必要はありません。

できれば cc1, cc1plus のプリプロセス部分である cpplib のソースを mcpp のものに置き換えたいところですが、cpplib の cc1, cc1plus との内部的な interface および cpplib を使うユーザプログラムとの外部的な interface を定義しているソースファイルが合わせて 46KB もあり、とても置き換えは不可能です。どうしてこういう複雑な interface にする必要があるのでしょうか。残念なことです。

注:

*1 configure して mcpp をインストールした場合は、これらが自動的に設定される。

3.9.7.1. GCC 3.3, 3.4-4.1 で mcpp を使うには

良い方向に向かっていた GCC V.3 でしたが、V.3.3 からは妙な方向に転換してしまいました。V.3.3 は V.3.2 と比べると、次のような点で大きく変わっています。

  1. 単体の cpp0 はなくなった。gcc -no-integrated-cpp はまだ使えるが、そこから呼び出されるのは cc1 (cc1plus) である。すなわち、プリプロセスでもコンパイルでも cc1 が呼び出される。そして、プリプロセスフェーズの cc1 にプリプロセス用でないオプションがしばしば渡される(何という汚い実装!)。
  2. 60 個から 70 個の大量のマクロが事前定義されている。これによってシステムヘッダと GCC との関係がさらに複雑になった。
  3. tradcpp もなくなり、GCC V.3.2 では obsolete とか deprecated とされた古い仕様の一部が復活した。

全体として、1つの巨大なコンパイラにすべてを吸収する方向となっており、C処理系の構成のしかたとしても、オープンソースの処理系の開発の方向としても、大いに疑問のあるところです。

mcpp の移植では、gcc からどんなオプションが渡されてくるかわからないというのが困るところです。間違ったオプションもすべてチェックせずに無視するのでは危険があります。とりあえず、しばしば間違って渡されてくるオプションは無視するようにしましたが、それ以外のオプションが渡されるとエラーになるはずです。
これだけの変更でも、mcpp の従来のオプションの中には使えなくなったものがあります。V.2.5 からは -E オプションは廃止し、-m オプションは -e に、-c は -@compat に変更しました。
また、GCC V.3.2 では cpp0 の呼び出しを mcpp に置き換えればすんだところが、今度は cc1 (cc1plus) の呼び出しを mcpp と cc1 (cc1plus) とに振り分けることが必要になります。このための shell-script は src/set_mcpp.sh の中に用意しました。大量の事前定義マクロは1つ1つ対応しているわけにはいかないので、GCC の -dM オプションの出力を一括して利用するようにしました。*1, *2, *3

GCC V.3.4 ではさらに、multi-byte character はすべて UTF-8 に変換してから処理するように変わりました。ドキュメントによると、具体的には次のようになっています。*4

  1. プリプロセスの最初のフェーズで、multi-byte character を UTF-8 に変換する。
  2. この変換には libiconv の関数を使う。したがって、iconv が対応している encoding はすべて使える。
  3. ソースファイルの encoding を指定するには -finput-charset=<encoding> オプションを使う。(実際には、これを指定しなければ UTF-8 には変換されない)。
  4. コンパイル後の encoding はデフォルトでは UTF-8 であるが、-fexec-charset=<encoding> オプションで他の encoding を指定することができる。*5

「国際化」と言えば Unicode に対応させることと考える風潮が、ことに実際に multi-byte character を使わない西欧の人々の間にありますが、この風潮が GCC にも及んでしまったようです。

しかも、仕様がまだ十分に実装されていません。実際に使ってみると、次のようになります。

  1. EUC-JP, GB2312, KSC-5601, Big5 は -finput-charset オプションを指定すると UTF-8 に正しく変換され、指定しなければそのまま通る。*6
  2. -fexec-charset オプションは V.3.4, 4.0 では効かなかったが、V.4.1-4.3 では効く。
  3. ISO2022-JP は V.3.4, 4.0 では処理できなかったが、V.4.1-4.3 ではできる。
  4. どのバージョンでも shift-JIS は -finput-charset を指定するとかえって混乱してしまう。

mcpp は -e <encoding> オプションで encoding を指定しますが、GCC-specific-build では、BIG5, shift-JIS, ISO2022-JP では <backslash> 等と一致する値のバイトの直前に <backslash> を挿入してコンパイラの欠陥を補います。しかし、UTF-8 には変換せず、元の encoding のまま出力します。-finput-charset オプションも -e オプションと同じものとして扱います。これは次のような理由です。*7

  1. SJIS は -f*-charset を指定すると、どのバージョンの GCC でもダメである。しかし、どのバージョンの GCC でも、SJIS, JIS, BIG5 は変換せずに <backslash> を補ってやるとそのまま通り、期待通りの結果になる。 EUC-JP, GB2312, KSC-5601 も変換しなければそのまま通る。 すなわち、single byte character sequence であるかのように扱われる。
  2. GCC 4.0 までのバージョンではどの encoding も UTF-8 から元の encoding に戻せない。
  3. GCC の近い将来の仕様変更を期待したい。

注:

*1 しかし、-dM オプションの出力は他のオプションによって少し違ってくるのである。 しかも、これらの事前定義マクロの大半は undocumented なものである。 このため、全容はなかなか把握できない。

*2 MinGW では symbolic link が使えない。ln -s というコマンドはあるが、これは単にコピーするだけのものである。また、MinGW の GCC はたとえ cc1 という名前のファイルであってもそれが shell-script であると、起動を拒否する。そのため、mcpp の GCC-specific-build では、MinGW に限って cc1.exe という名前の実行プログラムを生成する(これは cc1plus.exe にもコピーされる)。そして、その中から mcpp.exe または GCC の cc1.exe ないし cc1plus.exe を起動するようにしている。

*3 CygWIN / GCC では -mno-cygwin というオプションがあり、これを指定すると system include directory が変わり、GCC の事前定義マクロも変わる。このため、mcpp の CygWIN GCC-specific-build では V.2.6.1 からは2セットの事前定義マクロを用意するようにした。

*4 私のところの FreeBSD 6.3 ではなぜか GCC のこの変換はまったく動作しない。libiconv はリンクされているのであるが。 FreeBSD 5.3, 6.2 でもそうであった。

*5 この変換はプリプロセスフェーズではなく、コンパイルフェーズで行われるようである。 -E オプションの出力は UTF-8 のままである。

*6 しかし、GCC V.4.1-4.3 では、-save-temps または -no-integrated-cpp オプションを -f*-charset オプションと同時に指定すると、エラーになるというバグがある。

*7 mcpp の出力を cc1 に渡す時には -finput-charset はもちろん -fexec-charset オプションも付けてはいけない。

3.9.8. Linux / glibc 2.4 ソースのプリプロセス

glibc 2.4 (2006/03) のソースをコンパイルして、プリプロセスをチェックしてみました。 処理系は GCC 4.1.1 で、プリプロセッサを mcpp 2.6.3 に置き換えたものです。 マシンは x86 系なので、他の CPU 用のコードはチェックしていません。

以前に私がチェックした glibc 2.1.3 (2000/02) から6年経ったバージョンなので、大きく変わったところももちろんありますが、意外に変わっていないところも多く見られます。 全体としては、以前のバージョンについて私が取り上げた問題点は整理されずに、むしろ増える傾向にあります。

3.9.8.1. 行をまたぐ文字列リテラル

行をまたぐ文字列リテラルは見られなくなりました。

3.9.8.2. #include_next, #warning

次のファイルには #include_next があります。 5年半前のバージョンと比べると増えています。

catgets/config.h, elf/tls-macros.h, include/bits/dlfcn.h, include/bits/ipc.h, include/fpu_control.h, include/limits.h, include/net/if.h, include/pthread.h, include/sys/sysctl.h, include/sys/sysinfo.h, include/tls.h, locale/programs/config.h, nptl/sysdeps/pthread/aio_misc.h, nptl/sysdeps/unix/sysv/linux/aio_misc.h, nptl/sysdeps/unix/sysv/linux/i386/clone.S, nptl/sysdeps/unix/sysv/linux/i386/vfork.S, nptl/sysdeps/unix/sysv/linux/sleep.c, sysdeps/unix/sysv/linux/ldsodefs.h, sysdeps/unix/sysv/linux/siglist.h

次のファイルは make check で使われるテスト用のものですが、ここにも #include_next があります。

sysdeps/i386/i686/tst-stack-align.h

#warning は sysvipc/sys/ipc.h にありますが、正常にコンパイルされるときはスキップされるブロックにあるので、ひっかかることはありません。

3.9.8.3. 可変引数マクロ

次のファイルには可変引数マクロの定義がありますが、これらはすべて GCC2 以来の古い仕様のマクロです。 C99 の仕様のものはおろか、GCC3 仕様のものさえも見当たりません。

elf/dl-lookup.c, elf/dl-version.c, include/libc-symbols.h, include/stdio.h, locale/loadlocale.c, locale/programs/ld-time.c, locale/programs/linereader.h, locale/programs/locale.c, locale/programs/locfile.h, nptl/sysdeps/pthread/setxid.h, nss/nss_files/files-XXX.c, nss/nss_files/files-hosts.c, sysdeps/generic/ldsodefs.h, sysdeps/i386/fpu/bits/mathinline.h, sysdeps/unix/sysdep.h, sysdeps/unix/sysv/linux/i386/sysdep.h

次はテスト用のルーチンですが、ここにも GCC2 仕様の可変引数マクロの定義があります。

localedata/tst-ctype.c, posix/bug-glob2.c, posix/tst-gnuglob.c, stdio-common/bug13.c

しかも実際にこれらのマクロを呼び出すときには、可変引数が1つもない(カラ引数さえもない)変則的な呼び出しがきわめて多くなっています。 この種の変則的なマクロ呼び出しは 142 本ものソースファイルに見られます。 そのうち、置換リストで可変引数の直前に ", ##" という sequence があってこの ',' が削除されるケースは、120 本のソースに見られます。

可変引数マクロは C99 の仕様を使ったほうが portable で良いのですが、GCC2 あるいは GCC3 仕様の可変引数マクロを C99 仕様のものに書き直すのは、必ずしも簡単ではありません。 GCC2 あるいは GCC3 仕様では実際のマクロ呼び出しで可変引数がない場合、その直前のコンマが削除されるので、マクロの呼び出し方によっては C99 仕様とは1対1に対応しないからです。 マクロ定義を C99 仕様に書き換えた場合、コンマを削除しないですむようにするためには、マクロ呼び出しのほうも書き直す必要があります。

glibc 2.1.3 ではまだこれらのマクロの数がさほど多くはなかったので、ユーザが目で確かめながらエディタで書き換えることができましたが、glibc 2.4 では数が増え、しかもそれらのマクロの呼び出し個所が大幅に増えたため、ユーザが書き換えることは不可能になりました。

このため、mcpp では V.2.6.3 から GCC-specific-build に限って、GCC3 仕様の可変引数マクロも実装しました。 さらに V.2.7 からは GCC2 仕様のものにも対応しました。 しかし、GCC2 仕様のものは token-based な原則から掛け離れているので、それを使うマクロを新しく書くべきではありません。 GCC2 仕様は GCC3 仕様と1対1に対応しているので、マクロ定義を GCC3 仕様に書き直すことは容易にでき、マクロ呼び出しを書き直さなくてもすみます。 すでに GCC2 仕様で書かれているマクロはこう書き換えたほうが、いくらかわかりやすくなります。*1

GCC2 仕様の可変引数マクロを GCC3 仕様に書き直すには、例えば次のようなものを

#define libc_hidden_proto(name, attrs...)   hidden_proto (name, ##attrs)

次のようにします。

#define libc_hidden_proto(name, ...)    hidden_proto (name, ## __VA_ARGS__)

すなわち、パラメータの attrs...... にし、置換リスト中の attrs__VA_ARGS__ にします。

注:

*1 GCC2 仕様および GCC3 仕様の可変引数マクロについては、3.9.1.6, 3.9.6.3 を参照のこと。

3.9.8.4. マクロ呼び出しのカラ引数

カラ引数のあるマクロ呼び出しはきわめて多く、488 本ものソースファイルに見られます。 以前のバージョンと比べても大幅に増えました。 C99 でカラ引数が公認されたことが影響しているのでしょうか。

ことに math/bits/mathcalls.h には 79 個ものカラ引数マクロ呼び出しがあります。 以前とほぼ同様です。

3.9.8.5. Function-like マクロの名前に置換される object-like マクロ

次のファイルには function-like マクロの名前に置換される object-like マクロの定義が見られます。

argp/argp-fmtstream.h, hesiod/nss_hesiod/hesiod-proto.c, intl/plural.c, libio/iopopen.c, nis/nss_nis/nis-hosts.c, nss/nss_files/files-hosts.c, nss/nss_files/files-network.c, nss/nss_files/files-proto.c, nss/nss_files/files-rpc.c, nss/nss_files/files-service.c, resolv/arpa/nameser_compat.h, stdlib/gmp-impl.h, string/strcoll_l.c, sysdeps/unix/sysv/linux/clock_getres.c, sysdeps/unix/sysv/linux/clock_gettime.c

elf/link.h には次のように function-like マクロの名前に置換される function-like マクロがあります。

#define ELFW(type) _ElfW (ELF, __ELF_NATIVE_CLASS, type)
                                        /* sysdeps/generic/ldsodefs.h:46    */
#define _ElfW(e,w,t)    _ElfW_1 (e, w, _##t)            /* elf/link.h:32    */
#define _ElfW_1(e,w,t)  e##w##t                         /* elf/link.h:33    */
#define __ELF_NATIVE_CLASS __WORDSIZE               /* bits/elfclass.h:11   */
#define __WORDSIZE 32           /* sysdeps/wordsize-32/bits/wordsize.h:19   */
#define ELF32_ST_TYPE(val) ((val) & 0xf)                /* elf/elf.h:429    */

上記のようなマクロ定義があると、

    && ELFW(ST_TYPE) (sym->st_info) != STT_TLS      /* elf/do-lookup.h:81   */

このマクロ呼び出しでは ELFW(ST_TYPE) が次のように展開されていきます。

    ELFW(ST_TYPE)
    _ElfW(ELF, __ELF_NATIVE_CLASS, ST_TYPE)
    _ElfW_1(ELF, 32, _ST_TYPE)
    ELF32_ST_TYPE

そして、ELF32_ST_TYPE がさらに (sym->st_info) を巻き込んで ((sym->st_info) & 0xf) と展開されます。 すなわち、_ElfW_1(ELF, 32, _ST_TYPE) という function-like macro の呼び出しが ELF32_ST_TYPE という別の function-like macro の名前に展開されるのです。

これは3つのマクロ定義を次のように書いて、

#define ELFW( type, val)        _ElfW( ELF, __ELF_NATIVE_CLASS, type, val)
#define _ElfW( e, w, t, val)    _ElfW_1( e, w, _##t, val)
#define _ElfW_1( e, w, t, val)  e##w##t( val)

次のように呼び出したほうが、わかりやすくなります。 引数の渡し方が少し冗長に見えるかもしれませんが、関数呼び出しをモデルとして考えれば、こちらのほうが自然でしょう。

    && ELFW(ST_TYPE, sym->st_info) != STT_TLS

3.9.8.6. 'defined' に展開されるマクロ

次のファイルには、置換リスト中に 'defined' というトークンの出てくるマクロの定義があります。*1

iconv/skeleton.c, sysdeps/generic/_G_config.h, sysdeps/gnu/_G_config.h, sysdeps/i386/dl-machine.h, sysdeps/i386/i686/memset.S, sysdeps/mach/hurd/_G_config.h, sysdeps/posix/sysconf.c

次のファイルではそれらのマクロが #if 行で使われています。 また、上記のファイル自身の中でも使われている場合があります。

elf/dl-conflict.c, elf/dl-runtime.c, elf/dynamic-link.h

glibc 2.1.3 の malloc/malloc.c には HAVE_MREMAP という、やはり置換リストに 'defined' が出てくるマクロがありましたが、glibc 2.4 ではこれは portable な形に直されています。 しかし、他のソースにこの種のマクロが増えてしまいました。

#if 行で置換リスト中に 'defined' が出てくるマクロ呼び出しの結果は規格では undefined であり、これを恣意的に処理するのは GCC の勝手仕様です。 他の処理系でも処理できる portable なソースにするためには、これらのマクロ定義を書き換えることが必要です。 時にはマクロ呼び出しも書き換えなければなりません。*2

この書き換えは多くの場合は、3.9.4.6 にある方法でできます。 しかし、中にはこれでは対応できないものもあります。 'defined MACRO' の評価がタイミングによって異なる場合です。 例えば、sysdeps/i386/dl-machine.h には次のようなマクロ定義があり、このマクロが他のファイルの #if 式中で使われています。

#define ELF_MACHINE_NO_RELA defined RTLD_BOOTSTRAP

これをこう書き換えたのでは、うまくいきません。

#if defined RTLD_BOOTSTRAP
#define ELF_MACHINE_NO_RELA 1
#endif

RTLD_BOOTSTRAP というマクロは elf/rtld.c で定義されますが、これが dl-machine.h より先に include される場合と後で include される場合があり、それによって 'defined RTLD_BOOTSTRAP' の結果が違ってくるのです。 これを portable に書き直すには、ELF_MACHINE_NO_RELA というマクロを使うことをやめて、次のような #if 行を

#if ELF_MACHINE_NO_RELA

次のようにします。 ELF_MACHINE_NO_RELA は #if 行でしか使われていない無用なマクロなのです。

#if defined RTLD_BOOTSTRAP

glibc では実際にこの書き方になっているところも多くあるのですが、それが ELF_MACHINE_NO_RELA を使う undefined な書き方と混在しています。

注:

*1 Linux では /usr/include/_G_config.h は glibc の sysdeps/gnu/_G_config.h がインストールされたものなので、ここにも次のような同じマクロ定義がある。

#define _G_HAVE_ST_BLKSIZE defined (_STATBUF_ST_BLKSIZE)

これはこう書き換えておくべきである。

#if defined (_STATBUF_ST_BLKSIZE)
#define _G_HAVE_ST_BLKSIZE 1
#endif

*2 mcpp V.2.7 からは GCC-specific-build の STD モードに限って、#if 行のマクロに出てくる 'defined' トークンを GCC と同様に処理するようにした。 しかし、こうした bug-to-bug な対処をあてにすべきではない。

3.9.8.7. .S ファイルの「プリプロセス」

*.S ファイルは CPU ごとに用意されているのできわめて多く、1000 本あまりもあります(x86 等、1種類の CPU で使われるのはそのうちの一部)。

*.S ファイルはアセンブラソースに C の #if, #include 等の directive やコメントやマクロを混在させたものですが、アセンブラソースは C の token sequence の形をしていないので、これを C プリプロセッサで処理することには無理があります。 プリプロセッサは C で使わない %, $ 等の文字もそのまま通し、space の有無も原則としてそのまま維持しなければなりません。 さらに C ではエラーになるところをエラーにせずにそのまま通すように、文法チェックを大幅に緩和しなければなりません。 そして、その一方で #if やマクロは処理し、一応のエラーチェックもしなければならないのです。 やっかいなことです。 これらの仕様にはすべて論理的な根拠は何もなく、GCC の local な(多くは undocumented な)仕様を使っているにすぎません。

例えば nptl/sysdeps/unix/sysv/linux/i386/i486/pthread_cond_wait.S には次のような部分があります

    .byte   8               # Return address register
                            # column.
#ifdef SHARED
    .uleb128 7              # Augmentation value length.
    .byte   0x9b            # Personality: DW_EH_PE_pcrel
                            # + DW_EH_PE_sdata4

'#ifdef SHARED' は C の directive ですが、各行の後半の # で始まる部分はコメントのつもりのようです。 しかし、'# column.' のように行の最初の non-white-space-character が # の場合は、無効な directive と構文上、区別がつきません。 '# + DW_EH_PE_sdata4' に至っては C では構文エラーになるものです。
他のファイルには次のような例もあります。 ここで '\'' という文字は C では character constant の両端に使われるものなので、これが単独で現れると tokenization のエラーになります。

    movl 12(%esp), %eax     # that `fixup' takes its parameters in regs.

さらに、上記の pthread_cond_wait.S には次のような部分がありますが、これはマクロの呼び出しです。

versioned_symbol (libpthread, __pthread_cond_wait, pthread_cond_wait,
          GLIBC_2_3_2)

このマクロの定義は次のようになっています。

# define versioned_symbol(lib, local, symbol, version) \
  versioned_symbol_1 (local, symbol, VERSION_##lib##_##version)
                                    /* include/shlib-compat.h:65    */
# define versioned_symbol_1(local, symbol, name) \
  default_symbol_version (local, symbol, name)
                                    /* include/shlib-compat.h:67    */
# define default_symbol_version(real, name, version) \
     _default_symbol_version(real, name, version)
                                    /* include/libc-symbols.h:398   */
#   define _default_symbol_version(real, name, version) \
     .symver real, name##@##@##version
                                    /* include/libc-symbols.h:411   */
#define VERSION_libpthread_GLIBC_2_3_2  GLIBC_2.3.2
                            /* make で生成される abi-versions.h:145 */

これによって、このマクロはこういう展開結果になることが期待されています。

.symver __pthread_cond_wait, pthread_cond_wait@@GLIBC_2.3.2

問題は _default_symbol_version の定義です。 '@' という文字は C の token (pp-token) にはありません。 そして、これを ## 演算子で連結した pthread_cond_wait@@GLIBC_2.3.2 という token もありません。 連結の途中にも illegal な token が生成されます。 このマクロは C の ## 演算子を使っていますが、文法は C とは掛け離れたものです。

アセンブラソースについてプリプロセスのようなことをするのであれば、やはりアセンブラ用のマクロプロセッサを使うべきでしょう。 C プリプロセッサを使うのであれば、*.S ではなく *.c として、アセンブラ用のコードは文字列リテラルに埋め込んで asm() なり __asm__() なりの関数に渡すべきでしょう。 libc-symbols.h には次のような別バージョンのマクロも用意されていて、*.c であればこちらが使われます。 こちらは規格準拠の C プリプロセッサで問題なく処理できます。

#   define _default_symbol_version(real, name, version) \
     __asm__ (".symver " #real "," #name "@@" #version)

glibc には asm() や __asm()__ を使った *.c, *.h ファイルも多くありますが、まだ過半は変則的な *.S ファイルとなっています。

どうしてもアセンブラソースを C プリプロセッサで処理するのであれば、せめてコメント記号としては # ではなく /* */ か // を使うべきです。 実際、glibc でも /* */ か // を使っているソースが多いのですが、# を使っているソースもいくつかあります。

とは言え、glibc 2.4 では *.S ファイルがあまりにも多く、しかも 2.1.3 のころよりも C の文法を無視したソースが多くなっているので、mcpp では V.2.6.3 から lang-asm モードでの文法チェックを大幅に緩和し、これらの変則ソースが処理できるようにしました。

3.9.8.8. versions.awk, rpcgen と -dM オプションの仕様の問題

3.9.4.8 に書いた stdlib/isomac.c の問題は変わっていません。

rpcgen の問題も変わっていません。

さらに glibc 2.4 には scripts/versions.awk というファイルがありますが、これはプリプロセッサの出力の行頭の space の数について、GCC でしか通用しない仮定を持ち込んでいます。 mcpp や他のプリプロセッサを使うためには、このファイルを次のように修正しなければなりません。

$ diff -c versions.awk*
*** versions.awk        2006-12-13 00:59:56.000000000 +0900
--- versions.awk.orig   2005-03-23 10:46:29.000000000 +0900
***************
*** 50,56 ****
  }

  # This matches the beginning of a new version for the current library.
! /^ *[A-Z]/ {
    if (renamed[actlib "::" $1])
      actver = renamed[actlib "::" $1];
    else if (!versions[actlib "::" $1] && $1 != "GLIBC_PRIVATE") {
--- 50,56 ----
  }

  # This matches the beginning of a new version for the current library.
! /^  [A-Za-z_]/ {
    if (renamed[actlib "::" $1])
      actver = renamed[actlib "::" $1];
    else if (!versions[actlib "::" $1] && $1 != "GLIBC_PRIVATE") {
***************
*** 65,71 ****
  # This matches lines with names to be added to the current version in the
  # current library.  This is the only place where we print something to
  # the intermediate file.
! /^ *[a-z_]/ {
    sortver=actver
    # Ensure GLIBC_ versions come always first
    sub(/^GLIBC_/," GLIBC_",sortver)
--- 65,71 ----
  # This matches lines with names to be added to the current version in the
  # current library.  This is the only place where we print something to
  # the intermediate file.
! /^   / {
    sortver=actver
    # Ensure GLIBC_ versions come always first
    sub(/^GLIBC_/," GLIBC_",sortver)

3.9.8.9. -include, -isystem, -I- オプション

-isystem, -I- オプションは使われていません。

しかし、-include オプションは極端に頻繁に使われています。 include/libc-symbols.h というヘッダファイルが -include オプションによって 7000 回も include されるのです。 このオプションは、本来はソース中に #include で書くべき行を makefile に押し出して使うためのものです。 ソースを不完全にするので、決して良いことではありません。

3.9.8.10. Undocumented な事前定義マクロ

これは glibc の問題ではなく、GCC の問題です。 GCC 2 ではいくつかの重要な事前定義マクロが undocumented でしたが、その状態は GCC 3 で解消されました。 しかし、他方で GCC 3.3 からは事前定義マクロが極端に増えたのに伴って、undocumented なものが大幅に増えてしまいました。

3.9.8.11. その他の問題

debug/tst-chk1.c には奇妙な部分があり、次のように修正しないと GCC 以外のプリプロセッサでは意図通りに処理されません。

$ diff -cw tst-chk1.c*
*** tst-chk1.c  2007-01-11 00:31:45.000000000 +0900
--- tst-chk1.c.orig     2005-08-23 00:12:34.000000000 +0900
***************
*** 113,119 ****
  static int
  do_test (void)
  {
-   int   arg;
    struct sigaction sa;
    sa.sa_handler = handler;
    sa.sa_flags = 0;
--- 113,118 ----
***************
*** 135,146 ****
    struct A { char buf1[9]; char buf2[1]; } a;
    struct wA { wchar_t buf1[9]; wchar_t buf2[1]; } wa;

  #ifdef __USE_FORTIFY_LEVEL
!   arg = (int) __USE_FORTIFY_LEVEL;
  #else
!   arg = 0;
  #endif
!   printf ("Test checking routines at fortify level %d\n", arg);

    /* These ops can be done without runtime checking of object size.  */
    memcpy (buf, "abcdefghij", 10);
--- 134,146 ----
    struct A { char buf1[9]; char buf2[1]; } a;
    struct wA { wchar_t buf1[9]; wchar_t buf2[1]; } wa;

+   printf ("Test checking routines at fortify level %d\n",
  #ifdef __USE_FORTIFY_LEVEL
!         (int) __USE_FORTIFY_LEVEL
  #else
!         0
  #endif
!         );

    /* These ops can be done without runtime checking of object size.  */
    memcpy (buf, "abcdefghij", 10);

この元ソースは何の変哲もない書き方に見えますが、実はここでは printf() がマクロとして定義されているのです。 そのため、#ifdef 等のディレクティブ行らしきものが通常はマクロの引数として扱われる結果になります。 マクロ呼び出しの中でなければディレクティブ行となるものが引数中に現れた場合の結果は、規格では undefined です。 ディレクティブの処理とマクロ展開は同じ translation phase で行われるので、ディレクティブを先に処理するのは、GCC の勝手仕様です。 そもそも、#ifdef __USE_FORTIFY_LEVEL の処理にもマクロの処理が必要なので、この行を先に処理してから printf() マクロを展開するというのは、きわめて恣意的な処理です。 C プリプロセスというものは、頭から sequentially に処理してゆくべきものなのです。

glibc を configure するときにも、GCC の特殊な help message を使う部分があります。 Help に "-z relro" というオプションが出てくるかどうかをサーチするものです。 プリプロセッサに mcpp を使っていると、これは期待する結果にはなりません。 しかし、それでも glibc のコンパイルとテストは正常に行われます。

なお、GCC 3.2 までは gcc を起動すると無用な -A オプションがいろいろとデフォルトで付加されましたが、GCC 3.3 からはなくなりました。

3.9.8.12. 深まる GCC 依存

glibc 2.4 を6年前の glibc 2.1.3 と比べると、私がかつて取り上げた portability の問題はほとんど改善されておらず、逆に portability を欠いたソースが増えてきていることがわかります。

改善されたのは、行をまたぐ文字列リテラルがなくなったこと、-isystem, -I- オプションが使われなくなったこと、GCC の側で -A オプションが使われなくなったことくらいです。

#include_next、GCC2 仕様の可変引数マクロ、中でも可変引数が 0 個のマクロ呼び出し、置換リストに 'defined' のあるマクロ、*.S ファイル、-include オプション、これらは大幅に増えました。マクロ呼び出しのカラ引数も増えました。 Portable なソースに書き直すことが簡単にはできない、Standard C と1対1に対応しない書き方が増えたのも、やっかいなところです。

これらの問題はすべて GCC の local な仕様への依存によるものです。 GCC の undocumented な動作に依存している部分も多くあります。 大規模なソフトウェアでそうした部分が発生すると、多くのソースファイルが絡み合うため、修正が困難になり、何年でも同じ書き方が継承されてゆくことになりがちです。 そして、新しいソースもそれに合わせて書かれることになります。 可変引数マクロは GCC2 仕様のものだけで、C99 の仕様のものはおろか GCC3 仕様のものさえ使われていないことは、この関係を端的に表しています。 また、せっかく一部のソースの unportable な部分が書き直されても、他のソースに古い書き方が新たに現れる場合も多く、なかなか整理されません。

逆に GCC のほうも仕様の変更は大きな影響をもたらすので、簡単にはできなくなってしまいます。 どこかで双方の思い切った整理が必要だと思われます。

3.9.9. Linux の stddef.h, limits.h, #include_next の問題

Linux では GCC がシステムコンパイラである上にライブラリが glibc であるため、システムヘッダには GCC しか想定していない部分がところどころにあります。 これが compiler-independent-build の mcpp のような他の処理系を使う場合の障害になります。 中でも stddef.h 等のいくつかの標準ヘッダファイルが GCC の version-specific な include directory にだけあって /usr/include に存在しないというのはお粗末な欠陥であり、mcpp にとっては対策が必要です。 このセクションではこの問題を検討します。

Linux では GCC のバージョンごとに例えば /usr/lib/gcc-lib/SYSTEM/VERSION/include といった include directory がシステムに追加されますが、そこにある stddef.h, limits.h 等は奇妙なものです。 CygWIN でも同じです。 Mac OS X にも少し問題があります。

3.9.9.1. /usr/include に標準ヘッダがない

まず、C の標準ヘッダファイルのうち float.h, iso646.h, stdarg.h, stdbool.h, stddef.h の5つは Linux ではこの GCC のディレクトリにだけあり、/usr/include にも /usr/local/include にも存在しません。Linux のシステムヘッダは処理系が GCC であれば GCC 固有の include directory も使い、GCC でなければ /usr/include だけを使うように一応書かれているようですが、stddef.h 等がないのでは困ります。

かといって、GCC 以外の処理系で GCC 固有の include directory を使うようにすると、今度はこのディレクトリにある limits.h で GCC 固有の拡張仕様である #include_next にぶつかります。たとえ #include_next を実装しても、今度は limits.h の書き方がおかしいために問題が起こります。ことに GCC V.3.3 以降では limits.h で定義すべき定数を事実上、処理系で事前定義するという乱暴なことをやるようになったため、他の処理系では limits.h が使えないという結果になってしまいます。

また、GCC 自身も、この limits.h の #include_next では不可解な動作をします。

この問題は説明するとややこしいのですが、なぜか何年ものあいだ放置されている問題なので、ここにまとめておきます。

なお、これは compiler-independent-build の mcpp で問題になるものです。 GCC-specific-build では問題は発生しません。

3.9.9.2. #include_next の奇妙な処理

GCC では include directory は通常は次のようになります。

/usr/local/include
/usr/lib/gcc-lib/SYSTEM/VERSION/include
/usr/include

これらのディレクトリが上から下へサーチされます。 この2番目が GCC 固有の include directory です。この SYSTEM は例えば i386-vine-linux, i386-redhat-linux、VERSION は 3.3.2, 3.4.3 等となります。GCC の別のバージョンを /usr/local に追加インストールした場合はこの /usr/lib/gcc-lib/usr/local/lib/gcc となります。C++ では /usr/local/include の前にさらにいくつかのディレクトリが加わります。GCC V.3.*, 4.* では次のものです。

/usr/include/c++/VERSION
/usr/include/c++/VERSION/SYSTEM
/usr/include/c++/VERSION/backward

これらのディレクトリの名前は GCC 固有のものに見えますが、C++ の標準ディレクトリはほかに存在しないので、他の処理系も /usr/include/c++/VERSION を使うしかありません。GCC V.2.95 では C++ の include directory は次のものでした。

/usr/include/g++-3

さらに -I オプションや環境変数で指定されたディレクトリが、このリストの前に追加されます。

以下では説明をわかりやすくするために GCC V.3.3 以降での C の limits.h に話を限ります。中でも LONG_MAX の定義を例にとります。limits.h は /usr/include と GCC 専用ディレクトリの2個所にあります。

#include <limits.h>

この行があると GCC は /usr/lib/gcc-lib/SYSTEM/VERSION/include/limits.h を include します。すると、このファイルの冒頭は

#ifndef _GCC_LIMITS_H_
#define _GCC_LIMITS_H_
#ifndef _LIBC_LIMITS_H_
#include "syslimits.h"
#endif

となっているので、/usr/lib/gcc-lib/SYSTEM/VERSION/include/syslimits.h が include されます。このファイルは次のような短いものです。

#define _GCC_NEXT_LIMITS_H
#include_next <limits.h>
#undef _GCC_NEXT_LIMITS_H

さて、ここで limits.h が再び include されますが、#include_next なので /usr/lib/gcc-lib/SYSTEM/VERSION/include をスキップして /usr/include がサーチされるはずです。GCC の cpp.info には次のように書いてあります。

This directive works like `#include' except in searching for the specified file: it starts searching the list of header file directories _after_ the directory in which the current file was found.

ところが GCC はなぜか /usr/include/limits.h ではなく /usr/lib/gcc-lib/SYSTEM/VERSION/include/limits.h をもう一度 include してしまいます。
今度は _GCC_LIMITS_H_ が定義されている状態なので

#ifndef _GCC_LIMITS_H_

以下のブロックはスキップされ、次のブロックが評価されます。

#else
#ifdef _GCC_NEXT_LIMITS_H
#include_next <limits.h>
#endif
#endif

またしても /usr/lib/gcc-lib/SYSTEM/VERSION/include/syslimits.h にあったのとまったく同じ #include_next <limits.h> という行です。また /usr/lib/gcc-lib/SYSTEM/VERSION/include/limits.h が、すなわち自分自身が include されるのかと思うと、今度は GCC は /usr/include/limits.h を include するのです。GCC の #include_next の動作は一向にわかりません。

/usr/include/limits.h では <features.h> 等が include されます。また、次の行で始まるブロックがあります。

#if !defined __GNUC__ || __GNUC__ < 2

このブロックでは <bits/wordsize.h> を include した上で、wordsize が 32 ビットであるか 64 ビットであるかに応じて規格で要求されている各種定数を定義するようになっています。たとえば 32 ビットであれば LONG_MAX は次のように定義されます。

#define LONG_MAX     2147483647L

しかし、GCC ではこのブロックは当然スキップされます。そして、このファイルが終わり、include 元の /usr/lib/gcc-lib/SYSTEM/VERSION/include/limits.h に戻ります。このファイルもこれで2度目の include を終わり、/usr/lib/gcc-lib/SYSTEM/VERSION/include/syslimits.h に戻り、このファイルもこれで終わって /usr/lib/gcc-lib/SYSTEM/VERSION/limits.h の最初の include に戻ります。このファイルは上記の部分のあとに各種定数の定義があります。LONG_MAX については次のようになっています。

#undef LONG_MAX
#define LONG_MAX __LONG_MAX__

これでこのファイルも終わります。

#include <limits.h>

の処理はこれですべて終わりです。 結局、LONG_MAX__LONG_MAX__ と定義されてオシマイなのです。__LONG_MAX__ とはいったい何なのでしょうか。実は GCC V.3.3 以降では __LONG_MAX__ やその他の多くのマクロが事前定義されているのです。32 ビットシステムでは __LONG_MAX__ は 2147483647L と事前定義されています。LONG_MAX 以外の規格で要求されている各種マクロについても、事前定義されたマクロを元に定義されているので、事情は大同小異です。それなら、このややこしいヘッダファイルと #include_next の処理はいったい何のためなのでしょうか?

#include_next の動作は GCC V.2.95.3, V.3.2, V.3.4, V.4.0, V.4.1 でも V.3.3 と同じです。すなわち、

#include_next <limits.h>

/usr/lib/gcc-lib/SYSTEM/VERSION/include/syslimits.h から /usr/lib/gcc-lib/SYSTEM/VERSION/include/limits.h が include され、そこにある同じ

#include_next <limits.h>

/usr/include/limits.h が include されます。

#include <limits.h>

では /usr/lib/gcc-lib/SYSTEM/VERSION/include/limits.h が2回 include されることになります。このファイルを2回 include しても結果は変りませんが、ムダであり、何より仕様と動作が違っており、動作が一貫していません。このファイルの次のブロックも本来はムダな部分です。

#else
#ifdef _GCC_NEXT_LIMITS_H
#include_next <limits.h>
#endif

3.9.9.3. GCC でないと標準ヘッダを使えない

では次に GCC 以外の処理系で Linux のシステムヘッダを使うとどうなるでしょうか? GCC 固有の include directory を使わないと、stddef.h 等が見つかりません。それなら GCC の include directory を使うとどうなるでしょうか? stddef.h は見つかりますが、今度は limits.h がおかしくなります。

#include <limits.h>

この行でプリプロセッサは /usr/lib/gcc-lib/SYSTEM/VERSION/include/limits.h を include します。そこから /usr/lib/gcc-lib/SYSTEM/VERSION/include/syslimits.h が include されます。そして、

#include_next <limits.h>

でエラーになります。

では、プリプロセッサが #include_next を実装したらどうなるでしょうか? #include_next が仕様通りに実装されていれば、ここで /usr/include/limits.h が include されます。そして、上記の

#if !defined __GNUC__ || __GNUC__ < 2

以下のブロックが処理され、LONG_MAX が次のように定義され、その他のマクロも適切な定数に定義されます。

#define LONG_MAX     2147483647L

そして、include 元の /usr/lib/gcc-lib/SYSTEM/VERSION/syslimits.h に戻り、このファイルが終わって /usr/lib/gcc-lib/SYSTEM/VERSION/limits.h に戻ります。すると、何と

#undef LONG_MAX
#define LONG_MAX __LONG_MAX__

これでいったん正しく定義されたマクロはすべてご破算にされて __LONG_MAX__ 等という未定義の名前になってしまうのです!

GCC V.3.2 までなら、これでもまだ

#define __LONG_MAX__ 2147483647L

という行があったので、 ご破算にされてももう一度、正しく定義し直されました。途中はムダな処理ですが、結果は正しいので使うことができました。しかし、V.3.3 以降のヘッダファイルではすべては徒労に終わります。

3.9.9.4. 当面の対策

以上をまとめると、問題は次の点にあります。*1, *2, *3, *4

  1. Linux のシステムヘッダを GCC 専用にしないためには /usr/includefloat.h, iso646.h, stdarg.h, stdbool.h, stddef.h が必要であるが、これがない。
  2. C++ の標準 include directory を GCC のバージョン依存にしないためには、/usr/include/c++/VERSION ではなく /usr/include/c++ を使ってもらいたいものである。/usr/include/c++/VERSION/* は GCC 固有のものに限るべきである。 FreeBSD, Linux, Mac OS X ではすべて C++ の standard library は GCC の libstdc++ なので、対処しにくいことであるが。
  3. GCC の #include_next の動作は仕様と異なっており、かつ一貫性がない。
  4. GCC が <limits.h> で定義するマクロを事実上自前で事前定義するのでは、ややこしいヘッダファイルの処理をする意味がない。/usr/lib/gcc-lib/SYSTEM/VERSION/include/limits.h ですべてを #undef するのではなおさらムダである。少なくとも Linux や CygWIN では limits.h を2つに分ける必要はないはずである。このディレクトリのヘッダファイルは GCC をインストールする時に自動生成されるものなので、ある程度の冗長性はやむをえないであろうが、それにしてもシステム・ヘッダとしてインストールするには汚すぎる。

これらの問題の根元にあるのはシステムヘッダの構成の過剰な複雑さです。そして、#include_next という拡張ディレクティブが混乱に輪をかけています。このディレクティブの用途はごく限られており、GCC や glibc のコンパイルとインストールで使われているものの、インストールされたシステムヘッダでは limits.h にあるだけです。その limits.h の処理がこう混乱しているのでは、その存在理由が疑われます。

さて、Linux および CygWIN 上の mcpp の compiler-independent 版では以上の問題に対処するため、とりあえずは次のように設定する必要があります。Compiler-independent 版では混乱を避けるため、#include_next は実装しません。GCC 固有の include directory も組み込みません。

  1. /usr/include/stddef.h/usr/lib/gcc-lib/SYSTEM/VERSION/include/stddef.h へのリンクとして作成しておく。複数の GCC のバージョンがインストールされている場合もそのどれかにリンクしておけば、Linux, CygWIN 上で compiler-independent で使う分には問題ないと思われる。これは GCC にも、GCC 専用版の mcpp にも悪影響は与えない。stdarg.h も同様である。マクロが GCC の組み込み関数に展開されるが、単にプリプロセスするだけならそれでもすむ。
  2. iso646.h, stdbool.h はごく簡単なものであり、処理系やシステムに依存するものではないので、GCC のどれかのバージョンのものを /usr/include にコピーするか移動すればすむ。limits.h は GCC 以外の処理系では /usr/include のものだけで十分である。
  3. float.h は DBL_MAX_EXP__DBL_MAX_EXP__ に定義されるといった内容なので、他のプリプロセッサには役に立たない。必要なら GCC の内部設定などを参考に書く。*5
  4. GCC 固有の C の include directory は環境変数では設定しない。
  5. 環境変数 CPLUS_INCLUDE で /usr/include/c++/VERSION:/usr/include/c++/VERSION/SYSTEM:/usr/include/c++/VERSION/backward を C++ の include directory として設定する。

GCC 専用版の mcpp では GCC 固有の include directory を組み込み、#include_next も仕様通りに実装し、互換のための事前定義マクロも定義するので、特別な設定は必要ありません。

注:

*1 この 3.9.9 の記載は Linux / GCC 2.95.3, 3.2, 3.3.2, 3.4.3, 4.0.2, 4.1.1, 4.3.0 および CygWIN / GCC 2.95.3, 3.4.4 で確認したものである。 CygWIN / GCC 2.95.3 では #include_next の動作は仕様通りであったが、3.4.4 では Linux と同じになった。 また、CygWIN の C++ の include directory は 2.95.3 では /usr/include/g++-3 であったが、3.4.4 では /usr/lib/gcc/i686-pc-cygwin/3.4.4/include/c++ 以下となっている。

*2 FreeBSD 6.2, 6.3 / GCC 3.4.6 では /usr/include に C のすべての標準ヘッダがあり、GCC 固有の include directory も C では存在しない。 #include_next もシステムヘッダには無い。 ただ、C++ の include directory は /usr/include/c++/3.4, /usr/include/c++/3.4/backward と GCC のバージョン依存となっている。
FreeBSD でも別バージョンの GCC をインストールすると GCC-version-specific な include directory が作られる。 そこにできるヘッダファイルの多くはムダなものである。 しかし、/usr/include のヘッダファイルが書き換えられることはない。

*3 Mac OS X Leopard / Apple-GCC 4.0.1 では GCC の version-specific な include directory は Linux と同様である。 #include_next も limits.h 等で使われている。 limits.h の構成も Linux と同様であるが、ただし syslimits.h の #include_next は削除されている。 また、float.h, iso646.h, stdarg.h, stdbool.h, stddef.h はすべて /usr/include にあるので、mcpp の側での対策はあまり必要ない。 しかし、float.h, stdarg.h は GCC 用と Metrowerks (for powerpc) 用なので、mcpp で使うなら stdarg.h は GCC の version-specific なディレクトリ中のヘッダを include するようにディレクティブを書き足し、float.h ではマクロを書き足しておかなければならない。 float.h は x86 と powerpc とでいくつかの値が異なることに注意。

*4 MinGW / GCC 3.4.* では include directories とその優先順位が他のシステムと異なるが、GCC の #include_next の動作は同じであり、また標準の include directory である /mingw/include にいくつかの標準ヘッダがなくて version-specific-directory にある点も CygWIN 等と同じである。

*5 GCC の設定を参照して i386 版の float.h を書けば、次のようになる。

/* float.h  */

#ifndef _FLOAT_H___
#define _FLOAT_H___

#define FLT_ROUNDS      1
#define FLT_RADIX       2

#define FLT_MANT_DIG    24
#define DBL_MANT_DIG    53
#define LDBL_MANT_DIG   64

#define FLT_DIG         6
#define DBL_DIG         15
#define LDBL_DIG        18

#define FLT_MIN_EXP     (-125)
#define DBL_MIN_EXP     (-1021)
#define LDBL_MIN_EXP    (-16381)

#define FLT_MIN_10_EXP  (-37)
#define DBL_MIN_10_EXP  (-307)
#define LDBL_MIN_10_EXP (-4931)

#define FLT_MAX_EXP     128
#define DBL_MAX_EXP     1024
#define LDBL_MAX_EXP    16384

#define FLT_MAX_10_EXP  38
#define DBL_MAX_10_EXP  308
#define LDBL_MAX_10_EXP 4932

#define FLT_MAX         3.40282347e+38F
#define DBL_MAX         1.7976931348623157e+308
#define LDBL_MAX        1.18973149535723176502e+4932L

#define FLT_EPSILON     1.19209290e-7F
#define DBL_EPSILON     2.2204460492503131e-16
#define LDBL_EPSILON    1.08420217248550443401e-19L

#define FLT_MIN         1.17549435e-38F
#define DBL_MIN         2.2250738585072014e-308
#define LDBL_MIN        3.36210314311209350626e-4932L

#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
#define FLT_EVAL_METHOD 2
#define DECIMAL_DIG     21
#endif /* C99 */

#endif /* _FLOAT_H___ */

3.9.10. Mac OS X / Apple-GCC とシステムヘッダの問題

mcpp は V.2.7 から Mac OS X / GCC もサポートするようになりましたが、このシステムの問題点を以下にまとめておきます。 ただ、筆者はまだこのシステムではほとんど mcpp と firefox くらいしかコンパイルしていないので、知らない部分が多くあります。 ことに Objective C / Objective C++ についてはまったく知りません。

この OS は GCC がほとんど唯一のコンパイラとなっているため、そのシステムヘッダには GCC-local な仕様への依存が一部に見られます。 ライブラリが glibc ではないせいか、その程度は Linux ほどではありません。 しかし、FreeBSD よりは多いようです。 もう少し整理できるはずだと思われます。

このシステムのもう1つの特徴は Apple によってかなり手を加えられた GCC がシステムコンパイラとなっていることです。 Mac OS X のシステムヘッダや Apple のソースでは、一般の GCC-local な仕様よりもむしろ Apple-GCC-local な仕様への依存のほうが目立ちます。 中でも、Intel-Mac と PowerPC-Mac の双方を1つのマシンでサポートするための拡張仕様が特徴的です。

なお、ここで取り上げるのは Mac OS X Leopard / Apple-GCC 4.0.1 です。

3.9.10.1. #include_next, #warning

GCC-local な仕様である #include_next は多くはありませんが、/usr/include/float.h, stdarg.h, varargs.h および /Developer/SDKs/MacOSX10.*.sdk/usr/include/ の同名のファイルで使われています。 それらはいずれもコンパイラが GCC であるか Metrowerks であるかによって include されるヘッダファイルを振り分けるためのものです。 GCC だと stdarg.h 等の同名のヘッダファイルが #include_next されるようになっています。
また、Linux と同様に、GCC の version-specific な include directory では limits.h に #include_next がありますが、syslimits.h のものは削除されて、少しだけ整理されています。

このディレクティブは控え目に使われていますが、float.h, stdarg.h が GCC と Metrowerks しか想定していないのは問題です。 FreeBSD と同じところまで portable に書くことはできるはずです。*1
また、/usr/include 内のヘッダファイルで GCC 用に #include_next を書くのはナンセンスです。 GCC ではこのディレクトリのほうが version-specific な include directory より優先順位が低いからです。 したがって、この #include_next は決して実行されることはありません。

#warning は /usr/include/objc/, wx-2.8/wx/ 等のディレクトリやそれに対応する /Developer/SDKs/MacOSX*.sdk/usr/include/ 内のディレクトリに時々見られます。 多くは obsolete ないし deprecated な使い方やファイルに関するウォーニングのようです。
/usr/include/c++/VERSION/backward/ およびそれに対応する /Developer/SDKs/MacOSX*.sdk/ 内の backward_warning.h はやはり deprecated なヘッダを使うなという #warning を実行するためのファイルですが、このディレクトリ内のヘッダファイルはすべてこのファイルを include するようになっています。 これは Linux や FreeBSD と同じです。

注:

*1 mcpp の compiler-independent-build でこれらのヘッダを使う方法については、3.9.9.4 およびその注 3 を参照。

3.9.10.2. 'defined' に展開されるマクロ

/usr/include/sys/cdefs.h およびそれに対応する /Developer/SDKs/MacOSX*.sdk/ 内の同名のヘッダファイルには、次のようなマクロ定義があります。

#define __DARWIN_NO_LONG_LONG   (defined(__STRICT_ANSI__) \
                && (__STDC_VERSION__-0 < 199901L) \
                && !defined(__GNUG__))

そして、このマクロが stdlib.h 等で次のように使われています。

#if __DARWIN_NO_LONG_LONG

このマクロは次のように定義すべきです。*1

#if     defined(__STRICT_ANSI__) \
                && (__STDC_VERSION__-0 < 199901L) \
                && !defined(__GNUG__)
#define __DARWIN_NO_LONG_LONG   1
#endif

注:

*1 その理由は 3.9.4.6, 3.9.8.6 を参照。

3.9.10.3. #endif 行のトークン

/System/Library/Frameworks/Kerberos.framework/Headersgssapi.h, krb5.h, profile.h には次のような奇怪な #endif 行があります。

#endif \* __KERBEROS5__ */

この \* __KERBEROS5__ */ というのはコメントのようですが、なぜわざわざこんな書き方を発明するのか、理解に苦しみます。 他のシステムの GCC ではこれにはウォーニングが出ますが、Apple-GCC ではたとえ -pedantic 等のオプションを付けてもウォーニングは出ません。 Apple-GCC では次の書き方についてもウォーニングが出ません。 C90 以前の感覚をいまだにひきずっています。

#endif __KERBEROS5__

3.9.10.4. マクロの諸問題

次のような特殊なマクロの使い方は、glibc のソースや Linux のシステムヘッダなどでしばしば見られるものですが、いずれも firefox 3.0b3pre のソースをコンパイルした限りでは、そこから include される Mac OS X のシステムヘッダ中には見られません。

3.9.10.5. Apple-GCC の独自仕様

Apple-GCC には一般の GCC と異なる独自の仕様がいくつかあります。

3.9.11. firefox 3.0b3pre ソースのプリプロセス

firefox の developing version である 3.0b3pre (2008/01) すなわち 3.0-beta3-prerelease のソースを、Linux/x86 + GCC 4.1.2 および Max OS X + GCC 4.0.1 でプリプロセッサを mcpp V.2.7 に置き換えてコンパイルしてみました。 mcpp は -K オプションのテストを兼ねて、すべて -Kv オプションを付けて実行し、その出力を cc1 (cc1plus) に渡しています。 その結果、コンパイルが正常に終了し、firefox のバイナリが生成されるのを確かめることができました。*1

firefox のソースのプリプロセス上の portability は全体としてはかなり高いものです。 glibc のソースによく見られるような GCC-local な仕様への依存はあまり見られません。 Linux と Mac OS X の GCC、Windows の Visual C を公式なターゲットとしているだけのことはあります。

とは言え、プリプロセスの portability の問題は GCC と Visual C さえ通ればすむわけでもありません。 以下に具体的に検討してみます。 なお、GCC の問題については重複を避けるため、ここでは説明しません。 3.9.4, 3.9.8 等を参照してください。 firefox と対比するため glibc をしばしば引き合いに出していますが、glibc についてのコメントもこれらのセクションにあります。*2

注:

*1 ソースは mozilla.org の CVS リポジトリから checkout したものである。 これをコンパイルした1つの動機は mcpp の -K オプションをテストするためであった。 このオプションは Taras Glek の提案によるものであるが、彼は mozilla project で C/C++ ソースの refactoring に取り組んでいるので、mcpp の作者も firefox のソースをこのオプションのテストに使ったのである。 -K (-Kv) オプションについては 2.4 を参照。

*2 firefox のコーディング・ガイドラインは次のところにある。 しかし、内容はかなり古いものである。
portable-cpp

3.9.11.1. GCC-local な仕様はほとんど使われない

glibc のソースで時々使われている次のような GCC-local な仕様は、firefox のソースには見られません。 ただし、Linux 上でコンパイルする場合は、システムヘッダに GCC2 仕様の可変引数マクロなどが出てきますが、それは別です。

次のものは glibc でも最近のバージョンでは使われなくなっているものですが、firefox でも使われていません。

3.9.11.2. #include_next

しかし、#include_next だけは1つのディレクトリに限ってたくさんあります。 これは configure によって生成される config/system_wrappers というディレクトリです。 この中に生成されるのはすべて同じ形をした約 900 本のヘッダファイルです。 例えば stdio.h は次のようになっています。

#pragma GCC system_header
#pragma GCC visibility push(default)
#include_next <stdio.h>
#pragma GCC visibility pop

これは GCC 4.* の #pragma GCC visibility * の機能を利用するためのものです。 一方で、config/gcc_hidden.h というファイルがあります。 これは次のようなもので、これが多くの translation unit で -include オプションによって冒頭に読み込まれます。

#pragma GCC visibility push(hidden)

system_wrappers ディレクトリは最優先の include ディレクトリでなければならないので、常に最初の -I オプションで指定しなければならないというのが注意すべき点ですが、この #include_next の使い方は簡明であり、特に問題はないと思われます。

ただ、nsprpub ディレクトリでは -include gcc_hidden.h の代わりに -fvisibility=hidden オプションが指定される場合がしばしばありますが、その場合は system_wrappers のヘッダファイルは使われません。 このディレクトリはまだ整理されていないようです。

3.9.11.3. C99 を指定せずに C99 の仕様を使う

目立つのは C99 を指定せずに C99 の仕様を使うものです。 GCC ではソースファイル名が *.c だとデフォルトでは gnu89 という仕様が使われますが、これは C90 にいくつかの C99 仕様と GCC 独自の仕様を加えた折衷的なものです。 firefox のソースには、これを利用して次のような C99 の仕様を暗黙のうちに使っているものがあります。

これらの仕様は Visual C 2005, 2008 でも使えます。 GCC では -std=c99 というオプションがあるので、それを使いたいところです。 しかし、Visual C には規格のバージョンを指定するオプションはないので、暗黙のうちに使うしかありません。 したがって、暗黙のうちに C99 の仕様を使うことについては、主要な処理系の現状では firefox のソースを責めることはできません。*1

ところで、暗黙のうちに C99 仕様を使っていながら、可変引数マクロはなぜか使われていません。 Visual C でも 2005 で使えるようになったのですが、2003 までは使えなかったので、避けていたのでしょうか。

注:

*1 C++ では GCC は "gnu++98" という仕様がデフォルトである。 これは C++98 + GCC extensions とされているが、実際には C99 の仕様が混在している。 一方、Visual C は C では C90、C++ では C++98 に準拠とされているが、実際にはどちらも C99 仕様と独自の仕様が混在しており、Visual C 2005, 2008 ではことにそうである。 すなわち、GCC も Visual C も諸規格と独自仕様が混在しているのである。 とりわけ Visual C は規格のバージョンを指定できないのが困るところである。

3.9.11.4. Function-like マクロの名前に置換される object-like マクロ

Function-like マクロの名前に置換される object-like マクロの定義は他のプログラムにもよく見られるものです。 firefox のソースにもあまり多くはありませんが、次のところに見られます。

content/base/src/nsTextFragment.h, modules/libimg/png/mozpngconf.h, modules/libjar/zipstub.h, modules/libpr0n/src/imgLoader.h, nsprpub/pr/include/obsolete/protypes.h, nsprpub/pr/include/private/primpl.h, nsprpub/pr/include/prtypes.h, parser/expat/lib/xmlparse.c, security/nss/lib/jar/jarver.c security/nss/lib/util/secport.h, xpcom/glue/nsISupportsImpl.h

また、firefox を make することで多くのヘッダファイルへのリンクが開発環境用のディレクトリに作られ、それらは firefox の開発環境をインストールすると /usr/include/firefox-VERSION/ に入っていきますが、その中には上記のファイルへのシンボリックリンクがいくつかあります。 configure によって生成される mozilla-config.h にもこの種のマクロ定義があります。

これらはやはり function-like マクロとして書いたほうが、ソースがわかりやすくなります。 実際、firefox のソースにもその書き方は多くあります。 人によって書き方が異なるのでしょうが、これについてはコーディング・ガイドラインを決めたほうが良さそうです。

3.9.11.5. 'defined' に展開されるマクロ

glibc で時に見られる置換リスト中に 'defined' というトークンの出てくるマクロが、firefox にも1つだけあります。

modules/oji/src/nsJVMConfigManagerUnix.cpp というファイルでは次のようなマクロを定義して、

#define NS_COMPILER_GNUC3 defined(__GXX_ABI_VERSION) && \
                          (__GXX_ABI_VERSION >= 102) /* G++ V3 ABI */
自分自身の中で次のように使っています。

#if (NS_COMPILER_GNUC3)

このマクロは廃止して、#if 行は次のように改めるべきです。

#if defined(__GXX_ABI_VERSION) && (__GXX_ABI_VERSION >= 102) /* G++ V3 ABI */

このファイルが GCC でしかコンパイルされないものだとは言え、処理系の誤仕様を利用するのは良いことではありません。*1

注:

*1 mcpp も V.2.7 からは GCC-specific-build では #if 行のマクロに出てくる 'defined' を GCC と同様に処理するようにした。 しかし、ウォーニングが出るので、ソースを修正したほうが良い。

3.9.11.6. #endif のうしろの token sequences

jpeg ディレクトリの中の次のファイルには、何と #endif 行にコメントがコメントマークなしに書かれている行があります。 いずれも最近のアプデートで発生したものです。

jmorecfg.h, jconfig.h, jdapimin.c, jdcolor.c, jdmaster.c

この書き方は 1990 年代の中ごろまでは UNIX 系のソフトウェアに見られましたが、いまではほぼ消失しているものです。 かの glibc の世界でさえも使われていません。 さすがに GCC もこれにはウォーニングを出します。 しかし、これらのソースはそんなことにはおかまいなしのようです。 Apple-GCC はウォーニングを出しませんが、これは Mac OS 上でエディットされたものなのでしょうか。

3.9.11.7. プリプロセスを要するアセンブラソース

アセンブラソースは *.s (*.asm) という名前になっていて、中にはマクロが使われているものもありますが、プリプロセッサが呼び出されることは原則としてありません。

しかし、Mac OS X / ppc では1つだけ例外があり、xpcom/reflect/xptcall/src/md/unix/xptcinvoke_asm_ppc_rhapsody.s でプリプロセッサが呼び出されます。 このファイルにはたった1行の #if ブロックが1つあるからですが、今となっては必要ないと思われるブロックです。

3.9.11.8. -include オプション

firefox のコンパイルでは configure で mozilla-config.h というヘッダファイルが生成されますが、大半のソースのコンパイルでこのヘッダを -include オプションで指定しています。 config/gcc_hidden.h についても同様です。 なぜこれらをソースの冒頭で #include しないのでしょうか?

3.9.11.9. マクロの再定義

マクロが黙って再定義されることが稀にあります。

3.9.11.10. 長大なコメント

次のファイルには数百行以上におよぶ長大なコメントがあります。

extensions/universalchardet/src/base/Big5Freq.tab, extensions/universalchardet/src/base/EUCKRFreq.tab,intl/unicharutil/src/ignorables_abjadpoints.x-ccmap, layout/generic/punct_marks.ccmap

ことに intl/uconv/ucv*/ というディレクトリには長大なコメントを持つファイルが多数あります。 中には1つのコメントが 8000 行を超えるものまであります! それらはすべて *.uf, *.ut という名前になっています。 いずれも unicode とアジアの各種 encoding との間の mapping table のようで、ツールによって自動生成されるものです。 C/C++ のソースには見えませんが、C++ のソースから include されます。 コメントはある種のドキュメントか他のツールのための表のようです。

長大なドキュメントや表をコメントとしてソース中に含めるのは、いかがなものでしょうか。 ソースツリーに含めるとしても、ソースとは別のファイルに分離すべきでしょう。

以上のファイルはいずれも Linux では使われますが、Mac OS X では使われません。 他方で Mac OS X では framework ディレクトリ中のシステムヘッダがしばしば使われますが、その中にはコメントが大半を占めている奇妙なファイルが時々見られます。

3.9.11.11. 改行コードの混在

firefox のソースの改行コードは [LF] ですが、[CR][LF] の行が少し混ざっているファイルが数本あります。 これらはいずれも短いブロックで、ソースにパッチを挿入する時に、その部分だけ [CR][LF] になってしまったもののようです。 ソースを Windows 上で編集する時は、改行コード変換ツールでチェックすべきでしょう。


3.10. Visual C++ のシステムヘッダの問題

Visual C++ 2003, 2005, 2008 でいくつかのサンプルプログラムのプリプロセスに mcpp を使ってみました。このシステムのシステムヘッダには、プリプロセス上の互換性が問題となるようなものはごく少ないようです。次のようなものはありますが、これらは他の処理系でもしばしば見られるもので、特に問題となるものではありません。

  1. C99 の仕様はほとんど実装されていなかったころから、C で // コメントが多用されてきた。
  2. Function-like マクロの名前に展開される object-like マクロの定義が時々見られる。
  3. Visual C++ 2003 では limits.h に間違ったマクロ定義が1つあった (Visual C++ 2005 では直された。 cpp-test.html#5.1.3.1 の注2 を参照)。

Linux のシステムヘッダや glibc には GCC local な仕様がしばしば使われていますが、Visual C++ のシステムヘッダには Visual C++ local な書き方はあまり見られません。

3.10.1. コメントを生成するマクロ?

しかし、Visual C++ には1つだけとんでもないマクロがあります。Visual C++ 2003 の Vc7/PlatformSDK/Include/wtypes.h には次のようなマクロ定義があります。*1

#define _VARIANT_BOOL   /##/

そして、Vc7/PlatformSDK/Include/oaidl.h, propidl.h で次のように使われています。

_VARIANT_BOOL bool;

これはいったい何でしょうか?
これは _VARIANT_BOOL が // に展開されて、その結果、この行がコメントアウトされることを期待しているもののようです。そして、実際に Visual C の cl.exe ではそうなってしまいます!

しかし、// はトークン (preprocessing-token) ではありません。また、マクロの定義や展開は、ソースがトークンに分解されコメントが1個のスペースに変換されたあとのフェーズで処理されるものです。したがって、マクロによってコメントを生成することは決してできないのです。このマクロは // に展開されたところで、// は有効な preprocessing-token ではないので結果は undefined となるはずのものです。

mcpp でこれらのヘッダファイルを使うためには、このマクロ定義をコメントアウトし、数ヵ所ある _VARIANT_BOOL 云々のところを次のように書き換えなければなりません。

#if !__STDC__ && (_MSC_VER <= 1000)
    _VARIANT_BOOL bool;
#endif

Visual C 5.0 以降のバージョンしか使わないのであれば、この行は次のように本当にコメントアウトしてかまいません。

// _VARIANT_BOOL bool;

このマクロは論外ですが、それ以上に問題なのは、これをコメントとして処理してしまう Visual C / cl.exe のプリプロセスの実装です。この例には、このプリプロセッサの次のような深刻な問題が露呈しています。

  1. 少なくともこの例では Token-base ではなく文字ベースのプリプロセスがされている。
  2. マクロの展開結果がコメントとして扱われており、translation phases が混乱している。

おそらく、cl.exe のプリプロセッサは非常に古い、どちらかと言えば文字ベースのプリプロセッサのソースを元にしているのでしょう。それに部分的に手を加えながらバージョンアップを繰り返してきていることが推測されます。

こうした非常に古いプログラム構造を持っていると推測されるプリプロセッサは多くあります。3.9 で見た GCC 2 / cpp もその1つです。こうしたプリプロセッサでは、部分的に手を加えれば加えるほどプログラム構造がゴチャゴチャしてくるので、いくら改良してもあるところで品質は頭打ちとなります。古いソースを捨てて、初めから書き直さない限り、すっきりしたプリプロセッサにはならないと思われます。

GCC 3 / cpp ではソースが新しく書き直されて、GCC 2 とは別のプリプロセッサとなりました。mcpp も、DECUS cpp という古いプリプロセッサのソースから出発しながら、出発してまもなく全面的に書き直されたものです。

注:

*1 Visual C++ 2005 express edition には Platform SDK は含まれていないが、"Platform SDK for Windows 2003" をダウンロードして使うことができる。その PlatformSDK/Include ディレクトリの wtypes.h, oaidl.h, propidl.h でも、このマクロは同じである。
Visual C++ 2008 express edition の Microsoft SDKs/Windows/v6.0A/Include ディレクトリの同名のヘッダファイルでも同様である。

3.10.2. Identifier 中の '$'

もう一つの問題は、Visual C++ 2008 のシステムヘッダではマクロ名に '$' を使うケースが突然増えたことです。 これは Visual C++ 2005 までにもなくはありませんでしたが、例外的なものにとどまっていました。 しかし、2008 ではあちこちに出てきています。

最も目立つのは Microsoft Visual Studio 9.0/VC/include/sal.h というヘッダです。 これは Visual C++ の SAL (standard source code annotation language) なるものをソース中に記述するためのマクロを定義しているものです。 ここでは '$' を含む名前が大量に使われています。 このヘッダは多くの標準ヘッダファイルから Microsoft Visual Studio 9.0/VC/include/crtdefs.h というヘッダを介して #include されます。 したがって、多くのソースのコンパイルではこれらのマクロが知らないうちに使われることになります。

-Za オプションを付けて cl(コンパイラ)を起動すると、SAL は無効になり、sal.h の '$' を含む名前はすべて消えるようにはなっていますが、なぜこんな名前が必要なのか理解に苦しみます。 GCC でも identifier に '$' が使えるのがデフォルトになっていますが、実際には使われている例はほとんど見掛けなくなっています。

Microsoft SDKs/Windows/v6.0A/Include ディレクトリにもこの種の名前を持つヘッダファイルがあります。 specstrings*.h というヘッダです。 これらのヘッダは WinDef.h を介して Windows.h から include されますが、こちらは -Za オプションを指定しても '$' が消えるようには書かれておらず、単にエラーになるだけです。 したがって、Windows.h を include するソースは -Za オプションでコンパイルすることはできません。


4. 処理系定義の仕様

C言語のプリプロセス仕様を逐一ここに書くわけにはゆきません。cpp-test.html に Standard C のプリプロセスについて詳しい解説を書いてあるので、そちらを読んでください。mcpp の各種モードの動作仕様については、 2.1 を見てください。ここでは Standard C で処理系定義とされているものを含めて、プリプロセスの周辺のいくつかの仕様を述べます。さらにこまかな処理系定義仕様については、5 診断メッセージに書いてあります。


4.1. 終了時の status 値

mcpp 終了時に親プロセスに返す値は internal.H というヘッダで定義されています。エラーがなかった場合は 0 を返し、エラーがあった場合は errno != 0 なら errno を errno == 0 なら 1 を返します。


4.2. Include directory のサーチパス

#include directive で include するファイルは次の順序でサーチされます。

  1. #include ディレクティブの引数が "file-name" または <file-name> の形でない場合、それがマクロであればそれを展開する。その結果は "file-name", <file-name> のどちらかの形でなければならない。そうでない場合はエラーとなる。

  2. "file-name" の形でも <file-name> の形でも file-name がフルパスリストであれば、そのままオープンする。オープンできなければエラーとする。

  3. フルパスリストでなくて "file-name" の形であれば、次のディレクトリ(からの相対パス)と解釈してサーチする。-I1 オプションでは 3.1、-I2 では 3.2、-I3 ではその双方(この順で)となる。デフォルトでは、UNIX 系の処理系, GCC, Visual C 用では 3.2、その他では原則として 3.1 である。ただし、Borland C 版では 3.1+3.2である。 Compiler-independent 版では 3.2 である。

    3.1. カレントディレクトリ(もちろん mcpp 起動時の)。#include がネストされていても、常にカレントディレクトリを基準とする。
    3.2. ソースファイル(インクルード元)のあるディレクトリ。#include がネストされている場合、ヘッダファイルが別ディレクトリにあると、そのたびに基準がズレてゆく。

    GCC 版ではさらに -iqoute オプションで指定されたディレクトリがサーチされる。 Visual C 版ではソースファイルの親(インクルード元)ファイルのディレクトリも順次さかのぼって、サーチされる。 それで発見できなければ、<file-name> の形と同様のサーチをする。

  4. フルパスリストでなくて <file-name> の形であれば、次のディレクトリをサーチする。これらのディレクトリそのものが相対パスで指定されている場合は、カレントディレクトリからの相対パスと解釈する。これらを順にすべてサーチしてもファイルをオープンできなければエラーとする。

    4.1. mcpp 起動時に -I <directory> オプションで指定されたディレクトリ。複数あれば指定された順に(左から)サーチする。
    4.2. GCC 版では -isystem オプションで指定されたディレクトリ。複数あれば指定された順に(左から)サーチする。
    4.3. 環境変数で指定されたディレクトリ。この環境変数の名前は、noconfig.H (configed.H) の ENV_C_INCLUDE_DIR で定義されている。C++ では ENV_CPLUS_INCLUDE_DIR が定義されていればその環境変数が先に使われる。GCC 版では C_INCLUDE_PATH(C++ では CPLUS_INCLUDE_PATH も)、その他では INCLUDE(C++ では CPLUS_INCLUDE も)をデフォルトの環境変数名としている。環境変数で複数のディレクトリが separator で区切って指定されていれば、それらを最初のものから順にサーチする(separator は Windows では ;、その他では :)。
    4.4. noconfig.H (configed.H) のマクロ CPLUS_INCLUDE_DIR? で定義された implementation-specific なディレクトリ。
    4.5. system.c の set_sys_dirs() で指定された site-specific なディレクトリ(UNIX 系では /usr/local/include)。
    4.6. noconfig.H (configed.H) のマクロ C_INCLUDE_DIR? で定義された implementation-specific なディレクトリ。
    4.7. system-specific なディレクトリ(UNIX 系では /usr/include)。

-I- (GCC では -nostdinc、Visual C では -X) オプションを指定すると、上記の 4.4 以降のサーチは行われません。

パスの基準をカレントディレクトリとするのは、ANSI C Rationale では委員会の「意図 (intent)」であるとされています。基準ディレクトリが動くことがなく仕様が明確なので、妥当だと思われます。しかし、UNIX 系の処理系等では、少なくとも #include "header" の形式では include 元のソースファイルのあるディレクトリを基準とする習慣があるようです。mcpp も compiler-independent 版では大勢に従ってソースファイルのディレクトリを基準としました。


4.3. Header name の構築法

Header-name という pp-token の構築法と、そこから実際の include file のファイル名を取り出す方法は、次の通りです。

  1. ソース中に文字列リテラルの形式で書かれていれば、それをそのまま header-name とする。ソース中にマクロで書かれていて、それを展開した結果が文字列リテラルになった場合も、同様である。文字列リテラルの形式の header-name では、単にその両端の " をとったものをファイル名とする。
  2. ソース中に <filename> の形で書かれていれば、それをそのまま header-name とする。ソース中にマクロで書かれていて、それを展開した結果が <filename> の形になった場合も、同様である。マクロ中の space は複数の spaces を1個の space に圧縮した上で保存される。単に両端の <, > をとったものをファイル名とする。
  3. どの場合でも、Windows では path-delimiter として \ も / も使えるが、\ は / に変換する。

4.4. #if 式の評価

#if 式の評価はホスト処理系(mcpp をコンパイルした処理系)とターゲット処理系(mcpp を使う処理系)が持つ最大の整数型によって決まります。双方の型が異なる時は、小さいほうの型が #if 式の型になります。Compiler-independent 版の mcpp ではターゲット処理系は存在しないので、ホスト処理系によって決まります。

pre-Standard モードでは #if 式は (signed) long だけで評価します。

long long を持たない処理系の Standard モードでは、long および unsigned long で行います。

long long を持つ処理系の Standard モードでは、#if 式は long long / unsigned long long で評価します。C90, C++98 では long / unsigned long で評価するのが規定ですが、mcpp では C90, C++98 でも long long / unsigned long long で評価し、long / unsigned long の範囲を超える場合はウォーニングを出します。*1

Visual C, Borland C 5.5 では long long はありませんが、それと同サイズの __int64 という型があるので、#if 式は __int64 / unsigned __int64 で評価します(ただし、Visual C++ 2002 までと Borland C 5.5 では LL, ULL という suffix が使えないので、これらの suffix は #if 行では使えるが地の文で使ってはいけない)。

また、-+ オプションで C++ のプリプロセスをする時は、#if 式中の true, false という pp-token の値はそれぞれ 1L, 0L と評価します。

Standard モードでの具体的な評価のしかたを、以下に説明します。long long を持たない処理系の場合は、以下の 4.4, 4.5 の記載はすべて、long long / unsigned long long をそれぞれ long / unsigned long と読み替えてください。Pre-Standard モードではすべて long と読み替えてください。

  1. 個々の整数定数トークン(文字定数を含む)は、数値トークンに接尾子 U が付いていれば unsigned long long で評価する(pre-Standard モードでは接尾子 U は認知しない)。
  2. そうでなければ、long long の非負の範囲におさまれば long long で評価する。
  3. そうでなくて unsigned long long の範囲に入れば unsigned long long で評価する。
  4. それも越える値は out of range のエラーとする。
  5. 二項演算は被演算数のどちらかが符号なしであれば符号なしで、そうでなければ符号つきで行う。

どちらにしても整数定数トークンは常に非負の値をとります。
pre-Standard モードでは整数定数トークンの評価は非負の long の範囲で行い、それを越える値は out of range とします。その演算もすべて long で行います。

また、ホストの unsigned long long のほうがターゲットよりも範囲が狭い場合は、それを超える値は out of range となります。
定数同士の演算結果が範囲外となった場合は、long long では out of range のエラーとなり、unsigned long long ではウォーニングが出ます。演算の中間結果についても同様です。

負数の右ビットシフトや負数を含む割り算には移植性がないので、ウォーニングを出します。符号なし型と符号つき型の混合演算によって符号つき型の負の値が符号なし型の正の値に変換された場合も、ウォーニングを出します。実際の演算は、ホスト処理系のコンパイラ本体の仕様に従います。

C90, C++98 ではプリプロセスでの #if 式の評価はすべて long / unsigned long で(C99 ではその処理系の最大の整数型で)行うことになっています。mcpp では C90, C++98 でも long long / unsigned long long で評価し、long / unsigned long の範囲を超える場合はウォーニングを出します。どちらにしても、コンパイラ本体での if (expression) の評価の仕方よりは大ざっぱなものです。符号拡張が関係する場合には、コンパイラ本体とは違う結果になることが往々にしてあります。

また、Standard C のプリプロセスでは keyword というものが存在しないので、sizeof やキャストは使えません。もちろん、変数や列挙定数や浮動小数点数は使えません。Standard モードでは #if 式に defined 演算子が使え、#elif ディレクティブも使えます。あとはコンパイラ本体での if (expression) と同様に、各演算子の優先順位とグルーピング規則(いわゆる結合規則)に従って評価が行われます。2項演算子の多くでは、両辺を同型にするための算術変換が行われ、片方が unsigned long long の場合は他方は long long であっても unsigned long long に変換されます。

注:

*1 mcpp V.2.5 までは C90, C++98 では内部的には long long / unsigned long long で評価しながら、long / unsigned long の範囲を超える場合はエラーとしていたが、V.2.6 からは GCC や Visual C との互換性のためにエラーをウォーニングに格下げした。


4.5. #if 式での文字定数の評価

#if 式の定数トークンとしては識別子(マクロ、非マクロ)、整数の数値トークン、文字定数がありますが、このうち文字定数の評価の仕方はほとんど implementation-defined であり、portability はあまりありません。#if 'const' と compiler-proper での if ('const') との間でさえも結果が違う場合があります(Standard C でも、これが同じであることは保証されていない)。

POSTSTD モードではこのほとんど意味のない #if 式中の文字定数の評価は行いません(エラーとなる)。

文字定数の評価は他の整数定数トークンと同様に、long long, unsigned long long の範囲で常に正の値に評価します。pre-Standard モードでは long の範囲です。

Single character でない multi-byte character および wide character は、encoding が UTF-8 の場合は4バイト、それ以外ではすべて2バイトの型で評価します。UTF-8 はサイズが可変なので、4バイトの型で評価します。EUC-JP の3バイト encoding には対応していません(3バイト文字は1バイト+2バイトの2文字として認識される。その結果、値は正しく評価されることになる)。2バイトの encoding でありながら、wchar_t が4バイトの型である処理系もありますが、mcpp は wchar_t には関知しません。以下では2バイトの multi-byte character encoding の場合について説明します。

'字' というような multi-byte character constant は ((1バイト目の値 << 8) + 2バイト目の値) と評価します(8 は <limits.h> の CHAR_BIT の値)。

'ab', '\x12\x3', '\x123\x45' というような multi-character character constant では、'a', 'b', '\x12', '\x3', '\x123', '\x45' 等をそれぞれ1バイトとして [0, 0xFF] の範囲で評価し、その結果を上位バイトから順次 8 ずつ左シフトさせながら足してゆきます(0xFF は <limits.h> の UCHAR_MAX の値)。1つの escape sequence の値が 0xFF を超えた時は、out of range のエラーとなります。したがって、文字セットが ASCII であれば、この3つのトークンの値はそれぞれ 0x6162, 0x1203, エラーとなります。

L'字' は '字' と同じ値となります。L'ab', L'\x12\x3', L'\x123\x45' 等の multi-character wide character constant については、L'a', L'b', L'\x12', L'\x3', L'\x123', L'\x45' をそれぞれ1つの wide character として [0, 0xFFFF] の範囲で評価し、その結果を上位の wide character から順次 16 ずつ左シフトさせながら足してゆきます。1つの escape sequence の値が2バイト符号なし整数の最大値を超えた時は、out of range エラーとなります。したがって、文字セットが ASCII であれば、この3つのトークンの値はそれぞれ 0x00610062, 0x00120003, 0x01230045 となります。

Multi-character character constant, multi-character wide character constant の値が unsigned long long の範囲を超えた時は out of range エラーとなります。

__STDC_VERSION__ または __cplusplus の値が 199901L 以上の場合は、\uxxxx, \Uxxxxxxxx の形の UCN (universal-character-name) を16進 escape sequence として評価します(こういう評価をしても何の意味もないが、しかしこう評価するしかないのである)。

ターゲット処理系のコンパイラ本体で char や wchar_t が符号ありの場合は、#if 式での文字定数の評価とコンパイラ本体での if (expression) による文字定数の評価とは、結果が違ってくることがあります。範囲エラーとなる範囲も違う可能性があります。また、multi-character character constant, multi-byte character constant の評価は、プリプロセッサだけでなくコンパイラ本体でも処理系によってまちまちです。CHAR_BIT が8であっても、'ab' を 'a' * 256 + 'b' と評価するか、それとも 'a' + 'b' * 256 と評価するかさえも、Standard C では決められていません。

一般に、#if 式では文字定数はそれに代わる手段がある限りは使うべきではありません。それに代わる手段がない場合というのは、私には思い付きませんが。


4.6. #if sizeof (type)

Standard C ではプリプロセスは実行時環境やコンパイラ本体の仕様から独立した文字通りのプリプロセスとして規定が明確にされ、その結果、#if 行では sizeof とキャストは使えないことになりました。しかし、pre-Standard モードでは #if 行で sizeof (type) が使えるようになっています。これは DECUS cpp を継承して、それに long long, long double の処理を追加する等の手を加えたものです(さすがにキャストを実装するのは煩雑なので、やっていない。やる気もない)。

eval.c の S_CHAR 等の S_* というマクロでは各型のサイズが定義されていますが、クロス処理系で使う場合は、もしホストとターゲットとでこれらの型の扱いが異なるなら、その値としてターゲット処理系のこれらの型のサイズを整数値で直接書く必要があります。

mcpp の #if sizeof には手抜きがあります。char, short, int, long, long long の頭に付く signed, unsigned は単に無視します。また、sizeof (void *) はサポートしません。いささか中途半端ですが、こういう後ろ向きの機能のために system.H のフラグを増やして煩雑にしたくないのです。どうせキャストもサポートしないのだから sizeof は削除しようかとも思いましたが、せっかく旧版にあったものなので、若干の手を加えただけで残してあります。


4.7. White-space sequence の扱い

mcpp は translation phase 3 の tokenization に際して、token separator としての複数の white spaces の sequence は、改行コード以外は原則として one space に圧縮します。 しかし、STD モードで -k または -K オプションが指定されたときは、圧縮せずにそのまま出力します。 また、行末の white space sequence は削除します。

行頭の white spaces は POSTSTD モードでは削除し、他のモードでは特別扱いで、そのまま出力します。 後者は人間が出力を読む場合のつごうに合わせてあります。*1

ただし、これはプリプロセスの中間段階の話です。その後に phase 4 があって、マクロ展開とプリプロセスディレクティブ行の処理が行われます。マクロ展開の後ではその前後に複数の spaces ができることがあります。もちろん、space がいくつあろうと、コンパイルの結果は何も変わりません。

Standard C では translation phase 3 でこれを one space に圧縮するかどうかは implementation-defined とされていますが、通常はユーザはまったく気にする必要はありません。Portability が問題になるのは、preprocessing directive 行に <vertical-tab> または <form-feed> がある場合だけです。この場合は Standard C では undefined です。mcpp ではこれらは space に変換します。

注:

*1 V.2.6.3 までは原則として one space に圧縮していたが、V.2.6.4 から変更した。


4.8. mcpp 実行プログラムのデフォルトの仕様

noconfig ディレクトリにある各処理系用の差分ファイルと makefile を使ってデフォルトの設定でコンパイルした場合の mcpp 実行プログラムの仕様をここに書いておきます。Configure スクリプトで設定を生成してコンパイルした場合は configure の結果によって違ってきますが、OS と処理系のバージョンが同一であれば、少なくともインクルードディレクトリ以外は同じ結果になるはずです。

Compiler-independent 版の mcpp の仕様には処理系による相違はほとんどありませんが、OS と CPU による相違が少しあります。

mcpp には compiler-independent-build と compiler-specific-build とがあり、そのどちらでもいくつかの動作モードがあります。それについては 2.1 を見てください。ここでは STD モードを中心に説明します。

これらの差分ファイルと makefile は次の処理系用のものです。

FreeBSD 6.3 GCC V.3.4
Vine Linux 4.2 / x86 GCC V.2.95, V.3.2, V.3.3, V.3.4, V.4.1
Debian GNU/Linux 4.0 / x86 GCC V.4.1
Ubuntu Linux 8.04 / x86_64 GCC V.4.2
Fedora Linux 9 / x86 GCC V.4.3
Mac OS X Leopard / x86 GCC V.4.0
CygWIN 1.3.10 (GCC V.2.95), 1.5.18 (GCC 3.4)
MinGW & MSYS GCC 3.4
WIN32 LCC-Win32 2003-08, 2006-03
WIN32 Visual C++ 2003, 2005, 2008
WIN32 Borland C++ V.5.5

このほか、私の持っていない次の処理系についても、ユーザから contribute された差分ファイルが収録されています。

WIN32 Visual C++ V.6.0, 2002
WIN32 Borland C++ V.5.9 (C++Builder 2007)

いずれもそれらの処理系自身でコンパイルされます。

noconfig.H, system.H で定義されるマクロのうち、次のものはどの処理系用もすべて同じ設定にしてあります。

DIGRAPHS_INIT == FALSE でコンパイルされているので、digraph は -2 (-digraphs) オプションで有効となります。
TRIGRAPHS_INIT == FALSE としているので、trigraph は -3 (-trigraphs) オプションで有効となります。
OK_UCNTRUE にしているので、C99, C++ で UCN (universal character name) が使えます。
OK_MBIDENTFALSE としているので、識別子中に multi-byte-character は使えません。
STDC は 1 としているので、__STDC__ の初期値は 1 となります。

各種の translation limits は次のようにしています。

NMACPARS(マクロの引数の最大数) 255
NEXP (#if 式中の副式の最大ネストレベル) 256
BLK_NEST(#if section の最大ネストレベル) 256
RESCAN_LIMIT(マクロ再走査の最大ネストレベル)64
IDMAX (identifier の有効長) 1024
INCLUDE_NEST(#include の最大ネストレベル) 256
NBUFF(ソースの最大行長)*1 65536
NWORK(出力の最大行長) 65536
NMACWORK(マクロ展開等の内部バッファのサイズ)262144

ただし、GCC 版、Visual C 版では出力の最大行長は NWORK ではなく NMACWORK です。

次のマクロは OS によって異なった設定にしています。build type には関係しません。

MBCHAR (デフォルトの multi-byte character encoding)

FreeBSD, Linux, Mac OS X EUC_JP
Win32, CygWIN, MinGW SJIS

次のマクロは処理系によって異なった設定にしています。

STDC_VERSION__STDC_VERSION__ の初期値)

Compiler-independent, GCC 2199409L
その他 0L

HAVE_DIGRAPHS (digraphs をそのまま出力するか)

Compiler-independent, GCC, Visual CTRUE
その他 FALSE

EXPAND_PRAGMA (C99 で #pragma 行の引数をマクロ展開するか)

Visual C, Borland CTRUE
その他 FALSE

GCC 2.7-2.95 では __STDC_VERSION__ は 199409L となっていましたが、3.*, 4.* では __STDC_VERSION__ はデフォルトでは事前定義されず、実行時オプションに応じて定義されるようになりました。mcpp の GCC 用の設定はこれに対応したものです。

STDC_VERSION が 0L のものでは、__STDC_VERSION__ はデフォルトでは 0L に pre-define されます。-V199409L オプションで __STDC__ が 1 で __STDC_VERSION__ が 199409L、事前定義マクロは '_' で始まるものだけという、厳密な C95 モードとなります。また、-V199901L オプションで C99 モードとなります。

C99 モードでは __STDC_HOSTED__ が 1 に pre-define されます。__STDC_ISO_10646__, __STDC_IEC_559__, __STDC_IEC_559_COMPLEX__mcpp 自身は pre-define しません。処理系のシステムヘッダに任せます。実際には、glibc 2 / x86 のシステムではシステムヘッダによって __STDC_IEC_559__, __STDC_IEC_559_COMPLEX__ が 1 に定義され、他の処理系ではどれも定義されません。

HAVE_DIGRAPHSFALSE のものでは(digraph を実装していない処理系)、digraph は mcpp で通常の token に変換されてから出力されます。

EXPAND_PRAGMATRUE であっても、STDC, MCPP, GCC のどれかで始まる #pragma 行はマクロ展開しません。

Include ディレクトリは次のように設定してあります。
まず、UNIX 等で言ういわゆる system-specific なものおよび site-specific なものは次の通りです。これは compiler-independent 版でも同じです。

FreeBSD, Linux, Mac OS X, CygWIN/usr/include, /usr/local/include

Mac OS X ではこのほか、framework ディレクトリが /System/Library/Frameworks, /Library/Frameworks に設定されます。

MinGW では /mingw/include が標準の include ディレクトリになります。

処理系やそのバージョンによって異なる implementation-specific なものについては *.dif ファイルを見てください。Compiler-independent 版ではこれらは定義されません。Windows の処理系ではこれらは特に設定せず、環境変数を参照します。環境変数は INCLUDE, CPLUS_INCLUDE です。Compiler-independent 版でも同様。
これでつごうが悪ければ、設定を変えて mcpp をリコンパイルするか、環境変数で指定するか、-I オプションで指定するかしてください。

mcpp は GCC 版, Visual C 版以外では、プリプロセスした結果が NWORK-1 を超える時は、これ以下の長さに行を分割して出力します。文字列リテラルの長さは NWORK-2 以下でなければなりません。

念のために繰り返しますが、以上の italic で表示されているマクロはいずれも mcpp をコンパイルした時のものであり、mcpp の実行プログラムが持っている組み込みマクロではありません。

入力ファイルを指定せずに mcpp を起動して、#pragma MCPP put_defines と打ち込むと、組み込みマクロの一覧が表示されます。

__STDC__ が 1 以上の状態では、_ で始まらない組み込みマクロは削除されます。-N (-undef) オプションでは __MCPP 以外がすべて削除されます。その上で -D オプションで設定し直してもかまいません。処理系のバージョンが少し違うだけで include directory が違わない場合は、mcpp をリコンパイルしなくても、この方法でバージョンマクロを再定義することで別バージョンに対応させることができます。-N や -U を使わなくても、-D で特定のマクロだけ再定義することができます。

-+ (-lang-c++) オプションで C++ のプリプロセスを指定した時は、__cplusplus が事前定義されますが、その初期値は 1L です。そのほかにさらにいくつかのマクロが事前定義されます。

GCC では V.3.2 までは事前定義マクロと言っても GCC が事前定義するものは少なく、多くは gcc から cpp に -D オプションで渡されるものでした。それとの互換性のためには mcpp で定義する必要はないのですが、mcpp を("pre-preprocess" 等で)単体で動かす時の便宜のために mcpp 内で定義しています。
GCC V.3.3 以降は突然、60 個から 70 個のマクロが事前定義されるようになりました。mcpp V.2.5 以降でも GCC 専用版ではやむなくこれを取り込んでいます。したがって、GCC V.3.3 以降用の mcpp では上記のほかに多くのマクロが事前定義されます。その内容は mcpp をインストールする時に生成される mcpp_g*.h というヘッダファイルでわかります。

FreeBSD, Linux, CygWIN, MinGW / GCC, LCC-Win32, Visual C 2008 は long long を持っているので、#if 式は long long, unsigned long long で評価します。Visual C 6.0, 2002, 2003, 2005, Borland C 5.5 では long long はありませんが、__int64, unsigned __int64 という型があるのでこれを使います。

これらの処理系では long の範囲はいずれも

[-2147483647-1, 2147483647] ([-0x7fffffff-1, 0x7fffffff])

です。unsigned long はいずれも

[0, 4294967295] ([0, 0xffffffff])

の範囲です。

long long を持つ処理系ではいずれも long long の範囲は

[-9223372036854775807-1, 9223372036854775807]
([-0x7fffffffffffffff-1, 0x7fffffffffffffff])

で、unsigned long long の範囲は

[0, 18446744073709551615] ([0, 0xffffffffffffffff])

です。

これらの処理系本体ではいずれも符号つき整数型の内部表現は2の補数であり、ビット演算もそれに対応しているので、mcpp の #if 式でも同様です。
負の整数の右シフトはいずれも算術シフトであり、mcpp の #if 式でも同様です(1ビットの右シフトで値が符号つきのまま 1/2 になる)。
整数の除算・剰余算で operand の片方または双方が負である場合はいずれも Standard C の ldiv() 関数と同じ代数的演算が行われるので、mcpp の #if 式でも同様です。

これらのシステム(OS)ではいずれも基本文字セットが ASCII なので、mcpp でも同様です。

私が書いた kmmalloc というメモリー管理ルーチンがあり、malloc(), free(), realloc() 等を含んでいますが、CygWIN, Visual C 2005, 2008 以外ではこれがインストールされている場合は、make する時に MALLOC=KMMALLOC(または -DKMMALLOC=1)というオプションを指定するとこれが link されます。ヒープメモリのデバッグ用ルーチンもリンクされるようになっています。errno の番号 EFREEP, EFREEBLK, EALLOCBLK, EFREEWRT, ETRAILWRT には Linux, LCC-Win32 ではそれぞれ 2120, 2121, 2122, 2123, 2124 を、それ以外ではそれぞれ 120, 121, 122, 123, 124 を割り当てています( mcpp-porting.html#4.extra 参照)。*2

GNU のシステムおよび Visual C 以外では、環境変数 TZ はあらかじめ JST-9 にセットしておく必要があります。そうしないと、__DATE__, __TIME__ マクロの値がズレてしまいます。

注:

*1 これは <backslash><newline> による行接続をした後の行長にも、コメントを a space に変換した後の行長にも適用される。コメントが複数行にまたがっている場合、コメントの変換によってそれが1行に連結されることに注意。

*2 CygWIN 1.3.10, 1.5.18 では malloc() に _malloc_r() 等といった内部ルーチンがあり、他のライブラリ関数にはそれらを呼び出すものがいくつかある。そのため、他の malloc() を使うことができない。Visual C 2005, 2008 でも、プログラム終了処理のルーチンが Visual C の malloc() の内部ルーチンを呼び出すので、やはり他の malloc() を使うことができない。


5. 診断メッセージ

5.1. 診断メッセージの形式

mcpp が出す診断メッセージとその意味は、以下の通りです。診断メッセージはいずれも標準エラー出力に出力され、-Q オプションでカレントディレクトリ中の mcpp.err というファイルにリダイレクトされます。

診断メッセージは次の形をとっています。

  1. "filename:line: " に "fatal error: ", "error: ", "warning: " のどれかが続き、さらに 5.3 - 5.9 のメッセージのうちのどれかが続く。"filename:line: " に始まる1行の診断メッセージというのはいささか窮屈な仕様であるが、UNIX 上のC処理系での伝統的な診断形式で、各種のツールがこれを前提としているので採用している。画面上の1行におさまらないことがしばしばある。
  2. マクロ展開中であれば、そのマクロ呼び出しが表示される。ネストされたマクロ呼び出しであれば、それぞれのマクロ名が表示される。そのマクロ定義も表示され、そのマクロ定義のあるソースファイル名と行番号も表示される。
  3. ソースファイル名と行番号とその行が表示される。ファイルが include されたものであれば、include 元のファイルの名前と行番号と行が順次表示される。

表示される行は通常は、ソースの「物理行」が行末の \ によって接続されたあとの「論理行」からさらにコメントを a space に変換した後のものであり、コメントが行をまたいでいる場合は複数の論理行が連結されたものとなる。行番号は連結された最後の物理行の番号である。ただし、コメント処理等の前の translation phase でのエラー等では、その phase の行が表示される。

ただし、-j オプションを指定した時は、上記の2と3は出力しません。

診断メッセージには次の3つのレベルがあります。

fatal errorプリプロセスをこれ以上続けても意味がない場合
error 構文や使用法が間違っている場合
warning Portability のない場合や間違いの可能性のある場合

Warning にはさらに次の5つのクラスがあります。Class 1, 2 以外はやや特殊なものです。

class 1 間違いの可能性のある、または portability を欠いたソース
class 2 規格上は問題があるが実際にはたぶん問題のないソース
class 4 実際にはたぶん問題がない portability に関する warning
class 8 スキップされる #if group や、#if 式中の評価をスキップされる副式等についてのお節介な warning
class 16trigraph, digraph についての warning

mcpp はきわめて多種の診断メッセージを用意しています。STD モードでは次のような種類にのぼっています。

fatal error 17 種
error 76 種
warning class 1 49 種
warning class 2 15 種
warning class 4 17 種
warning class 8 30 種
warning class 16 2 種

これらについて、原則としてその行の中の問題の部分を具体的に指摘します。

なお、以下の説明では、診断メッセージで引用されるソース中のトークンや数値の部分には例として何かのトークンをはめこんでいます。そのうち、数値のかわりにマクロ名を書いているところは、実際にはそのマクロを展開した値が表示されます。
また、場合によってエラーとして出たりウォーニングとして出たりするメッセージもあります。以下の説明では同一のメッセージについては最初に記載するところでだけ解説を加え、あとは単にメッセージを並べるだけにします。


5.2. Translation limits

以下のエラーの中には、バッファのオーバーフロー等の mcpp の仕様上の制限によるものがあります。バッファサイズ等の translation limits は system.H のマクロで定義されています。必要な場合はその定義を大きくして mcpp をリコンパイルしてください(しかし、メモリの少ないシステムでは、あまり仕様を大きくすると out of memory が発生しやすくなるので、ほどほどに)。


5.3. Fatal error

I/O エラーやメモリ不足等、プリプロセスをそれ以上続けることができない場合、バッファオーバーフロー等、プリプロセスを続けても意味がない場合に、このエラーメッセージが出て、プリプロセスを中止します。親プロセスには「失敗」の状態値を返します。


5.3.1. mcpp 自身のバグ

5.3.2. 物理的エラー

5.3.3. Translation limits と内部バッファのエラー

次の4つのエラーはトークンがさほど長くなくても、マクロ展開中にそのトークンのところでバッファオーバーフローになった場合にも起こります。その場合はマクロ呼び出しを分割してください。

5.3.4. #pragma MCPP preprocessed に関するエラー

5.4. Error

文法的な間違いがある場合にこのエラーメッセージが出ます。
Standard C では、violation of syntax rule or constraint があった場合は処理系は何らかの診断メッセージを出さなければならないことになっていますが、Standard モードではこの violation に対しては原則としてエラーメッセージが出ます。一部はウォーニングです。

また、Standard C で undefined とされているものの多くについても error メッセージまたは warning が出ます。Undefined でありながら error も warning も出ないのは、次のものだけです。

  1. 文字列リテラルの形の header name 中の ' と /*。これは単なる文字として扱われる。実際にはこれは、ファイルをオープンしようとしてエラーになるはずである(<, > で囲まれた header name の中ではこれらは文字定数およびコメントの開始と解釈されるので、何らかのエラーとなる)。header name の中では \ も undefined であるが、これもチェックしない。実際にはやはりファイルのオープンでエラーになるであろう(Windows 版では \ は class 2 の warning を出した上で / に変換して処理する)。
  2. #undef defined。defined という名前を #undef するのは undefined であるが、mcpp では defined という名前のマクロを定義することはできないようになっているので、それが取り消されることもない。
  3. コメント中に illegal な multi-byte character sequence があった場合は undefined であるが、これは実害はないので診断しない(文字列リテラル、文字定数、header name 中の illegal multi-byte character sequence にはウォーニングが出る)。
  4. _ で始まる identifier は処理系のために予約されており、ユーザプログラムで定義すると結果は undefined であるが、プリプロセッサではユーザプログラムかそうでないかを必ずしも判別できないので、診断しない。
  5. C99 でオプションとして規定されている事前定義マクロのうち、__STDC_ISO_10646__, __STDC_IEC_559__, __STDC_IEC_559_COMPLEX__ は #define したり #undef したりすると undefined であるが、診断しない。これらのマクロは処理系のヘッダファイルで定義されることになるであろうが、プリプロセッサはユーザプログラムかどうかを必ずしも判別できないからである。
  6. C99 の UCN については、translation phase 2 で <backslash><newline> を削除した結果 UCN に相当する sequence ができた場合、および文字列リテラルの連結によって UCN sequence が生成された場合は undefined であるが、これは診断しない(いずれも UCN として扱う)。

Standard C のプリプロセスで具体的に何が violation of syntax rule or constraint で、何が undefined で何が unspecified で何が implementation-defined であるかについては、cpp-test.html を参照してください。

Fatal でない error メッセージが出てもプリプロセスは続けます。終了すると、エラーの数を表示し、親プロセスに「失敗」の状態値を返します。

5.4.1. 文字とトークンに関するエラー

次はトークンのエラーです。初めの4つはいずれもその行をスキップして処理を続けます。初めの3つは文字列リテラル等のトークンで、論理行の行末までに引用符が閉じられていないことを示します。

#error  I can't understand.

などと、preprocessing-token sequence の形を成さないテキストを文字列リテラルでもコメントでもないところに書くと、この種のエラーとなります。Pre-processing-token は本来の(コンパイラ本体での)Cの token よりおおまかなもので、文字が source character set に含まれてさえいればほとんどの character sequence が何らかの pp-token sequence として通るので、preprocessing-token エラーとなるのはこれだけです。

なお、スキップされる #if group の中でも pp-token エラーはエラーとなります。

5.4.2. 完結しないソースファイルのエラー

次のメッセージはソースファイルが完結しない #if section、マクロ呼び出し等で終わっている場合に出ます。そのファイルで入力が終わりの場合(include されたファイルでない場合)は、"End of file" ではなく "End of input" と表示されます。
これらの診断メッセージは mcpp のモードによって、エラーとなる場合とウォーニングとなる場合とあります。
Standard モードではこれらはすべてエラーです。そのマクロ呼び出しはスキップし、#if section の対応関係はそのファイルが include された時の初期状態に戻します。
pre-Standard モードではすべてウォーニングです。pre-Standard モードでも、OLDPREP モードでは unterminated macro call 以外はウォーニングさえも出ません。

5.4.3. Preprocessing group 等の対応関係のエラー

次は #if, #else 等の group の対応関係のエラーです。これらの行は無視して(それまでの group が続いているものとして)処理を続けます。これらのチェックはたとえスキップされる #if group の中にあっても行われます。
なお、#if (#ifdef) section とは #if, #ifdef, #ifndef から #endif まで、#if (#elif, #else) group とは1つの #if (#ifdef) section のうちの #if (#ifdef, #ifndef), #elif, #else, #endif 等ではさまれた1つの行ブロックを指します。

次の2つは #asm, #endasm の対応関係のエラーです。もちろん、pre-Standard モードの特定の処理系の場合だけです。

5.4.4. ディレクティブ行の単純な構文エラー

これ以降(5.4.12 まで)のエラーはスキップされる #if group の中では起こりません(-W8 オプションで起動すると、Unknown directive についてはウォーニングを出す)。
次は # で始まるディレクティブ行の単純な文法エラーです。これらの行は無視して処理を続けます(すなわち、#if を section の開始とみなさず、#line では行番号は変わらない等)。#include, #line 行の引数がマクロであれば、それを展開したうえでチェックが行われます(pre-Standard モードでは展開しない)。
下記のメッセージそのものにはディレクティブ名が出てきませんが、これに続いて表示されるソース行でディレクティブがわかります(ディレクティブ行はコメントが space に変換されると、必ず1行になる)。

次のエラーは Standard モードの場合だけで、これらのディレクティブは無視されます。OLDPREP モードではエラーもウォーニングも出ず、KR モードではウォーニングとなり、この "junk" がなかったものとしてプリプロセスを続けます。

5.4.5. #if 式の構文エラー等

次は #if, #elif, #assert ディレクティブ中の式の構文に関するエラーです。

#if (#elif) でエラーが起こった時は、その #if (#elif) 行は偽と評価されたものとして(すなわちその group をスキップして)、プリプロセスを続けます。

スキップされる #if (#ifdef, #ifndef, #elif, #else) group については、それがCの legal な preprocessing token で成り立っているかどうかと、#if 等の group の対応関係はチェックしますが、その他の文法エラーはエラーにはなりません。

#if 行そのものの中では、評価をスキップされる部分式があります。例えば #if a || b のような式で a が真である場合は、b の評価は行われません。しかし、次の14種の文法エラーないし translation limit のエラーはたとえ評価をスキップされる部分式中にあってもチェックされます。

次は #if sizeof に関するエラーです。もちろん、pre-Standard の場合だけです。

5.4.6. #if 式の評価に関するエラー

次のエラーは評価をスキップされる部分式にある場合は起こりません(-W8 オプションではこれらについてもウォーニングが出る)。

#if 式は C99 ではその処理系の持つ最大の整数型で、C90, C++98 では long / unsigned long で評価するのが規定ですが、mcpp では C90, C++98 でも long long / unsigned long long で評価します。ただし、C90, C++98 で long / unsigned long の範囲を超えた場合はウォーニングを出します。
long long のない処理系では、この節の long long / unsigned long long は long / unsigned long と読み替えてください。pre-Standard ではすべて (signed) long と読み替えてください。POSTSTD では #if 式に文字定数は使えないので、別のエラーになります。

次は sizeof に関するエラーです。スキップされる部分式では出ません(-W8 オプションではウォーニングが出る)。pre-Standard の場合です。

5.4.7. #define のエラー

次は #define に関するエラーです。 マクロは定義されません。

#, ## 演算子に関するエラーは Standard モードのものです。
__VA_ARGS__ に関するエラーも Standard モードの場合です。 可変引数マクロは C99 の仕様ですが、GCC, Visual C++ 2005, 2008 との互換性のために C90 でも C++ でも有効としています(ただし、ウォーニングが出る)。

GCC-specific-build の STD モードでは GCC2 仕様の可変引数マクロが使えますが、そのマクロ定義で __VA_ARGS__ が使われていると、次のエラーになります。 __VA_ARGS__ を使うなら GCC3 仕様か C99 仕様で書かなければなりません。

5.4.8. #undef のエラー

次は #undef に関するエラーです。

5.4.9. マクロ展開のエラー

次はマクロ展開に関するエラーです。それらのマクロ定義も表示され、そのマクロ定義のあるソースファイル名と行番号も表示されます。#, ## 演算子に関するエラーは Standard モードだけです。

以下のエラーでは、そのマクロ呼び出しはスキップされます。

次のエラーは STD モードで -K オプションを指定したときだけのものです。 ともに、マクロが極端に複雑なため、macro notification のためのバッファが足りなくなったことを意味しています。 実際にはまず起こり得ません。

5.4.10. #error, #assert

5.4.11. #include の失敗

5.4.12. その他のエラー

次の2つは Standard モードの C99 だけのものです。 C++ でも -V199901L オプションで起動した場合は同様です。


5.5. Warning (class 1)

文法的には間違いではないが何かの書き間違いの可能性がある場合や portability の問題のある場合に、warning が出ます。Warning には 1, 2, 4, 8, 16 の5つの class があります。mcpp の起動時に -W <n> というオプションを指定することで、これらが有効になります。<n> は 1, 2, 4, 8, 16 のうちの任意のものの OR をとったものです。なお、以下の説明で -W4 等と言っているのは、-W<n> で <n> & 4 が真の場合のことで、1|4, 1|2|4, 2|4, 1|4|8, 4|8, 4|16 等を含みます。

Standard モードでは Standard C で undefined とされている動作を引き起こすソースの多くは error にしますが、一部については warning を出します。

同様に Standard モードでは Standard C で unspecified とされている仕様を使うソースに対しては、次の点以外は必ず warning を出します。

  1. #if 式中の sub-expression の評価順序については、warning は出さない。||, &&, ? : 以外の演算子に関しては operand の評価順序は unspecified であるが、#if 式は副作用を生じないので、この評価順序は結果には影響しないからである。mcpp では、整数定数トークンの評価は常に出現と同時に左から右に行い、それらの間の演算は常に演算子のグルーピングの規則に従って、その項の値が必要になった時に初めて行う。

Standard モードでは、implementation-defined とされている動作の多くについても warning を出します。Implementation-defined でありながら warning の出ないのは、次の点だけです。

  1. #include directive で include するファイルを探す場所、および #include の引数から header-name という pp-token を構築する方法。これに毎度 warning を出していたのではうるさい。header-name はマクロでなければ、ソースのトークンが space の有無も含めてそのまま使われる。マクロであれば、それを展開した結果が space の有無も含めてそのまま使われる(POSTSTD モードでは、マクロ展開によって pp-token 間に space が挿入されるが、その上で < から > までを space を削除してくっつけたものを header-name と解釈する。どちらにしても POSTSTD では <, > による header-name は obsolescent feature である)。Warning は出さないが、その代わりに、#pragma MCPP debug path, #debug path でサーチパスを表示する。
  2. #if 式での single byte 文字定数('a' 等)の評価と、単一の multi- byte character のワイド文字定数(L'字' 等)の評価。これは基本文字セットが同一であっても、single byte のカタカナとか、符号の有無とか、漢字の en-coding とかによって portability はごく限られるのであるが、キリがない。UCN についても同様である。
  3. #if 式で負数がからむビット演算は整数の内部表現によって結果の値が異なるが、大半のマシンは2の補数の表現をとっているので、それを前提とすれば portability の問題はほとんど存在しない。ただし、負数の右ビットシフトおよび operand の片方または双方が負数の除算は portability が乏しいので、warning を出す。
  4. Token separator としての複数の white spaces の sequence。Standard C では translation phase 3 でこれを one space に圧縮するかどうかは implementation-defined とされているが、通常はユーザはまったく気にする必要はない。Portability が問題になるのは、preprocessing directive 行に <vertical-tab> または <form-feed> がある場合だけである。mcpp ではこれらは space に変換するが、その時は warning を出す。複数の space, tab の sequence は one space に黙って圧縮する。
  5. 処理系独自の組み込みマクロについては warning は出さない。
  6. #pragma sub-directive についても原則として warning は出さない。mcpp 自身が処理する #pragma once, #pragma __setlocale, #pragma MCPP * で引数が間違っている場合は、warning を出す。また、GCC V.3 での #pragma GCC poison (dependency) のように、処理系付属のプリプロセッサは処理するが mcpp は処理しない #pragma についても warning を出す。
  7. C99 では、UCN sequence が # 演算子によって文字列化される場合、\ を \\ というふうに重ねるかどうかは implementation-defined となっているが、これについては warning は出さない。mcpp では \ は重ねない。

したがって、mcpp ではプリプロセスのレベルでの portability のチェックをほぼ完全に行うことができます。

POSTSTD モードでは、 2.1 にある仕様の違いを除けば STD モードと同様です。

ウォーニングがいくつ出ても、「成功」の状態値を返します。-W0 のオプションで起動すると、ウォーニングは出ません。

5.5.1. 文字、トークンおよびコメントに関するウォーニング

5.5.2. 完結しないソースファイルのウォーニング

ソースファイルの最後の行が中途半端である場合に、次のウォーニングが出ます。old_prep モードではウォーニングも出ません。

次のウォーニングは pre-Standard モードでしか出ません(Standard モードではエラー)。入力の終わりでない場合は、これらのウォーニングを無視して処理を続けますが、その結果はさらに妙なエラーを引き起こすでしょう。OLDPREP モードでは unterminated macro call 以外はウォーニングさえも出ません。

5.5.3. ディレクティブ行に関する各種のウォーニング

次は Standard モードの場合だけです。

次は STD モードの場合だけです。

次は lang-asm モードの場合だけです。

以下の #pragma に関するウォーニングは Standard モードの場合だけです。ウォーニングは出てもその行は原則としてそのまま出力されますが、#pragma MCPP, #pragma GCC で始まる行のうち、プリプロセスで処理されるべきものは出力しません。 #pragma GCC visibility * のようにコンパイラやリンカのための行は、ウォーニングなしにそのまま出力されます。

GCC 版では次のウォーニングが出ます。

ただし、GCC 用では #pragma GCC に poison, dependency のどれかが続く行は class 2 のウォーニングを出したうえで捨てます。これは GCC V.3 ではプリプロセッサが処理するものですが、mcpp は処理しません。

次は pre-Standard モードで出ます(Standard モードではエラー)。

次は KR モードの場合と、Standard モードの #pragma once, #pragma MCPP push_macro, #pragma MCPP pop_macro, #pragma push_macro, #pragma pop_macro, #pragma __setlocale, #pragma setlocale, #pragma MCPP put_defines, #pragma MCPP debug, #pragma MCPP end_debug の場合、および GCC 用での STD モードの #endif 行の場合だけ出ます(Standard モードのその他の場合はエラー、OLDPREP ではエラーもウォーニングも出ない)。

5.5.4. #if 式に関するウォーニング

次の3つは #if, #elif, #assert の引数に関するウォーニングです。

次も #if, #elif, #assert の引数に関するウォーニングですが、評価をスキップされる部分式では出ません(-W8 オプションでは出る)。

次は #if (#elif, #assert) 行の定数式の演算と型に関するウォーニングです。やはりスキップされる部分式に関しては出ません(-W8 オプションでは出る)。

Standard モードでは #if 式は C90, C++98 でも long long / unsigned long long で評価します。ただし、C90, C++98 で long / unsigned long の範囲を超えた場合はウォーニングを出します。LL という suffix についても同様に、C99 以外ではウォーニングが出ます。これらのウォーニングは compiler-independent-build では class 1 で、compiler-specific-build では class 2 です。
POSTSTD モードでは文字定数は #if 式には使えないので、ウォーニングも出ません(エラーになる)。

5.5.5. マクロ展開に関するウォーニング

これらのウォーニングではそのマクロ定義が表示され、そのマクロ定義のあるソースファイル名と行番号も表示されます。

次の2つは OLDPREP モードだけです(他のモードではエラー)。

5.5.6. 行番号に関するウォーニング

次は行番号に関するウォーニングです。

C90 では #line で 32767 よりは小さいがそれに近い番号を指定した場合、その時点ではエラーにならないものの、いずれこの範囲をオーバーします。オーバーした場合、mcpp では warning を出した上で行番号をそのまま増やし続けていきますが、しかし、コンパイラ本体によってはこれを受け取れないかもしれません。#line の指定がむやみに大きいことが問題です。

5.5.7. #pragma MCPP warning (#warning)

5.6. Warning (class 2)

間違いではないが portability に問題のあるケースについてのウォーニングです。

#if 式は Standard モードでは C90, C++98 でも long long / unsigned long long で評価します。ただし、C90, C++98 で long / unsigned long の範囲を超えた場合はウォーニングを出します。LL という suffix についても同様に、C99 以外ではウォーニングが出ます。Visual C, Borland C の compiler-specific-build では I64 という suffix が使えますが、これについても同様です。これらのウォーニングは compiler-independent-build では class 1 で、compiler-specific-build では class 2 です。

次の5つは Standard モードの場合だけです。

次は POSTSTD モードの場合だけです。

次の2つのウォーニングは特定のシステムだけのものです。それらのシステムでは正しいプログラムですが、portability がないので、念のためにウォーニングを出します。


5.7. Warning (class 4)

Standard C ではいくつかの translation limits について、最低限保証すべき値を規定しています。プリプロセッサはこの値を超えた translation limits を持っているほうが仕様が良いとも言えますが、しかしそれに依存するソースは portability が制限されます。mcpp ではこれらの translation limits は system.H のマクロを定義することで任意に設定できるようになっていますが、Standard モードではこの値が Standard C の最小値を超えている場合は、そのことを利用するソースに対してはウォーニングを出します。しかし、処理系の標準ヘッダやソースによっては頻発する結果になるので、class 1, 2 から外してあります。

次のウォーニングはスキップされる #if group では出ません。

__STDC_VERSION__ >= 199901L の場合はこれらの translation limits は次の通りです。Identifier の長さでは、UCN と multi-byte-character はそれぞれ1文字と数えます(ソースのバイト数ではない。奇妙な規定である)。

ソースの論理行の長さ 4095 バイト
文字列リテラル、文字定数、header name の長さ4095 バイト
Identifier の長さ 63 文字
#include のネスト 15 レベル
#if, #ifdef, #ifndef のネスト 63 レベル
#if 式のカッコのネスト 63 レベル
マクロのパラメータの数 127 個
定義できるマクロの数 4095 個

-+ オプションで C++ のプリプロセスを指定した時は、次のような translation limits とします。ただし、マクロのパラメータの最大数は mcpp では 255 までしか実装できないので、256 個ではエラーとなります。

ソースの論理行の長さ 65536 バイト
文字列リテラル、文字定数、header name の長さ65536 バイト
Identifier の長さ 1024 文字
#include のネスト 256 レベル
#if, #ifdef, #ifndef のネスト 256 レベル
#if 式のカッコのネスト 256 レベル
マクロのパラメータの数 256 個
定義できるマクロの数 65536 個

次のウォーニングも実際にはうるさいので、class 1, 2 から外してあります。

次の2つは Standard モードの場合だけ出ます。

次は STD モードで -k または -K オプションを指定したときだけのものです。


5.8. Warning (class 8)

ソースの間違いである可能性は少ないが念のために注意を促す意味で、このメッセージが出ます。これをチェックするのは -W8 のオプションで起動された場合だけです。

スキップされる #if group の中の preprocessing directive は通常は #if, #ifdef, #ifndef, #elif, #else, #endif の対応関係しかチェックしませんが、-W8 では Illegal directive, Unknown directive のチェックもします。また、Standard モードでは #if のネストが8レベルを超えた場合もウォーニングを出します。

次は #if 式に関するウォーニングです。例えば #if a || b という式では、a が真であれば b の評価は行われません。しかし、-W8 として起動すると、評価されない部分式に関してもこれらのウォーニングが出されます。この場合はいずれも (in non-evaluated sub-expression) というただし書きが付けられます。


5.9. Warning (class 16)

Trigraph と digraph は使う必要のない環境ではまったく使わないものです。その環境でもしこれらが検出されれば、注意を要するでしょう。-W16 オプションはこれを検出するものです。他方で、trigraph あるいは digraph が常用されているソースではこのウォーニングが頻発することになってうるさいでしょうから、これを他のウォーニングとは別のクラスにしてあります。どちらにしても、これらは trigraph あるいは digraph が有効な状態でだけ検出されます。Digraph は Standard モードの場合で、trigraph は STD モードだけです。


5.10. 診断メッセージ索引

診断メッセージFatal
error
ErrorWarning class
124816
"..." isn't the last parameter5.4.7
"/*" in comment5.5.1
"and" is defined as macro5.5.3
"defined" shouldn't be defined5.4.7
"MACRO" has not been defined5.5.3
"MACRO" has not been pushed5.5.3
"MACRO" is already pushed5.5.3
"MACRO" wasn't defined5.8
"op" of negative number isn't portable5.5.45.8
"__STDC__" shouldn't be redefined5.4.7
"__STDC__" shouldn't be undefined5.4.8
"__VA_ARGS__" without corresponding "..."5.4.7
"__VA_ARGS__" cannot be used in GCC2-spec variadic macro5.4.7
## after ##5.4.7
#error5.4.10
#include_next is not allowed by Standard5.65.8
#warning5.5.7
'$' in identifier "THIS$AND$THAT"5.6
16 bits can't represent escape sequence L'\x12345'5.4.65.8
2 digraph(s) converted5.9
2 trigraph(s) converted5.9
8 bits can't represent escape sequence '\x123'5.4.65.8
_Pragma operator found in directive line5.4.12
Already seen #else at line 1235.4.3
Bad defined syntax5.4.5
Bad pop_macro syntax5.5.3
Bad push_macro syntax5.5.3
Buffer overflow expanding macro "macro" at "something"5.4.9
Buffer overflow scanning token "token"5.3.3
Bug:5.3.1
Can't open include file "file-name"5.4.11
Can't use a character constant 'a'5.4.5
Can't use a string literal "string"5.4.5
Can't use the character 0x245.4.5
Can't use the operator "++"5.4.5
Constant "123456789012" is out of range of (unsigned) long5.5.45.65.8
Constant "1234567890123456789012" is out of range5.4.65.8
Converted 0x0c to a space5.7
Converted [CR+LF] to [LF]5.5.15.6
Converted \ to /5.6
Division by zero5.4.65.8
Duplicate parameter names "a"5.4.7
Empty argument in macro call "MACRO( a, ,"5.6
Empty character constant ''5.4.15.5.1
Empty parameter5.4.7
End of file with no newline, supplemented the newline5.5.2
End of file with unterminated #asm block started at line 1235.4.25.5.2
End of file with unterminated comment, terminated the comment5.5.2
End of file with \, deleted the \5.5.2
End of file within #if (#ifdef) section started at line 1235.4.25.5.2
End of file within macro call started at line 1235.4.25.5.2
Excessive ")"5.4.5
Excessive token sequence "junk"5.4.45.5.3
File read error5.3.2
File write error5.3.2
Header-name enclosed by <, > is an obsolescent feature 5.6
GCC2-spec variadic macro is defined5.6
I64 suffix is used in other than C99 mode "123i64"5.65.8
Identifier longer than 31 bytes "very_very_long_name"5.7
Ignored #ident5.5.35.8
Ignored #sccs5.5.35.8
Illegal #directive "123"5.4.45.5.35.8
Illegal control character 0x1b in quotation5.5.1
Illegal control character 0x1b, skipped the character5.4.1
Illegal digit in octal number "089"5.5.1
Illegal multi-byte character sequence "XY" in quotation5.5.1
Illegal multi-byte character sequence "XY"5.4.1
Illegal parameter "123"5.4.7
Illegal shift count "-1"5.5.45.8
Illegal UCN sequence5.4.1
In #asm block started at line 1235.4.3
Integer character constant 'abcde' is out of range of unsigned long5.5.45.65.8
Integer character constant 'abcdefghi' is out of range5.4.65.8
Less than necessary N argument(s) in macro call "macro( a)"5.4.95.5.5
Line number "0x123" isn't a decimal digits sequence5.4.45.5.6
Line number "2147483648" is out of range of 1,21474836475.4.4
Line number "32768" got beyond range5.5.6
Line number "32768" is out of range of 1,327675.5.6
Line number "32769" is out of range5.5.6
LL suffix is used in other than C99 mode "123LL"5.5.45.65.8
Logical source line longer than 509 bytes5.7
Macro "MACRO" is expanded to "defined"5.5.4
Macro "MACRO" is expanded to "sizeof"5.5.4
Macro "MACRO" is expanded to 0 token5.5.4
Macro "macro" needs arguments5.8
Macro started at line 123 swallowed directive-like line5.5.5
Macro with mixing of ## and # operators isn't portable5.7
Macro with multiple ## operators isn't portable5.7
Misplaced ":", previous operator is "+"5.4.5
Misplaced constant "12"5.4.5
Missing ")"5.4.5
Missing "," or ")" in parameter list "(a,b"5.4.7
More than 1024 macros defined5.7
More than 31 parameters5.7
More than 32 nesting of parens in #if expression5.7
More than 8 nesting of #if (#ifdef) sections5.75.8
More than 8 nesting of #include5.7
More than BLK_NEST nesting of #if (#ifdef) sections5.3.3
More than INCLUDE_NEST nesting of #include5.3.3
More than necessary N argument(s) in macro call "macro( a, b, c)5.4.9
More than NEXP*2-1 constants stacked at "12"5.4.5
More than NEXP*3-1 operators and parens stacked at "+"5.4.5
More than NMACPARS parameters5.4.7
Multi-character or multi-byte character constant '字' isn't portable5.75.8
Multi-character wide character constant L'ab' isn't portable5.75.8
Negative value "-1" is converted to positive "18446744073709551615"5.5.45.8
No argument5.4.45.5.3
No header name5.4.4
No identifier5.4.4
No line number5.4.4
No space between macro name "MACRO" and repl-text5.5.3
No sub-directive5.5.3
No token after ##5.4.7
No token before ##5.4.7
Not a file name "name"5.4.4
Not a formal parameter "id"5.4.7
Not a header name "UNDEFINED_MACRO"5.4.4
Not a line number "name"5.4.4
Not a valid preprocessing token "+12"5.4.95.6
Not a valid string literal5.4.9
Not an identifier "123"5.4.45.5.3
Not an integer "1.23"5.4.5
Not in a #if (#ifdef) section5.4.3
Not in a #if (#ifdef) section in a source file5.4.35.5.3
Old style predefined macro "linux" is used5.5.5
Operand of _Pragma() is not a string literal5.4.12
Operator ">" in incorrect context5.4.5
Out of memory (required size is 0x1234 bytes)5.3.2
Parsed "//" as comment5.6
Preprocessing assertion failed5.4.10
Quotation longer than 509 bytes "very_very_long_string"5.7
Recursive macro definition of "macro" to "macro"5.4.9
Removed ',' preceding the absent variable argument5.5.5
Replacement text "sub(" of macro "head" involved subsequent text5.5.55.8
Rescanning macro "macro" more than RESCAN_LIMIT times at "something"5.4.9
Result of "op" is out of range5.4.65.8
Result of "op" is out of range of (unsigned) long5.5.45.65.8
Shift count "40" is larger than bit count of long5.5.45.65.8
sizeof is disallowed in C Standard5.8
sizeof: Illegal type combination with "type"5.4.65.8
sizeof: No type specified5.4.5
sizeof: Syntax error5.4.5
sizeof: Unknown type "type"5.4.65.8
Skipped the #pragma line5.6
String literal longer than 509 bytes "very_very_long_string"5.7
The macro is redefined5.5.4
This is not a preprocessed source5.3.4
This preprocessed file is corrupted5.3.4
Too long comment, discarded up to here5.7
Too long header name "long-file-name"5.3.3
Too long identifier, truncated to "very_long_identifier"5.5.1
Too long line spliced by comments5.3.3
Too long logical line5.3.3
Too long number token "12345678901234"5.3.3
Too long pp-number token "1234toolong"5.3.3
Too long quotation "long-string"5.3.3
Too long source line5.3.3
Too long token5.3.3
Too many magics nested in macro argument5.4.9
Too many nested macros in tracing MACRO5.4.9
UCN cannot specify the value "0000007f"5.4.15.8
Undefined escape sequence '\x'5.5.45.8
Undefined symbol "name", evaluated to 05.75.8
Unknown #directive "pseudo-directive"5.4.45.5.45.8
Unknown argument "name"5.5.3
Unterminated character constant 't understand.5.4.1
Unterminated expression5.4.5
Unterminated header name 5.4.1
Unterminated macro call "macro( a, (b,c)"5.4.9
Unterminated string literal5.4.1
Unterminated string literal, catenated to the next line5.5.1
Variable argument macro is defined5.6
Wide character constant L'abc' is out of range of unsigned long5.5.45.65.8
Wide character constant L'abc' is out of range5.4.65.8


6. バグ報告等

プリプロセスの Standard C 適合度を検証するための Validation Suite を mcpp のソースとともに公開しています。Standard C のプリプロセスのすべての規定を検証できるものにしたつもりです。もちろん、mcpp はこれを使ってチェックしてあります。それも多くの処理系でコンパイルしてチェックしてあります。したがって、バグや誤仕様はほとんどないつもりですが、しかし、まだいくつか残っている恐れは十分あります。

もし、不可解な動作が発見されたら、ぜひご報告ください。
もし、 "Bug: ..." という診断メッセージが出たら、それは間違いなく mcpp または処理系の(たぶん mcpp の)バグです。また、たとえむちゃくちゃな「ソース」でも、それを食わせることで mcpp が暴走するなら、それもバグです。

バグ報告には次のようなデータを付けてくださるようお願いします。

  1. mcpp を移植した処理系。
  2. バグと思われるものを再現できるなるべく短いサンプルソース。
  3. その処理結果。

バグ報告のほかにも、mcpp の使い勝手、診断メッセージ、このドキュメントの書き方、などについてご意見をお寄せください。
ご意見と情報は

http://mcpp.sourceforge.net/

の "Open Discussion Forum" またはメールでお願いします。