\documentclass[twocolumn]{jsarticle}
\usepackage{newcent}
\usepackage{type1cm}
\usepackage{cite}
\usepackage[dvips]{graphicx}
\begin{document}

\title{高品質のＣプリプロセッサ MCPP}
\author{松井　潔\\
{\normalsize kmatsui@t3.rim.or.jp}}
\date{2005 年 3 月 19 日}
\maketitle

\abstract{
長い歴史を持つＣのプリプロセス仕様には多くの混乱があった。
C90 以降は各処理系の仕様が規格を中心に収束してきているが、
「規格準拠」をうたう処理系が間違った動作をすることがいまだにみられる。
また、既存のプリプロセッサはどれも寡黙すぎて、
ソースチェックの能力が不十分である。
MCPP は free でかつ portable なＣプリプロセッサであり、
C/C++ プリプロセッサの徹底的なテストと評価をする検証セットが付属している。
これを適用すると MCPP は抜群の成績を示す。
MCPP は規格の仕様をほぼ完全に実装しているとともに、
豊富で的確な診断メッセージを持ち、
ソースのプリプロセス上の問題をほぼすべてチェックすることができる。
}

\section{はじめに}

  長い歴史を持つＣのプリプロセス仕様には多くの混乱があった。
C90 (C89)~\cite{C90a,C90b,C90c,C90d} 以降は各処理系の仕様が
規格を中心に収束してきているが、
「規格準拠」をうたう処理系が間違った動作をすることがいまだに稀ならず見られる。
コンパイラ本体と比べると、
プリプロセッサはかなり未成熟な分野だと言わざるをえない。

  この状況の背景となっているのは、C90 以前のＣプリプロセスの仕様が
はなはだあいまいなものだったことである。
C90 でＣのプリプロセス仕様は、
その原則にまでさかのぼって初めて全面的に定義されたのであった。
しかし、C90 にも歴史的な負の遺産を清算しきれなかった部分があり、
それらの問題は C99~\cite{C99a,C99b,C99c} でも解決されなかったのである。
さらに現実の多くの処理系は原則をあいまいにしたまま
規格にある個々の仕様を接ぎ木することによって、
問題を長く引きずってきていると考えられる。

  こうした事情を背景として、Ｃで書かれたソースプログラムには、
portability を欠いた不必要に処理系依存のコード等、
プリプロセスレベルの問題を持つものが少なくない。
Ｃにプリプロセスというフェーズがあるのは 
portability の確保が大きな目的の１つであるが、
プリプロセスが逆に portability を損なう結果も生んできてしまっているのである。

  私は久しく以前からＣプリプロセッサを開発してきた。
その成果はすでに 1998/08 に cpp V.2.0 として、
1998/11 に cpp V.2.2 として公開していたが、
V.2.3 への update の途中で
情報処理振興事業協会（現・情報処理推進機構） (IPA) の
平成１４年度と平成１５年度の「未踏ソフトウェア創造事業」に採択され、
そのプロジェクトの成果として V.2.3, V.2.4 がリリースされた~\cite{ipa}。
そして今回、V.2.5 をリリースすることになった。
この cpp を他の cpp と区別するために MCPP と呼ぶ。
Matsui CPP の意味である。

  MCPP はおそらく世界一優れたＣプリプロセッサである。
私が勝手にそう言っているだけではなく、
「検証セット」を並行して作成して、
それによって検証ずみであるところが特徴である。

  また、MCPP は診断メッセージが豊富である。
これを使うことでソースのプリプロセス上の問題点をほぼすべて
チェックすることができ、
ソースの portability の向上に役立てることができる。

  本稿では、まず MCPP の概要を述べ、
Ｃプリプロセスの基礎的規定に手短に触れたあとで、
検証セットを紹介し、他のプリプロセッサとの比較データを示す。
そして、処理系のバグの例を示す。
次いで、MCPP による現実のソースプログラムのチェックを取り上げ、
さらに、Ｃプリプロセスの原則とその実装方法について論ずる。
最後に今後の update の計画に触れる。

\section{MCPP の概要}

  MCPP は次のような特徴を持っている。

\begin{enumerate}
\item 規格準拠性がきわめて高い。
C, C++ のプリプロセスの reference model となるものを目指して作ってある。
C90 はもちろんのこと、C99, C++98~\cite{C++98} に対応する
実行時オプションも持っている。
\item C, C++ プリプロセッサそのものの詳細かつ網羅的なテストと評価をする
検証セットが付属している。
\item 診断メッセージが豊富で親切である。診断メッセージは百数十種に及び、
問題点を具体的に指摘する。それらは数種のクラスに分けられており、
実行時オプションでコントロールすることができる。
\item デバッグ用の情報を出力する各種の \#pragma ディレクティブを持っている。
Tokenization をトレースしたり、 マクロ展開をトレースしたり、
マクロ定義の一覧を出力したりすることができる。
\item Multi-byte character の処理は日本の EUC-JP, shift-JIS, ISO-2022-JP、
中国の GB-2312、台湾の Big-5、韓国の KSC-5601 (KSX 1001)、
および UTF-8 の各 encoding に対応している。
Shift-JIS や Big-5 の場合、
コンパイラ本体が漢字を認識しない処理系では、MCPP がそれを補う。
\item 速度も遅いほうではないので、デバッグ時だけでなく日常的に
使うことができる。
16 ビットシステムでも使えるように作られているので、
メモリが少なくても動作する。
\item Portable なソースである。
MCPP をコンパイルする時に、ヘッダファイルにある設定を書き換えることで、
UNIX 系、DOS/Windows 系のいくつかの処理系で、
付属のプリプロセッサに代替して使えるプリプロセッサが
生成されるようになっている。
C90, C99, C++98 のどれに準拠する処理系でもコンパイルでき、
C90 以前のいわゆる $K\&R^{1st}$ の処理系でさえもコンパイルできる広い
portability を持っている。
\item 標準モード（C90, C99, C++98 対応）のプリプロセッサのほか、
$K\&R^{1st}$ の仕様のプリプロセッサを生成することもできる。
標準モードの実行プログラムには、規格のバージョンを指定するオプションがあり、
規格の問題点を私が整理した自称 post-Standard モードのオプションまである。
$K\&R^{1st}$ モードの実行プログラムには、
いわゆる Reiser cpp モデルのオプションもある。
\item UNIX 系のシステムでは configure スクリプトによって MCPP の実行プログラムを
自動生成することができる。
GNU C の testsuite がインストールされていれば、`make check' コマンドによって
検証セットのうちの動作テストの testcase の大半を自動実行することもできる。
\item オープンソースである。
BSD 形式の LICENSE のもとに
ソース・ドキュメント・検証セットのすべてが公開されている。
\item 詳細なドキュメントが付属している。
次の文書についてそれぞれ日本語版と英語版が用意されている。
英語版は未踏ソフトウェアのプロジェクトで、
翻訳会社のハイウェル（東京）~\cite{hw} に日本語版からの翻訳を委託し、
それに筆者が修正を加えて作成したものである。
    \begin{enumerate}
    \item INSTALL: configure と make の方法を説明。
    \item mcpp-summary.pdf: サマリ文書。この文書。
    \item mcpp-manual.txt: 実行プログラム用マニュアル。
    使い方、仕様、診断メッセージの意味。ソースの書き方も示唆。
    \item mcpp-porting.txt: 実装用ドキュメント。任意の処理系に実装する方法。
    \item cpp-test.txt: 検証セット解説。
    規格の解説を兼ねる。規格そのものの矛盾点も指摘し、
    代案を提示している。
    検証セットをいくつかの処理系に適用した結果を報告している。
    \end{enumerate}
\end{enumerate}

\section{プリプロセスの基礎的規定}

  本題に入る前に、C/C++ プリプロセスの基礎的な規定に少し触れておきたい。

\vspace{8pt}
\subsection{プリプロセスの手順}

  プリプロセスの手順は $K\&R^{1st}$ ではまったく記載されていなかったために、
多くの混乱の元となっていた。
C90 では次のような translation phases というものが規定されて、
これが明確にされた。

\begin{enumerate}
\item ソースファイルの文字を必要ならソース文字セットに map する。
Trigraph の置換をする。
\item \verb|<backslash><newline>| の sequence を削除する。
それによって物理行を接続して論理行にする。
\item Preprocessing token と white spaces とに分割する。
コメントは one space character に置換する。
改行コードは保持する。
\item 各種の preprocessing directive を実行し、マクロ呼び出しを展開する。
\#include directive があれば、指定されたファイルについて phase 1 から phase 4 
を再帰的に処理する。
\item ソース文字セットから実行時文字セットへの変換をする。
同様に、文字定数中と文字列リテラル中の escape sequence を変換する。
\item 隣接する文字列リテラルを連結する。
\item Preprocessing token を token に変換し、コンパイルする。
\item リンクする。
\end{enumerate}

  その後、C99 では phase 4 に \verb|_Pragma()| operator の処理が付け加えられた。
そのほか若干の字句が追加されたり修正されたりしたが、大筋は変わっていない。

  C++98 では、phase 7 と 8 の間に 
instantiation 処理の phase が挿入されたほか、
phase 1 で basic source character set に含まれない文字
は universal character name (UCN) というものに変換し、
phase 5 でこれを実行時文字セットに再変換するという規定が追加された。

  これらの translation phases のうち、
通常は phase 4 までをプリプロセスと呼んでいる。

\vspace{8pt}
\subsection{診断メッセージとドキュメント}

  診断メッセージとドキュメントに関する規定は、
用語の違いを除けば C90, C99, C++98 で実質的にすべて同じであり、
次のように規定されている。

  Violation of syntax rule（構文規則違反）
または violation of constraint（制約違反）を含む translation unit に対しては、
処理系は診断メッセージを出さなければならない。
診断メッセージの出し方は implementation-defined（処理系定義）である。

  処理系定義の事項についてはすべて、
処理系はその仕様をドキュメントに記載しなければならない。

\section{プリプロセス検証セットによる各種プリプロセッサの検証}

\begin{table}
\begin{center}
    \caption{検証セット V.1.5 の項目数と配点}
    \label{set}
    \begin{tabular}{l|l|r|r}                         \hline\hline
        &                   & 項目数    & 最高点  \\ \hline
    規  & K\&R              &    31     &   166   \\
    格  & C90               &   140     &   432   \\
    準  & C99               &    20     &    98   \\
    拠  & C++98             &     9     &    26   \\
    度  &                   &           &         \\ \hline
    品  & 診断メッセージ    &    47     &    74   \\
    質  & その他            &    18     &   164   \\ \hline
    \multicolumn{2}{c|}{計} &   265     &   960   \\ \hline
    \end{tabular}
\end{center}
\end{table}

  プリプロセッサの開発でもう一つ問題となるのは、
プリプロセッサの動作や品質の検証である。
処理系が誤動作したり品質が悪かったりするのでは論外であるが、
実際にテストしてみると、かなりの問
%\newpage
%\vspace*{4pt}
%\noindent
題が見つかるものである。
処理系の開発には検証システムの整備が不可欠である。

  私は MCPP 開発の一環として、プリプロセス検証セットを作成し、
MCPP とともに公開している。
これはきわめて多面的な評価項目を持ち、
プリプロセッサのできるだけ客観的で網羅的なテストをするものである。

  検証セット V.1.5 は表\ref{set} のようにテスト項目が 265 に及んでいる。
うち動作テストが 230 項目、ドキュメントや品質の評価が 35 項目を占めている。
各項目はウェイトを付けて配点されている。
各項目とも最低点はすべて 0 点である。
「規格準拠度」には診断メッセージとドキュメントの評価も含まれる。
「規格準拠度」のうち「K\&R」というのは、$K\&R^{1st}$ と C90 との共通の仕様に関するものであり、
「C90」というのは C90 で新たに規定された仕様に関する項目である。
C99, C++98 の「規格準拠度」というのは、
C90 にない新たな規定に関するものである。
「規格準拠度」のテスト項目は規格の全仕様を網羅している。

  また、「品質:診断メッセージ」というのは、規格
%\newpage
%\vspace*{4pt}
%\noindent
で要求されていない診断メッセージに関する評価である。
「品質:その他」というのは、実行時オプション・\#pragma・multi-byte character 
への対応・
速度等々の各種品質の評価である。

  検証セット V.1.5 を次のようないくつかの処理系に適用してみた。
その結果を表\ref{result} および図\ref{graph} に示す。
処理系は古い順に並べてある。

\vspace{8pt}

  *1 DECUS cpp: Martin Minow による DECUS cpp のオリジナル版~\cite{decus} を
筆者が若干の修正を加えて Linux / GNU C 3.2 でコンパイルしたもの。

  *2 Borland C 4.0: 1993 年のものの日本語版。
MS-DOS 版をテスト。~\cite{bc4}

  *3 MCPP 2.0: 筆者による free software の V.2.0。
DECUS cpp をベースとして書き直したもの。
FreeBSD / GNU C 2.7, DJGPP V.1.12, WIN32 / Borland C 4.0, 
MS-DOS / Turbo C 2.0,  LSI C-86 3.3 等に対応していた。
各種の動作モードのプリプロセッサを生成することができるが、
このテストでは 32 ビットシステムでの標準版
（Linux / GNU C 2.95.3 でコンパイルしたもの）を使用。

%\newpage
%\vspace*{4pt}
%\noindent
  *4 Borland C 5.5: 日本語版。~\cite{bc55}

  *5 GNU C 2.95.3: VineLinux 2.6 および 3.1, FreeBSD 4.7, CygWIN 1.3.10 等に
バンドルされているもの。

  *6 GNU C 3.2: ソースから
筆者が VineLinux 2.6, FreeBSD 4.7 上でコンパイルしたもの。~\cite{gcc}

  *7 ucpp 1.3: Thomas Pornin による portable な free software。
Stand-alone のプリプロセッサ。~\cite{ucpp}

  *8 Visual C 2003: Visual C++ .net 2003。Microsoft。~\cite{vc2003}

  *9 LCC-Win32 3.2: Jacob Navia による shareware。ソース付き。
プリプロセス部分のソースは Dennis Ritchie が C90 対応の
プリプロセッサとして書いたもの。~\cite{lcc-win32}

  *10 Wave 1.0.0: Hartmut Kaiser による free software。
Paul Mensonides らによる Boost C++ preprocessor library というものを
使って実装されている。WIN32 版の実行プログラムでテスト。~\cite{wave}

  *11 GNU C 3.4.3: VineLinux 3.1 上で筆者がコンパイルしたもの。~\cite{gcc}

  *12 MCPP 2.5: V.2.0 以降、
Linux / GNU C (2.95, 3.2, 3.3, 3.4), FreeBSD / GNU C (2.95, 3.2, 3.4), 
CygWIN 1.3.10, LCC-Win32 3.2, Borland C 5.5, Visual C++ .net 2003, 
Plan 9 ed.4 / pcc 等
への対応が追加されている。~\cite{mcpp}

\vspace{8pt}

  MCPP はずば抜けた成績である。
V.2.5 では、
規格準拠性は C++98 での multi-byte character 等の UCN への変換という奇怪な仕様を
実装していない以外は完全であり、
診断メッセージの豊富さと的確さ、ドキュメントの網羅性、
豊富な実行時オプションと \#pragma、
多様な multi-byte character encoding への対応、
portability 等々の規格外の品質では他の処理系との差がさらに大きい。

  MCPP の次に優れているのは、このリストでは GNU C / cpp である。
この cpp は C90 規格に準拠した正しいソースを処理する分には、
ほとんど問題がない。
しかし、C99, C++98 の新しい仕様のいくつかが未実装であることは時間ととも
に解決されてゆくとしても、それ以外にも次のような問題がある。

\begin{table}
\twocolumn[
\begin{center}
\caption{各種プリプロセッサの検証結果}
\label{result}
%\small
\begin{tabular}{rlc|rrrr|r|rr|r}
\hline \hline
No. & 処理系        & 年/月     & \multicolumn{5}{c}{規格準拠度}    &  \multicolumn{2}{|c|}{品質}& 総合  \\
                                \cline{4-10}
    &               &           & K\&R  & C90   & C99   & C++   & 計    & 診断  & その  & 評価  \\
    &               &           &       &       &       & 98    &       & msg   & 他    &       \\
\hline
1   & DECUS cpp     & 1985/01   & 150   & 240   &   0   &   0   & 390   &  15   &  78   & 483   \\
2   & Borland C 4.0 & 1994/12   & 164   & 366   &  14   &   6   & 552   &  14   &  69   & 633   \\
3   & MCPP 2.0      & 1998/08   & 166   & 430   &  58   &  10   & 664   &  68   & 125   & 857   \\
4   & Borland C 5.5 & 2000/08   & 164   & 368   &  20   &   6   & 558   &  18   &  72   & 646   \\
5   & GNU C 2.95.3  & 2001/03   & 166   & 404   &  56   &   6   & 632   &  23   & 113   & 768   \\
6   & GNU C 3.2     & 2002/08   & 166   & 419   &  86   &  20   & 691   &  33   & 117   & 841   \\
7   & ucpp 1.3      & 2003/01   & 166   & 384   &  88   &   9   & 647   &  25   &  88   & 760   \\
8   & Visual C 2003 & 2003/04   & 156   & 394   &  43   &  15   & 610   &  20   &  83   & 711   \\
9   & LCC-Win32 3.2 & 2003/08   & 160   & 376   &  18   &   6   & 560   &  18   &  96   & 674   \\
10  & Wave 1.0.0    & 2004/01   & 140   & 338   &  53   &  18   & 549   &  20   &  79   & 648   \\
11  & GNU C 3.4.3   & 2004/11   & 166   & 415   &  87   &  20   & 688   &  39   & 120   & 847   \\
12  & MCPP 2.5      & 2005/03   & 166   & 432   &  98   &  22   & 718   &  74   & 134   & 926   \\
\hline
    & 最高点        &           & 166   & 432   &  98   &  26   & 722   &  74   & 164   & 960   \\
\hline
\end{tabular}
\end{center}]
\end{table}

\begin{enumerate}
\item 診断メッセージが不十分である。
-pedantic \mbox{-Wall} オプションを指定することで多くの問題はチェックできるが、
それでもまだかなり不足している。
ことに規格で要求されていない事項に関する診断メッセージはごく少ない。
\item デバッグ情報を出力する機能はほとんどない。
\item ドキュメントが少なく、仕様の不明確な部分
\newpage
\vspace*{4pt}
\noindent
や隠れ仕様が多い。
ことに V.2 では \mbox{-traditional} オプションを指定しなくても 
traditional な仕様が隠れていて、
それを期待するソースが黙って通ってしまうことが問題である。
\item 規格と矛盾する独自仕様が多い
（拡張仕様は \#pragma で実装すべきものである）。
\end{enumerate}

\begin{figure*}
\begin{center}
\includegraphics{graph.epsi}
\end{center}
\caption{各種プリプロセッサの検証結果}
\label{graph}
\end{figure*}

  GNU C V.3 / cpplib は GNU C V.2 / cpp に
比べるとこれらの点で大幅に改善されたが、まだ不十分である。

  MCPP が GNU C / cpp に劣るのは速度くらいである。
  
  他のプリプロセッサにはさらに問題が多い。
多くのプリプロセッサに共通して見られる傾向としては、次のようなものがある。

\begin{enumerate}
\item C99, C++98 の仕様はまだ半分しか実装していないものが多い。
\item 診断メッセージの不足している処理系が多い。
\item 規格外事項に関する診断メッセージはほとんど出さない処理系が大半である。
\item 診断メッセージの質の悪いものも目立つ。
\item ドキュメントの不十分な処理系が大半である。
\item Multi-byte character は１、２種の encoding にしか対応していない
処理系が多い。
\end{enumerate}

  そのほか、処理系ごとに意外なバグが１つや２つはたいてい発見される。

\section{プリプロセッサのバグと誤仕様の例}

  検証セットまたは MCPP によって検出された、
各種プリプロセッサのバグないし誤仕様のほんの一部の例を次に示す。

\vspace{8pt}
\subsection{コメントを生成するマクロ}

  図\ref{bugs} の example-1 は Visual C++ .net のシステムヘッダに実際に出てくる
マクロ定義であり、example-2 のように使われている。
これは \verb|_VARIANT_BOOL| が // に展開されて、
その結果、この行がコメントアウトされることを期待しているものである。
そして、実際に Visual C の cl.exe ではそういう結果になる。

  しかし、// はトークン (preprocessing-token) ではない。
また、マクロの定義や展開は、
ソースがトークンに分解されコメントが１個のスペースに変換されたあとの
フェーズで処理されるものであり、したがって
コメントを生成するマクロなどというものはありえないのである。
このマクロは // に展開されたところで、
// は有効な preprocessing-token ではないので
結果は undefined となるはずのものである。

  このマクロは論外であるが、それ以上に問題なのは、
これをコメントとして処理してしまう Visual C / cl.exe の
プリプロセスの実装である。
この例には、このプリプロセッサの次のような深刻な問題が露呈している。

\begin{figure}
\includegraphics{example1.epsi}
\caption{プリプロセッサのバグの例}
\label{bugs}
\end{figure}

\begin{enumerate}
\item トークンベースではなく文字ベースの処理がされている。
\item プリプロセスの手順 (translation phases) が恣意的であり、
論理的な一貫性がない。
\end{enumerate}

\vspace{8pt}
\subsection{スキップされるはずの式でエラーになるもの}

  example-3 と example-4 はいずれも正しい式である。
分母が 0 でない場合だけ割り算を実行するように用心深く書かれた式である。
ところが、\verb|MACRO_0| が 0 であるにもかかわらず割り算を実行して
エラーになる処理系がある。
example-3 はかつてはエラーになる処理系はよくあったが、
いまは見掛けなくなっている。
しかし、example-4 は Visual C++ ではエラーになってしまう。
式の評価に関するＣの基本的な仕様が正しく実装されていないのである。

  一方、Borland C 4.0, 5.5 では example-3 でも example-4 でも
ウォーニングが出る。
これは必ずしも間違っているとは言えないが、
しかし、この処理系は example-5 のような正真正銘の 0 除算でも
まったく同じウォーニングを出す。
すなわち、正しいソースと間違ったソースとの区別がつかない。
Turbo C では本当の 0 除算でも正しい式でもどちらも同じエラーになっていたが、
Borland C はその診断メッセージをウォーニングに格下げしただけなのである。
規格に違反しているとは言えないが、
間に合わせ的な診断メッセージであり品質は良くない。

\vspace{8pt}
\subsection{オーバーフローが診断されないもの}

  example-6 は C90 ではオーバーフローの発生する定数式であるが、
これには何の診断メッセージも出さない処理系が大半である。
Borland C と ucpp だけがこの種のソースに対しては場合によってウォーニングを出すが、
出さない場合も多く、一貫していない。

\section{プリプロセッサによるソースチェックはなぜ必要か}

  次に、MCPP による現実のソースコードのチェックを、
glibc 等を例に見ることにする。

  Ｃで書かれたソースプログラムには、
プリプロセスのレベルでの問題を持っているものが少なくない。
特定の処理系でコンパイルできることをもって良しとしてしまっているものの 
portability を欠いているもの、
不必要にトリッキーな書き方をしているもの、
C90 以前の特定の処理系の仕様をいまだにあてにしているもの、等々である。
こうしたソースの書き方は portability と readability  そして
メンテナンス性を損なうものであり、悪くすればバグの温床ともなりかねない。
そうしたソースをより portable で明快な形で書き直すことは、多くの場合、
簡単なことなのであるが、見過ごされている場合も多い。

  そうしたソースが多く存在する背景となっているのは、
一つには C90 以前のプリプロセス仕様がはなはだあいまいだったことである。
これが、C99 が決まった今となっても尾を引いている。
もう一つは、既存のプリプロセッサが寡黙すぎることである。
プリプロセッサが怪しげなソースを黙って通すために、
問題が見過ごされてしまうのである。

\vspace{8pt}
\subsection{プリプロセッサによるソースチェックの影響力}

  MCPP を処理系付属のプリプロセッサと置き換えて使うことで、
ソースプログラムのプリプロセス上の問題点を、
潜在的なバグや規格違反から  portability の問題まで、
ほぼすべて洗い出すことができる。

  これを FreeBSD 2.2.2R (1997/05) の kernel 
および libc ソースに適用した結果は、
MCPP V.2.0 以来、そのマニュアル文書 mcpp-manual.txt の 3.9 節で報告している。
Libc にはまったくと言ってよいほど問題がなかったが、
kernel には全体からみればごく一部のソースではあるものの、
いくつかの問題が発見された。
問題のソースの多くは、4.4BSD-lite にあったものではなく、
FreeBSD への実装と拡張の過程で新しく書かれたものであった。

  その後、当時開発中であった MCPP V.2.3 を 
Linux の glibc 2.1.3 (2000/09) に適用してみたところ、
こちらにも少なからぬ問題点のあることがわかった。
これらの問題には、UNIX 系システムに古くから存在する
いわゆる traditional なプリプロセス仕様を使ったものと、
GNU C / cpp の独自の仕様や undocumented な仕様を前提としたものが多い。
GNU C / cpp がこれらを、
少なくともデフォルトの設定では黙って通してしまうことが、
こうした感心しない書法のソースを温存させ、
そればかりか新たに生み出す結果になっていると考えられる。
こうした書法は古いソースに多いとは限らず、
むしろ新しいソースに時々見られることが問題である。
システムのヘッダファイルにさえも時に問題がある。

  他方で、コメントのネストは規格違反であるが、
1990 年代中ごろまでは UNIX 系のオープンソースにしばしば見られたものの、
その後は見かけなくなっている。
これは GNU C / cpp がコメントのネストを認めなくなったためであろう。
プリプロセッサはソースに大きな影響を与えるのである。

\vspace{8pt}
\subsection{glibc のソースを例に}

  VineLinux 2.1 (i386) で使われている glibc 2.1.3 のソースを例にとって、
プリプロセス上の問題点の一端を見てみたい。

\vspace{8pt}
\subsubsection{行をまたぐ文字列リテラル}

  図\ref{glibc} の example-7 のようなものである。
この traditional な仕様を使う必要はないはずであるが、いまだに使われている。
Makefile によって生成されるものもある。

  この例はプリプロセスディレクティブ行なので行接続が必要であり、
それを使って example-8 のように書くことができる。

  ディレクティブ行に限らない一般性のある書き方は、
example-9 のように文字列リテラルの連結の機能を使うものである。
ディレクティブ行でなければ、行接続はもちろん不要である。

  他のソースファイルではこの形になっているものが多いのであるが、
なぜか古い書き方も残っている。

\begin{figure*}
\begin{center}
\includegraphics{example2.epsi}
\end{center}
\caption{glibc のソースの例}
\label{glibc}
\end{figure*}

\vspace{8pt}
\subsubsection{プリプロセスを要する $*$.S ファイル}
  アセンブラソースの中に \verb|#if| 等のプリプロセスディレクティブや
Ｃのコメントが挟まっているものである。
アセンブラはＣとは構文が異なるので、
これをＣプリプロセッサで処理するのは危険がある。
さらに \verb|#APP, #NO_APP| といった
アセンブラ用の行まで混じっているものもあるが、
これは無効なプリプロセスディレクティブと構文上、区別がつかない。

  example-9 のように、できるだけ asm() 関数を使って、
アセンブラソースの部分を文字列リテラルに埋め込み、
$*$.S ではなく $*$.c ファイルとするのが良いと思われる。
この形であれば、文字列リテラルの行が並んでいる途中にディレクティブ行を
（\#include 以外なら）挟んでも問題ない。

  アセンブラソースの中にマクロが埋め込まれているものもあるが、
これは asm() では対処できない。
この種のソースはＣのソースではなく、
本来はアセンブラ用のマクロプロセッサを使うべきものであろう。
Ｃプリプロセッサはそのために流用されているのであり、好ましいことではない。

\vspace{8pt}
\subsubsection{`defined' に展開されるマクロ}
  example-10 のようなマクロ定義があり、example-11 のように使われている。
しかし、\#if 式中でマクロ展開の結果に \verb|defined| という
トークンが出てくるのは、
規格では undefined である。
そのことは別としても、このマクロはまず example-12 のように置換され、
\verb|__linux__| が 1 に定義されていて \verb|__arm__| が定義されていない場合、
最終的な展開結果は普通は example-13 のようになる。
\verb|defined(1)| というのは \#if 式としては、もちろん syntax error である。

実際、GNU C / cpp でも \#if 行でなければこうなるが、
ところが \#if 行では example-12 で展開をやめてしまって、
これを \#if 式として評価するのである。
一貫しない仕様であり、この書き方には portability がない。
このマクロは example-14 のように書けば問題ない。

\vspace{8pt}
\subsubsection{関数型マクロとして展開されるオブジェクト型マクロ}
  展開すると関数型マクロ (function-like macro) の名前になる
オブジェクト型マクロ (object-like macro) の定義が時々ある。
このマクロの呼び出しは後続するトークン列を取り込んで、
関数型マクロとして展開されることになる。
マクロ展開のこの仕様は C90 以前からの伝統的なものであり、
C90 でも公認されたものである。
その意味では portability が高いとも言える。
example-15 のようなオブジェクト型マクロの定義があり、
\verb|SWAPU16| の定義を見ると example-16 のようになっているというものである。

  しかし、オブジェクト型マクロと見えて
実は関数型マクロとして展開されるマクロというのは、
少なくとも readability が悪い。
そういう書き方をするメリットも、少なくともこの場合はないと思われる。
この書き方の背景にあるのは、エディタによる一括置換の発想であり、
Ｃの関数型マクロの書き方としては感心しない。
これは初めから関数型マクロとして example-17 のように書いたほうが良い。

\vspace{8pt}
\subsubsection{Undocumented な環境変数の仕様}
  これはＣのソースではなく Makefile の問題であるが、
\verb|SUNPRO_DEPENDENCIES| なる環境変数が定義されていると、
-dM オプションの出力先がその定義されたファイル名
になるという GNU C 2 / cpp では undocumented な仕様を使うものがある。
\verb|DEPENDENCIES_OUTPUT| という類似の環境変数もあり、
こちらはドキュメントにあるが、どちらも必要性は疑問である。
\vspace{8pt}

  以上のほかにもいくつかの問題点がある。
その多くは、より明快な形で書くことが簡単にできるものである。
Glibc の数千本のソースファイルから見れば問題のソースはごく一部であるが、
GNU C / cpp がウォーニングを出していれば、
こうしたソースは書き直されるか、
あるいは初めから別の書き方になっていたと思われるのである。

\section{Ｃプリプロセスの原則と MCPP の実装方法}

  MCPP とその検証セットで洗い出される
プリプロセス上の多くの問題の底流にあるのは、
Ｃプリプロセスの原則に関する混乱である。
C90 以前はＣプリプロセスの原則や仕様はきわめてあいまいなものであった。
C90 でＣのプリプロセス仕様は、
その原則にまでさかのぼって初めて全面的に定義されたのであった。
しかし、現実の処理系の多くは原則をあいまいにしたまま
個々の仕様を接ぎ木することによって、
問題を長く引きずってきていると考えられる。
その上、C90 自体にも歴史的な背景から来る中途半端な規定や矛盾が
いくつか存在し、
C99 でも改められていないことが、
問題をいっそう複雑にしている。

  C90 のプリプロセス規定から，次のような原則を抽出することができるであろう．

\begin{enumerate}
\item 「トークンベース」の処理を原則とする。
\item 引数付きマクロの呼び出しは関数呼び出しをモデルとして文法が整理されている。
\item マクロの定義と展開は多くのプリプロセス処理のうちの１つであり、
他の処理に優先するものではない。
\item 実行時環境からは独立した文字通りの「プリ」プロセスであり、
処理系依存の部分の必要性はほとんどない。
\end{enumerate}

  これらは MCPP の実装の原則でもある。

\vspace{8pt}
\subsection{トークンベースの処理}

  Ｃのプリプロセスは「トークンベース」を原則とするものであるが、
C90 以前にはあいまいであったために、
文字ベースのテキスト処理の発想が入り込んでいた。
C90 以降もそうした処理を期待するソースをプリプロセッサが看過していたり、
プリプロセッサ自身に文字ベースの処理が混入していたりすることによって、
問題が長く続いてきているのである。
さらに C90 自体にも、
\# 演算子の規定や header-name トークンの規定に文字ベースのなごりが残っている
（この問題については cpp-text.txt の 1.7 節で詳細に検討している）。

  MCPP のプログラム構造は「トークンベースのプリプロセス」
という原則で組み立てられており、
traditional な文字ベースのプリプロセスとは発想を異にする。
他のプリプロセッサでは、トークンベースの処理を意図しながらも、
そこに文字ベースの処理が紛れ込んでしまっていることが多いようである。
プリプロセッサのバグの何割かはこれによるものだと思われる。

  例えば Borland C 4.0,5.5 や Visual C++ .net 2003 では、
マクロ展開によって生成されたトークンが
前後のトークンとくっついて一つになってしまうことがある。
これは中途半端なトークン処理の例である。
また、マクロ展開によって illegal なトークンが生成されても、
何のウォーニングも出さないプリプロセッサは多い。
これはプリプロセスの結果に対するトークンチェックを怠っているからである。

\vspace{8pt}
\subsection{関数型マクロの関数的展開}

  引数のないマクロの展開は単純なものであるが、
引数付きマクロの展開には歴史的にさまざまな仕様があり、混乱があった。
C90 で一応の整理がされたが、まだ収束したとは言えない状況にある。
この問題については私の検証セットの cpp-test.txt の 1.7.6 節で
詳細に論じているところである。

  混乱の元は一つには、エディタによるテキストの一括置換と同じ
テキストベースの発想である。
もう一つは、マクロ呼び出しに際して、
その置換リストが別の引数付きマクロの呼び出しの前半部分を構成する場合、
再走査時に後続するトークン列を巻き込んで展開されるという伝統的な仕様である。
6.2.4 で言及したのはその最も害の少ない例である。
この仕様は、たまたまＣプリプロセッサの伝統的な実装方法が
そうした欠陥を持っていたことによるものと考えられる。
意図しない「バグのような仕様」だったのではないだろうか。
しかし、これが種々の変則的マクロを誘発する結果となったのである。

  C90 はこの混乱の続いていた引数付きマクロについて、
「関数型マクロ (function-like macro)」という名前を付けて、
関数呼び出しに似せて仕様を整理した。
すなわち，関数呼び出しと相互に置き換えて使うことができる形を意図したのである．
それによって、引数内のマクロが先に展開されてから
置換リスト中のパラメータが対応する引数と置き換えられること、
引数中のマクロの展開はその引数の中で完結しなければならないことが明確にされた
（C90 以前には、パラメータを引数と置き換えてから、
再走査時に展開する実装が多かったと思われる）。

  ところが C90 は他方で、再走査時に後続するトークン列を取り込むという
バグのような仕様を公認してしまった。
これは function-like の原則をぶちこわすものであり、
これによって混乱がその後も続くことになったのである。
同時に C90 は、マクロ展開で無限再帰が発生することを防ぐために、
同名のマクロは再走査時には再置換しないという規定を付け加えている。
しかし、「後続するトークン列」の取り込みを認めたために、
再置換禁止の範囲はどこまでかという疑義を解消することができず、
corrigendum が出たり再訂正されたりと、迷走を続けてきている。

\vspace{8pt}
\subsection{マクロ展開と他の処理との分離}

  多くのＣプリプロセッサの実装では、
マクロ再走査時に置換リストと後続テキストとを連続して読み込むことを原則とする、
伝統的なプログラム構造をとっているようである。
これはマクロ呼び出しが置換リストに置き換えられると、その先頭に戻って再走査し、
対象を後ろにずらしながら次のマクロ呼び出しをサーチして、
再走査を繰り返してゆくものである。

  この伝統的プログラム構造の背景にあるのは、
Ｃプリプロセッサがマクロプロセッサから生まれたという歴史的事情である。
GNU C 2 / cpp のように、
マクロ再走査ルーチンがプリプロセッサの事実上のメインルーチンとなっていて、
これが対象を後ろにずらしながらテキストを入力ファイルの終わりまで
「再走査」してゆき、
この中からプリプロセスディレクティブの処理ルーチンまでも呼び出されるという
組み立てになっているものもある。
これはマクロプロセッサの構造であるが、
マクロ展開と他の処理とが混交しやすいという問題を持っている。
6.2.3 で見た \#if 行でマクロ展開の仕様が変わってしまうのは、その一例である
（GNU C 2 / cpp は \#if 行では
内部的に \verb|defined| を特殊なマクロとして扱っている）。

  MCPP の実装では、
標準モードおよび post-Standard モードのマクロ展開ルーチンは
伝統モードのものとはまったくの別ルーチンとして書いている。
そして、マクロ展開ルーチンはマクロ展開だけをやり、他のことはやらない。
また、他のルーチンはマクロ展開はすべてマクロ展開ルーチンに任せて、
その結果だけを受け取る。
マクロ展開ルーチンは繰り返しではなく再帰構造で組み立て、
同名マクロの再置換を防ぐ簡単な歯止めを付けている。
関数型マクロの展開は function-like の原則を徹底させ、
再走査はマクロ呼び出しの中で完結することを原則としている。
Post-Standard モードでは、マクロ展開はすっきりとこれでおしまいである。
そして、標準モードでは規格の不規則な規則に対応するためにあるトリックを設けて、
必要なときだけ例外的に後続のトークン列を取り込むようにしている。
このほうがプログラム構造が明快になり、
変則的なマクロを捕捉してウォーニングを出すことが容易になるからである。

\vspace{8pt}
\subsection{ポータブルなＣプリプロセッサ}

  Ｃにプリプロセスというフェーズがあるのは
portability の確保が大きな目的の一つであるが、
プリプロセッサが処理系のオマケのような存在である場合が多く、
その仕様がまちまちであることによって、
プリプロセスが逆に portability を損なう結果をしばしば生んできた。
それに対して、C90 ではプリプロセスは実行時環境から
ほぼ独立したフェーズとして規定されており、
それによってかなりの portability が保証されている。

  そればかりでなく、プリプロセッサそのものについても、
処理系の他の部分と異なり、portable に書くことが可能となった。
各処理系が高品質で portable な同一のプリプロセッサを使うという形態さえ、
ありえないことではない。
Portable なソースのための portable なプリプロセッサが現れる条件は C90 で
用意されたと言える。
MCPP もこの状況を動機の１つとして開発が始められたものである。
現在はコンパイラにプリプロセッサを吸収した処理系が増えてきているが、
プリプロセッサが独立したプログラムであることは、
コンパイラに依存する部分を少なくして
特立したフェーズとしてのプリプロセスの portability を
確保するために有効なことなのである。

  C90 のプリプロセス規定には以上のような諸原則が体現されている。
しかし、残念ながら同時に上記のような矛盾も存在しており、
その解決は後の規格が課題とすべきものであった。
ところが、C99 では新しい機能がいくつか付け加えられたものの、
これらの論理の矛盾は何一つ解決されなかったのである。
そればかりか、追加された機能によって仕様の明快さが損なわれた部分さえある。
C++98 にはさらに問題が多い
（これらの問題については、cpp-test.txt で詳細に検討している）。

  結局、Ｃプリプロセスの歴史では、
C90 が不十分ながらも言語仕様の根本にまで踏み込んで規定した
唯一の動きだったと言える。
現在は再び仕様が拡散しかけている時であり、
再度踏み込んだ整理が必要になっている。
その方向は、C90 で中途半端に終わったいくつかの原則を徹底させることであろう。

  MCPP は「トークンベース」「関数型マクロの関数的展開」
「マクロ処理と他の処理との分離」「portable なプリプロセス」
等の原則を中心として組み立てられたＣプリプロセッサである。
規格準拠モードではその上にいくつかの修飾を加えることで
規格に対応させている。
規格そのものの不規則性を整理してこれらの原則を徹底させた
自称 post-Standard モードのプリプロセスもあり、
これは私の期待する単純明快な仕様のＣプリプロセッサを実装してみたものである。
このモードで問題の検出されないソースは、
プリプロセス上はきわめて portability の高いソースだと言える。

\section{現バージョンと update 計画}

\subsection{V.2.5}

MCPP V.2.5 は 2004/03 にリリースした V.2.4.1 に
次のような updates を加えたものである。

\begin{enumerate}
\item 従来の post-Standard モードを Standard モードの実行時オプションに
吸収した。
Old-preprocessor 仕様を pre-Standard モードの実行時オプションに吸収した。
\item 再帰的マクロの展開方法を再修正し、完全にした。
\item Old-preprocessor 仕様を変更し、Reiser cpp の仕様に近づけた。
\item 一部の実行時オプションと \#pragma ディレクティブの名前を変更した。
\item GNU C V.3.3, 3.4 に対応させた。
\item その他、いくつかの小さな改良を加えた。
\item 検証セットの配点を変更した。
\end{enumerate}

\subsection{V.2.6 の計画}

MCPP の update は作業がやや遅れているが、
現在は V.2.6 で次のようなことを計画している。

\begin{enumerate}
\item MCPP の診断メッセージを別ファイルに分離し、
各国語版の診断メッセージを随時追加できるようにする。
\item GNU C 3 / cpp のソースプログラムと testsuite に検討を加える。
\item 問題のあるソースを portable なものに自動的に書き換える
オプションを実装する。
\item ドキュメントには texinfo 版と html 版を用意して、検索性を改善する。
\end{enumerate}

\section{おわりに}

  最高度の規格準拠性と品質のＣプリプロセッサを目指して MCPP を
Ｃプリプロセス検証セットと並行して開発し、
他のＣプリプロセッサに対する優位性を示すことができた。
また、Ｃプリプロセッサの実装では、
原則を明確にしてプログラムを組み立てることが最重要であることを論じた。

  私が DECUS cpp をいじり始めたのは 1992 年のことである。
それから１０年の歳月が経過した末に、
MCPP は「未踏ソフトウェア」に採択されて、ようやく世に出る機会を与えられた。
２年近くにわたる仕上げによって、
世界一正確で品質の優れたＣプリプロセッサを開発することができたつもりである。
そして、英語版ドキュメントとともに国際的な評価の場に出せる状態となった。
「未踏ソフトウェア」では、その成果によって最高級の評価も受けることができた。
熟年のアマチュアプログラマとして、非力ながらもよくやったと自分では納得している。

\vspace{8pt}
  MCPP は V.2.0 以来、vector と @nifty で公開してきた。

  未踏ソフトウェアのプロジェクトでは、
m17n.org に CVS repository, FTP site, WWW page が用意された。
MCPP V.2.3 以降はここに置かれている。~\cite{mcpp}

  プロジェクトの終了後も MCPP の update 作業を続けており、
今後も続けてゆくつもりである。
多くのＣプログラマのコメントと開発参加をいただければ幸いである。

\renewcommand{\refname}{参考文献, URL}
\begin{thebibliography}{99a}
\bibitem{C90a}
  ISO/IEC.
  \newblock \textit{ISO/IEC 9899:1990(E) Programming Languages -- C}.
  \newblock 1990.
\bibitem{C90b}
  ISO/IEC.
  \newblock \textit{~~ibid.~~Technical Corrigendum 1}.
  \newblock 1994.
\bibitem{C90c}
  ISO/IEC.
  \newblock \textit{~~ibid.~~Amendment 1: C integrity}.
  \newblock 1995.
\bibitem{C90d}
  ISO/IEC.
  \newblock \textit{~~ibid.~~Technical Corrigendum 2}.
  \newblock 1996.
\bibitem{C99a}
  ISO/IEC.
  \newblock \textit{ISO/IEC 9899:1999(E) Programming Languages -- C}.
  \newblock 1999.
\bibitem{C99b}
  ISO/IEC.
  \newblock \textit{~~ibid.~~Technical Corrigendum 1}.
  \newblock 2001.
\bibitem{C99c}
  ISO/IEC.
  \newblock \textit{~~ibid.~~Technical Corrigendum 2}.
  \newblock 2004.
\bibitem{C++98}
  ISO/IEC.
  \newblock \textit{ISO/IEC 14882:1998(E) Programming Languages -- C++}.
  \newblock 1998.
\bibitem{ipa}
  情報処理推進機構\\
  「未踏ソフトウェア創造事業」\\
  \verb|http://www.ipa.go.jp/jinzai/esp/|
\bibitem{decus}
  Martin Minow,~DECUS cpp.\\
  \verb|http://sources.isc.org/devel/|\\
  \verb|        lang/cpp-1.0.txt|
\bibitem{bc4}
  Borland International Inc.,~ボーランド株式会社.\\
  \newblock \textit{Borland C++ V.4.0}.
  \newblock 1994.
\bibitem{bc55}
  Borland Software Corp.,~ボーランド株式会社.,~Borland C++ Compiler 5.5\\
  \verb|http://www.borland.co.jp/|\\
  \verb|    cppbuilder/freecompiler/|
\bibitem{gcc}
  Free Software Foundation,~GCC\\
  \newblock \verb|http://gcc.gnu.org/|
\bibitem{ucpp}
  Thomas Pornin,~ucpp V.1.3.\\
  \newblock \verb|http://pornin.nerim.net/ucpp/|
\bibitem{vc2003}
  Microsoft Corporation.\\
  \newblock \textit{Visual C++ .net 2003}.
\bibitem{lcc-win32}
  Jacob Navia,~LCC-Win32 V.3.2.\\
  \newblock \verb|http://www.q-software-|\\
  \newblock \verb|    solutions.com/lccwin32/|
\bibitem{wave}
  Hartmut Kaiser,~Wave V.1.0.0.\\
  \newblock \verb|http://sourceforge.net/|\\
  \newblock \verb|    projects/spirit/|
\bibitem{mcpp}
  松井　潔,~MCPP V.2.5.\\
  \newblock \verb|http://www.m17n.org/mcpp/|
\bibitem{hw}
  有限会社ハイウェル.\\
  \newblock \verb|http://www.highwell.net/|
\end{thebibliography}

\end{document}

