Files correlati : Commento : Aggiunto il preprocessore c++ mcpp per sostituire il compilatore nella compilazione delle maschere.
4138 lines
330 KiB
HTML
4138 lines
330 KiB
HTML
<html>
|
||
<head>
|
||
<meta http-equiv="Content-Type" content="text/html; charset=EUC-JP">
|
||
<style> tt {color: maroon} </style>
|
||
<style> table {border-collapse: separate; border-spacing: 0px; empty-cells: show; background-color: #f0f0ff} </style>
|
||
<style> th, td {text-align: left; padding-left: 15px; padding-right: 15px} </style>
|
||
</head>
|
||
<body>
|
||
<div align="center">
|
||
<h1>CPP-TEST</h1>
|
||
<h3>== Validation Suite for Standard C Conformance of Preprocessing ==</h3>
|
||
</div>
|
||
<div align="right">
|
||
<h4>for V.1.5.5 (2008/11)<br>
|
||
松井 潔 (kmatsui@t3.rim.or.jp)</h4>
|
||
</div>
|
||
<div align="center">
|
||
<h2>== 目次 ==</h2>
|
||
</div>
|
||
|
||
<h4><dl><dt><a name="toc.1" href="#1">1. Standard C と Validation Suite</a>
|
||
<dd><a name="toc.1.1" href="#1.1">1.1. 履歴</a>
|
||
<dd><a name="toc.1.2" href="#1.2">1.2. 規格</a>
|
||
<dd><a name="toc.1.3" href="#1.3">1.3. このドキュメントの表記法</a>
|
||
<br>
|
||
<br>
|
||
<dt><a name="toc.2" href="#2">2. Standard C プリプロセスの特徴</a>
|
||
<dd><a name="toc.2.1" href="#2.1">2.1. K&R 1st. と Standard C のプリプロセス</a>
|
||
<dd><dl><dt><a name="toc.2.2" href="#2.2">2.2. Translation phases</a>
|
||
<dd><a name="toc.2.2.1" href="#2.2.1">2.2.1. 行接続は tokenization の前に</a></dl>
|
||
<dd><dl><dt><a name="toc.2.3" href="#2.3">2.3. Preprocessing token</a>
|
||
<dd><a name="toc.2.3.1" href="#2.3.1">2.3.1. Keyword がない</a>
|
||
<dd><a name="toc.2.3.2" href="#2.3.2">2.3.2. Preprocessing-number</a>
|
||
<dd><a name="toc.2.3.3" href="#2.3.3">2.3.3. Token-base の動作と token の連結</a></dl>
|
||
<dd><a name="toc.2.4" href="#2.4">2.4. #if 式の評価の型</a>
|
||
<dd><a name="toc.2.5" href="#2.5">2.5. Portable なプリプロセッサ</a>
|
||
<dd><dl><dt><a name="toc.2.6" href="#2.6">2.6. 関数様マクロの展開方法</a>
|
||
<dd><a name="toc.2.6.1" href="#2.6.1">2.6.1. 関数呼び出しと同等</a>
|
||
<dd><dl><dt><a name="toc.2.6.2" href="#2.6.2">2.6.2. 引数は置き換えの前に展開される</a>
|
||
<dd><a name="toc.2.6.2.1" href="#2.6.2.1">2.6.2.1. #, ## 演算子の operand は展開されない</a></dl>
|
||
<dd><a name="toc.2.6.3" href="#2.6.3">2.6.3. 再走査とは</a>
|
||
<dd><a name="toc.2.6.4" href="#2.6.4">2.6.4. 同一マクロの再帰的展開の防止</a></dl>
|
||
<dd><dl><dt><a name="toc.2.7" href="#2.7">2.7. 問題点</a>
|
||
<dd><a name="toc.2.7.1" href="#2.7.1">2.7.1. <stdio.h> の形の header name</a>
|
||
<dd><a name="toc.2.7.2" href="#2.7.2">2.7.2. Character-base のなごりを残す # 演算子の規定</a>
|
||
<dd><a name="toc.2.7.3" href="#2.7.3">2.7.3. マクロ再定義時の white spaces の扱い</a>
|
||
<dd><a name="toc.2.7.4" href="#2.7.4">2.7.4. 関数様マクロ再定義時のパラメータ名</a>
|
||
<dd><a name="toc.2.7.5" href="#2.7.5">2.7.5. 何を評価するのかわからない #if 式の文字定数</a>
|
||
<dd><a name="toc.2.7.6" href="#2.7.6">2.7.6. マクロ再走査の関数様でない規定</a>
|
||
<dd><a name="toc.2.7.7" href="#2.7.7">2.7.7. C90 Corrigendum 1, Amendment 1 での追加</a>
|
||
<dd><a name="toc.2.7.8" href="#2.7.8">2.7.8. 冗長な規定</a></dl>
|
||
<dd><a name="toc.2.8" href="#2.8">2.8. C99 のプリプロセス規定</a>
|
||
<dd><a name="toc.2.9" href="#2.9">2.9. 明快なプリプロセス規定を</a>
|
||
<br>
|
||
<br>
|
||
<dt><a name="toc.3" href="#3">3. Validation Suite 解説</a>
|
||
<dd><a name="toc.3.1" href="#3.1">3.1. Validation Suite for Conformance of Preprocessing</a>
|
||
<dd><dl><dt><a name="toc.3.2" href="#3.2">3.2. テスト方法</a>
|
||
<dd><a name="toc.3.2.1" href="#3.2.1">3.2.1. 手動テスト</a>
|
||
<dd><a name="toc.3.2.2" href="#3.2.2">3.2.2. cpp_test による自動テスト</a>
|
||
<dd><dl><dt><a name="toc.3.2.3" href="#3.2.3">3.2.3. GCC / testsuite による自動テスト</a>
|
||
<dd><a name="toc.3.2.3.1" href="#3.2.3.1">3.2.3.1. TestSuite とは</a>
|
||
<dd><a name="toc.3.2.3.2" href="#3.2.3.2">3.2.3.2. TestSuite へのインストールとテスト</a>
|
||
<dd><a name="toc.3.2.3.3" href="#3.2.3.3">3.2.3.3. <b>mcpp</b> の自動テスト</a>
|
||
<dd><a name="toc.3.2.3.4" href="#3.2.3.4">3.2.3.4. TestSuite と検証セット</a></dl></dl>
|
||
<dd><a name="toc.3.3" href="#3.3">3.3. Violation of syntax rule or constraint と診断メッセージ</a>
|
||
<dd><dl><dt><a name="toc.3.4" href="#3.4">3.4. 詳細</a>
|
||
<dd><a name="toc.3.4.1" href="#3.4.1">3.4.1. Trigraphs</a>
|
||
<dd><a name="toc.3.4.2" href="#3.4.2">3.4.2. <backslash><newline> による行接続</a>
|
||
<dd><a name="toc.3.4.3" href="#3.4.3">3.4.3. コメント</a>
|
||
<dd><a name="toc.3.4.4" href="#3.4.4">3.4.4. 特殊なトークン (digraphs) と文字 (UCN)</a>
|
||
<dd><a name="toc.3.4.5" href="#3.4.5">3.4.5. Preprocessing directive line 中の space, tab</a>
|
||
<dd><a name="toc.3.4.6" href="#3.4.6">3.4.6. #include</a>
|
||
<dd><a name="toc.3.4.7" href="#3.4.7">3.4.7. #line</a>
|
||
<dd><a name="toc.3.4.8" href="#3.4.8">3.4.8. #error</a>
|
||
<dd><a name="toc.3.4.9" href="#3.4.9">3.4.9. #pragma, _Pragma() operator</a>
|
||
<dd><a name="toc.3.4.10" href="#3.4.10">3.4.10. #if, #elif, #else, #endif</a>
|
||
<dd><a name="toc.3.4.11" href="#3.4.11">3.4.11. #if defined</a>
|
||
<dd><a name="toc.3.4.12" href="#3.4.12">3.4.12. #if 式の型</a>
|
||
<dd><a name="toc.3.4.13" href="#3.4.13">3.4.13. #if 式の演算</a>
|
||
<dd><a name="toc.3.4.14" href="#3.4.14">3.4.14. #if 式のエラー</a>
|
||
<dd><a name="toc.3.4.15" href="#3.4.15">3.4.15. #ifdef, #ifndef</a>
|
||
<dd><a name="toc.3.4.16" href="#3.4.16">3.4.16. #else, #endif のエラー</a>
|
||
<dd><a name="toc.3.4.17" href="#3.4.17">3.4.17. #if, #elif, #else, #endif の対応関係のエラー</a>
|
||
<dd><a name="toc.3.4.18" href="#3.4.18">3.4.18. #define</a>
|
||
<dd><a name="toc.3.4.19" href="#3.4.19">3.4.19. マクロの再定義</a>
|
||
<dd><a name="toc.3.4.20" href="#3.4.20">3.4.20. Keyword と同名のマクロ</a>
|
||
<dd><a name="toc.3.4.21" href="#3.4.21">3.4.21. Pp-token の分離を要するマクロ展開</a>
|
||
<dd><a name="toc.3.4.22" href="#3.4.22">3.4.22. Pp-number 中のマクロ類似 sequence</a>
|
||
<dd><a name="toc.3.4.23" href="#3.4.23">3.4.23. ## 演算子を使ったマクロ</a>
|
||
<dd><a name="toc.3.4.24" href="#3.4.24">3.4.24. # 演算子を使ったマクロ</a>
|
||
<dd><a name="toc.3.4.25" href="#3.4.25">3.4.25. マクロ引数中のマクロの展開</a>
|
||
<dd><a name="toc.3.4.26" href="#3.4.26">3.4.26. マクロ再走査中の同名マクロ</a>
|
||
<dd><a name="toc.3.4.27" href="#3.4.27">3.4.27. マクロの再走査</a>
|
||
<dd><a name="toc.3.4.28" href="#3.4.28">3.4.28. 事前定義マクロ</a>
|
||
<dd><a name="toc.3.4.29" href="#3.4.29">3.4.29. #undef</a>
|
||
<dd><a name="toc.3.4.30" href="#3.4.30">3.4.30. マクロ呼び出し</a>
|
||
<dd><a name="toc.3.4.31" href="#3.4.31">3.4.31. マクロ呼び出しのエラー</a>
|
||
<dd><a name="toc.3.4.32" href="#3.4.32">3.4.32. #if 式の文字定数</a>
|
||
<dd><a name="toc.3.4.33" href="#3.4.33">3.4.33. #if 式のワイド文字定数</a>
|
||
<dd><a name="toc.3.4.35" href="#3.4.35">3.4.35. #if 式の multi-character 文字定数</a>
|
||
<dd><a name="toc.3.4.37" href="#3.4.37">3.4.37. Translation limits</a></dl>
|
||
<dd><a name="toc.3.5" href="#3.5">3.5. 処理系定義部分のドキュメント</a>
|
||
<br>
|
||
<br>
|
||
<dt><a name="toc.4" href="#4">4. 規定されていない諸側面の評価</a>
|
||
<dd><a name="toc.4.1" href="#4.1">4.1. Multi-byte character encoding</a>
|
||
<dd><a name="toc.4.2" href="#4.2">4.2. Undefined behavior</a>
|
||
<dd><a name="toc.4.3" href="#4.3">4.3. Unspecified behavior</a>
|
||
<dd><a name="toc.4.4" href="#4.4">4.4. ウォーニングの望ましいその他のケース</a>
|
||
<dd><dl><dt><a name="toc.4.5" href="#4.5">4.5. その他の各種品質</a>
|
||
<dd><a name="toc.4.5.1" href="#4.5.1">4.5.1. 動作に関する品質</a>
|
||
<dd><a name="toc.4.5.2" href="#4.5.2">4.5.2. オプションと拡張機能</a>
|
||
<dd><a name="toc.4.5.3" href="#4.5.3">4.5.3. 実行効率等</a>
|
||
<dd><a name="toc.4.5.4" href="#4.5.4">4.5.4. ドキュメントの品質</a></dl>
|
||
<dd><a name="toc.4.6" href="#4.6">4.6. C++ のプリプロセス</a>
|
||
<br>
|
||
<br>
|
||
<dt><a name="toc.5" href="#5">5. C プリプロセスの周辺</a>
|
||
<dd><dl><dt><a name="toc.5.1" href="#5.1">5.1. 標準ヘッダファイル</a>
|
||
<dd><a name="toc.5.1.1" href="#5.1.1">5.1.1. 一般的規約</a>
|
||
<dd><a name="toc.5.1.2" href="#5.1.2">5.1.2. <assert.h></a>
|
||
<dd><a name="toc.5.1.3" href="#5.1.3">5.1.3. <limits.h></a>
|
||
<dd><a name="toc.5.1.4" href="#5.1.4">5.1.4. <iso646.h></a></dl>
|
||
<br>
|
||
<dt><a name="toc.6" href="#6">6. 各種プリプロセッサのテスト結果</a>
|
||
<dd><a name="toc.6.1" href="#6.1">6.1. テストしたプリプロセッサ</a>
|
||
<dd><a name="toc.6.2" href="#6.2">6.2. 採点表</a>
|
||
<dd><a name="toc.6.3" href="#6.3">6.3. 各プリプロセッサの特徴</a>
|
||
<dd><a name="toc.6.4" href="#6.4">6.4. 総評</a>
|
||
<dd><a name="toc.6.5" href="#6.5">6.5. テスト報告とご意見を</a>
|
||
</dl>
|
||
<br>
|
||
|
||
<h1><a name="1" href="#toc.1">1. Standard C と Validation Suite</a></h1>
|
||
<p>私は Martin Minow 作の DECUS cpp を全面的に書き直して <b>mcpp</b> V.2 と称する portable なCプリプロセッサを作りました。このプリプロセッサはソースで提供するもので、コンパイルする時にヘッダファイル中のいくつかのマクロを書き替えることで各種の処理系用に移植できるようになっています。また Standard C (ISO/ANSI/JIS C) を初めとする各種の動作モードを持っています。それらのうちの Standard C モードは、文字通りの厳密な Standard C プリプロセスを実現しているつもりです。</p>
|
||
<p>このプリプロセッサを作りながら私は、そのテストのために "Validation Suite for Standard C Conformance of Preprocessing" (プリプロセスの標準C適合性を検証するソフトウェア一式)と称するものを作りました。C ばかりでなく C++ もカバーしています。このドキュメントはその検証セット (Validation Suite) の解説です。この検証セットはこのドキュメントとともに、BSD ライセンスによるオープンソース・ソフトウェアとして公開します。</p>
|
||
<br>
|
||
|
||
<h2><a name="1.1" href="#toc.1.1">1.1. 履歴</a></h2>
|
||
<p>この検証セットは 1998/08 に NIFTY SERVE / FC / LIB 2 で公開され、http://www.vector.co.jp/pack にも転載されました。これには version number がありませんでしたが、これを version 1.0 であったことにします。</p>
|
||
<p>V.1.1 は、C99 1997/08 draft に対応して V.1.0 を update したものです。1998/09 に、やはり NIFTY SERVE / FC / LIB 2 および vector / software pack で公開されました。</p>
|
||
<p>V.1.2 は正式に決定した C++ Standard に対応して、V.1.1 にささいな updates を加えたものです。1998/11 に、やはり NIFTY SERVE / FC / LIB 2 および vector / software pack で公開されました。</p>
|
||
<p>V.1.3 は正式に決定した C99 に対応して、V.1.2 に updates を加えたものです。また、動作テストのサンプルは、GCC / testsuite で利用できるように書き直した edition が追加されました。</p>
|
||
<p>V.1.3 は開発の途中で、<b>mcpp</b> V.2.3 とともに、情報処理推進機構(IPA) の平成14年度「未踏ソフトウェア創造事業」に新部 裕・プロジェクトマネージャによって採択され、2002/07 - 2003/02 の間は IPA の資金援助と新部PMの助言のもとに開発が進められました。英語版ドキュメントもこのプロジェクトの中で、有限会社・ハイウェルに翻訳を委託し、それに私が修正とテキスト整形を加えてできあがったものです。2003/02 には <b>mcpp</b> V.2.3 と検証セット V.1.3 が m17n.org で公開されました。</p>
|
||
<p>さらに <b>mcpp</b> と検証セットは平成15年度「未踏ソフトウェア創造事業」にも伊知地 宏・PM によって継続して採択され、それぞれ V.2.4, V.1.4 への update 作業が進められました。*1, *2</p>
|
||
<p>その後も <b>mcpp</b> と検証セットはさらに改良の作業が続けられています。2005/03 にはそれぞれ V.2.5, V.1.5 がリリースされました。検証セット V.1.5 では配点の変更などがありました。2006/07 にはそれぞれ V.2.6, V.1.5.1 が公開されました。検証セット V.1.5.1 では各処理系の新しいバージョンのプリプロセッサのテスト結果が追加された以外には大きな変更はありません。その後、2006/11 に <b>mcpp</b> V.2.6.2 と検証セット V.1.5.2 が、2007/04 に <b>mcpp</b> V.2.6.3 と V.1.5.3 がリリースされています。検証セット V.1.5.2, V.1.5.3 ではこのドキュメントの若干の更新があっただけで、実質的な変更はありません。
|
||
さらに、2007/05 に <b>mcpp</b> V.2.6.4 が、2008/03 に <b>mcpp</b> V.2.7 と検証セット V.1.5.4 がリリースされました。
|
||
検証セット V.1.5.4 では少数のテストケースに修正と追加があったほか、Visual C++ 2008 のテスト結果が追加されました。
|
||
2008/05 には <b>mcpp</b> V.2.7.1 が、2008/11 には <b>mcpp</b> V.2.7.2 と検証セット V.1.5.5 がリリースされました。
|
||
検証セット V.1.5.5 では Wave V.2.0 のテスト結果が追加されました。</p>
|
||
<p>注:</p>
|
||
<p>*1 この cpp は V.2.2 までは単に cpp と呼んでいたが、一般の cpp と紛らわしいので、 V.2.3 からは <b>mcpp</b> と呼ぶことにした。Matsui CPP の意味である。このドキュメントでは V.2.2 までのバージョンも <b>mcpp</b> と呼ぶ。また、このドキュメントの名前は V.1.2 までは cpp_test.doc としていたが、V.1.3 からは cpp-test.txt と変更した。さらに V.1.5.2 からは cpp-test.html となった。私自身の名前も、V.1.2 までは Psycho としていたが、V.1.3 からは kmatsui と変更した。</p>
|
||
<p>*2 「未踏ソフトウェア創造事業」(Exploratory Software Project) の概要は次のところで知ることができる。</p>
|
||
<blockquote>
|
||
<p><a href="http://www.ipa.go.jp/jinzai/esp/">http://www.ipa.go.jp/jinzai/esp/</a></p>
|
||
</blockquote>
|
||
<p><b>mcpp</b> V.2.3 から V.2.5 までは次のところに置いてきたが、</p>
|
||
<blockquote>
|
||
<p>http://www.m17n.org/mcpp/</p>
|
||
</blockquote>
|
||
<p>2006/04 に次のところに移った。</p>
|
||
<blockquote>
|
||
<p><a href="http://mcpp.sourceforge.net/">http://mcpp.sourceforge.net/</a></p>
|
||
</blockquote>
|
||
<p>cpp V.2.2 および検証セット V.1.2 はベクター社のサイトの次のところにある。dos/prog/c というディレクトリに入れられているが、MS-DOS 専用ではない。ソースは UNIX, WIN32/MS-DOS, OS-9 等に対応している。</p>
|
||
<blockquote>
|
||
<a href="http://www.vector.co.jp/soft/dos/prog/se081188.html">http://www.vector.co.jp/soft/dos/prog/se081188.html</a><br>
|
||
<a href="http://www.vector.co.jp/soft/dos/prog/se081189.html">http://www.vector.co.jp/soft/dos/prog/se081189.html</a><br>
|
||
<a href="http://www.vector.co.jp/soft/dos/prog/se081186.html">http://www.vector.co.jp/soft/dos/prog/se081186.html</a><br>
|
||
</blockquote>
|
||
<p>これらのアーカイブファイル中のテキストファイルは、Vector のものは DOS/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種類のアーカイブファイルを置くようにした。</p>
|
||
<br>
|
||
|
||
<h2><a name="1.2" href="#toc.1.2">1.2. 規格</a></h2>
|
||
<p>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 と呼ぶことがあります。</p>
|
||
<p>この解説で参照する「規格書」は次のものです。</p>
|
||
<pre>
|
||
C90:
|
||
ANSI X3.159-1989 (ANSI, New York, 1989)
|
||
ISO/IEC 9899:1990(E) (ISO/IEC, Switzerland, 1990)
|
||
ibid. Technical Corrigendum 1 (ibid., 1994)
|
||
ibid. Amendment 1: C Integrity (ibid., 1995)
|
||
ibid. Technical Corrigendum 2 (ibid., 1996)
|
||
JIS X 3010-1993 (日本規格協会「JIS ハンドブック 59-1994」、東京、1994)
|
||
C99:
|
||
ISO/IEC 9899:1999(E)
|
||
ibid. Technical Corrigendum 1 (2001)
|
||
ibid. Technical Corrigendum 2 (2004)
|
||
C++:
|
||
ISO/IEC 14882:1998(E)
|
||
ISO/IEC 14882:2003(E)
|
||
</pre>
|
||
<p>ANSI X3.159 には "Rationale"(理由書)が付属していました。これは ISO C90 にはなぜか採用されませんでしたが、ISO C99 では復活しました。この "Rationale" も折りに触れて参照します。<br>
|
||
C++ は 1998 年版のあとで 2003 年版が制定されましたが、細部の文言の修正だけでした。少なくともプリプロセスについては変更はありません。そこで、この文書では 2003 年版も含めて C++98 と呼びます。</p>
|
||
<p>C99, C++ の規格書は PDF でフォーマットされたオンライン版が次のところで入手できます(open-std.org の各種 draft は無料)。</p>
|
||
<p>C99, C++98, C++03:</p>
|
||
<blockquote>
|
||
<p><a href="http://webstore.ansi.org/ansidocstore/default.asp">http://webstore.ansi.org/ansidocstore/default.asp</a></p>
|
||
</blockquote>
|
||
<p>C99+TC1+TC2:</p>
|
||
<blockquote>
|
||
<p><a href="http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf">http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf</a></p>
|
||
</blockquote>
|
||
<p>C99 Rationale 1999/10 final draft:</p>
|
||
<blockquote>
|
||
<p><a href="http://www.open-std.org/jtc1/sc22/wg14/www/docs/n897.pdf">http://www.open-std.org/jtc1/sc22/wg14/www/docs/n897.pdf</a></p>
|
||
</blockquote>
|
||
<br>
|
||
|
||
<h2><a name="1.3" href="#toc.1.3">1.3. このドキュメントの表記法</a></h2>
|
||
<p>このドキュメントはかつてはテキストファイルでしたが、V.1.5.2 からは html ファ
|
||
イルに変わりました。<br>
|
||
このドキュメントでは次のようにフォントを使い分けています。</p>
|
||
<ul>
|
||
<li><tt style="color:navy">source</tt>:<br>
|
||
<tt style="color:navy">紺色</tt>の等幅フォントは、ソースコードの断片・コマンドラインの入力・プログラムの出力を示すのに使われます。<br>
|
||
<li><tt>__STDC__</tt>:<br>
|
||
<tt>くり色</tt>の等幅フォントは、標準事前定義マクロを示すのに使われます。<br>
|
||
</ul>
|
||
<br>
|
||
|
||
<h1><a name="2" href="#toc.2">2. Standard C プリプロセスの特徴</a></h1>
|
||
<p>Validation Suite の解説をする前に、Standard C (ISO C) のプリプロセスの全体的な特徴を説明しておきます。これは教科書的な説明ではなく、K&R 1st. と比較しながら Standard C のプリプロセスの考え方と問題点を明らかにしようとするものです。</p>
|
||
<p>説明の仕方としては、K&R 1st. と C90 との相違を中心とし、 C90 と C99, C++ との相違を付け加えるという順序をとります。現在は規格としては C99 が有効なものですが、現実の処理系には C99 はまだ半分しか実装されていないので、C90 を中心とするほうが実際的だと思われるからです。</p>
|
||
<p>なお、この章ではサンプルをほとんど示していませんが、Validation Suite そのものがサンプルなので、そちらを参照してください。</p>
|
||
<br>
|
||
|
||
<h2><a name="2.1" href="#toc.2.1">2.1. K&R 1st. と Standard C のプリプロセス</a></h2>
|
||
<p>Pre-Standard のC言語処理系には千差万別の方言がありましたが、中でもプリプロセスにはほとんど基準がないと言ってよいくらいの状況でした。そうなった原因は、基準となるべき Kernighan & Ritchie "The C Programming Language", 1st. edition のプリプロセスの規定があまりにも簡単なあいまいなものであったことにあります。さらにその背景には、プリプロセスは言語本体に対する「おまけ」のようなものという考えがあったと思われます。しかし、K&R 1st. 以降、各処理系によってプリプロセスには多くの機能が付け加えられてきました。その内容には言語本体の不備を補うためのものと、異なる処理系間での portability を確保しようとするものとがありますが、どちらにしても処理系間の相違が多く、portable であるには程遠いのが実情でした。</p>
|
||
<p>C90 では長年の混乱の元であったこのプリプロセスに明確な規定が与えられました。新しく追加された機能もいくつかあり、それらについてはよく知られていますが、もっと大事なことは、C90 がプリプロセスに関する事実上初めての全面的な規定であるということです。この規定には、これまであいまいであった「プリプロセスとは何か」という基本的なことについての考え方が随所に現れています。C90 のプリプロセスは K&R 1st. + α ではないのです。これを理解するには「新機能」だけでなく、こうした基本を明確に把握することが必要だと思われます。しかし、残念なことには規格書本文にはそのことがまとめて述べられてはおらず、規格書の注釈である "Rationale" でもサラリと触れられているだけです。さらに残念なことには、旧来のプリプロセスとの妥協の結果と思われる首尾一貫しない部分も残されています。そこで、以下に C90 プリプロセスの基本的な特徴をまとめて述べ、次にその問題点を検討することにします。</p>
|
||
<p>Pre-Standard のプリプロセスと異なる、あるいは初めて明確にされた Standard C プリプロセスの特徴は、次の4点にまとめることができます。</p>
|
||
<ol>
|
||
<li>言語本体の処理系依存の部分(いわゆる実行時環境 execution environ-ment)からは独立した文字通りのプリプロセスである。処理系によってプリプロセスが意外な結果になる恐れはきわめて少ない。またこれによって、プリプロセッサそのもののソースを portable に書くことが可能になった。さらに同一のOSであれば、プリプロセッサの実行プログラムは1つでもすむと言って良いくらいである。。<br>
|
||
<br>
|
||
<li>Translation phases の規定によって、ソースを token に分割するまでの手順が明確に定められている。Token もプリプロセスが終わるまでは pre-processing token という暫定的な形で扱われる。Preprocessing token というものを本来の token と別に規定したことも、プリプロセスを言語本体の処理系依存の部分から切り離すことに役立っている。<br>
|
||
<br>
|
||
<li>プリプロセスは preprocessing token を処理単位とする、原則として token-oriented なものである。これに対して pre-Standard のプリプロセスは token-oriented な建前も一方にありながら、歴史的経緯からくる character-oriented な処理をする部分を少なからず持つ中途半端なものであった。<br>
|
||
<br>
|
||
<li>関数様マクロの展開は関数呼び出しをモデルにすることで、文法が整理されている。関数様マクロの呼び出しは関数呼び出しの使えるところならどこでも使える。引数中にマクロ呼び出しがある場合の処理も、関数の引数中に関数呼び出しがある場合の評価とパラレルであり、引数中のマクロが完全に展開されてから置換リスト中のパラメータと置き換えられる。この時、引数中のマクロ呼び出しはその引数内で完結していなければならない。<br>
|
||
</ol>
|
||
<p>これらの原則を以下に順次、検討していきます。</p>
|
||
<br>
|
||
|
||
<h2><a name="2.2" href="#toc.2.2">2.2. Translation phases</a></h2>
|
||
<p>プリプロセスの手順は K&R 1st. ではまったく記載されていなかったために、多くの混乱の元となってきました。C90 では translation phases というものが規定されて、これが明確にされました。要約すると次のようなものです。*1</p>
|
||
<ol>
|
||
<li>ソースファイルの文字を必要ならソース文字セットに map する。Trigraph の置換をする。*2<br>
|
||
<li><backslash><newline> を削除する。それによって物理行を接続して論理行にする。<br>
|
||
<li>Preprocessing token と white spaces とに分解する。コメントは one space character に置換する。改行コードは保持する。<br>
|
||
<li>Preprocessing directive を実行し、マクロ呼び出しを展開する。#include directive があれば、指定されたファイルについて phase 1 から phase 4 を再帰的に処理する。<br>
|
||
<li>ソース文字セットから実行時文字セットへの変換をする。同様に、文字定数中と文字列リテラル中の escape sequence を変換する。<br>
|
||
<li>隣接する文字列リテラルを連結し、隣接するワイド文字列リテラルを連結する。<br>
|
||
<li>Preprocessing token を token に変換し、コンパイルする。<br>
|
||
<li>リンクする。<br>
|
||
</ol>
|
||
<p>もちろん、これらは実際に別々の phase になっている必要はなく、それと同じ結果になるように処理すれば良いことになっています。</p>
|
||
<p>このうち phase 1 から phase 4 または 6 までがプリプロセスの範囲に属しますが、プリプロセッサが独立したプログラムになっていてプリプロセス結果を中間ファイルとして出力する場合は、改行コード等の token separator を保存する必要があるため、phase 4 までを担当するのが普通です(phase 5 で \n 等の escape sequence を変換してしまうと、これが token separator としての改行コード等と区別がつかなくなる)。この Validation Suite でテストするのも phase 4 までです。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 5.1.1.2 Translation phases 翻訳フェーズ
|
||
C99 5.1.1.2 Translation phases
|
||
</pre>
|
||
C99 では phase 4 に _Pragma() operator の処理が付け加えられた。
|
||
そのほか若干の字句が追加されているが、意味に変化はない。</p>
|
||
<p>*2 C99 1997/11 draft では、trigraphs の変換の前に、basic source character set に含まれない multi-byte character は \Uxxxxxxxx という形の Unicode の16進 sequence すなわち universal character name に変換する、という規定になっていた。そして、phase 5 でこれを実行時文字セットに再変換するというのである。C++ Standard でもほぼ同様である。仕様があいまいであるうえに、処理系にとっては負担の大きいことである。しかし、幸いなことに 1999/01 draft で phase 1 の処理は削除され、C99 正規版でもその通りとなった。</p>
|
||
|
||
<h3><a name="2.2.1" href="#toc.2.2.1">2.2.1. 行接続は tokenization の前に</a></h3>
|
||
<p><backslash><newline> による行接続は K&R 1st. では、</p>
|
||
<ol>
|
||
<li>長い #define 行の途中にあるもの、<br>
|
||
<li>長い文字列リテラルの途中にあるもの、<br>
|
||
</ol>
|
||
<p>の2つが記載されているだけで、その他の場合はどうなのかが明らかではありませんでした。</p>
|
||
<p>C90 では、phase 3 で preprocessing token と token separator としての white spaces への分割が行われる前に、phase 2 で <backslash><newline> の削除が行われることが明確にされ、どんな行でも、どんな token の途中でも有効となりました。</p>
|
||
<p>また、trigraph の処理は phase 1 で行われるので、??/<newline> という sequence も同様に削除されます。他方で、基本文字セットが ASCII で multi-byte character の encoding が shift-JIS 漢字である処理系では、漢字1字が1つの multi-byte character であるので、漢字2バイト目の 0x5c のコードは <backslash> ではありません。</p>
|
||
<p>Translation phase が明確になったのは良いのですが、はたして token の途中での行接続などというものを認める必要があるのかどうかは、疑問です。K&R 1st. では画面1行におさまらない長い文字列リテラルを画面におさまるように書くにはこの方法しかなかったわけですが、C90 では隣接する文字列リテラルが連結されることになったので、わざわざ token の途中で改行する必要はなくなっています。行接続が必要なのは長いコントロール行を書く時だけです。それだけであれば、phase 2 と 3 は逆のほうが良かったのです。</p>
|
||
<p>にもかかわらずこれが現在の規定のように決まったのは、K&R 1st. の文字列リテラルの行接続による連結という仕様を前提として書かれたソースを処理できるようにという、backward compatibility(後方互換性)のためだと思われます。新しいソースについては実用上はほとんど意味のない規定ですが、しかし、単純明快な、実装の最も容易なものであるので、妥当でしょう。</p>
|
||
<br>
|
||
|
||
<h2><a name="2.3" href="#toc.2.3">2.3. Preprocessing token</a></h2>
|
||
<p>Preprocessing token(以後、pp-token と略す)という概念も C90 で初めて導入されたものです。しかし、これについては世間であまり知られていないようなので、内容の要約から始めなければなりません。pp-token として規定されているのは次の通りです。*1</p>
|
||
<blockquote>header-name<br>
|
||
identifier<br>
|
||
preprocessing-number<br>
|
||
character-constant<br>
|
||
string-literal<br>
|
||
operator<br>
|
||
punctuator<br>
|
||
上記のどれにも当てはまらない non-white-space character</blockquote>
|
||
<p>何気なく見ると当たり前のようで見過ごしてしまいそうですが、これは本来の token とはかなり違っています。Token は次のようなものです。</p>
|
||
<blockquote>keyword<br>
|
||
identifier<br>
|
||
constant (floating-constant, integer-constant, enumeration-constant, character-constant)<br>
|
||
string-literal<br>
|
||
operator<br>
|
||
punctuator</blockquote>
|
||
<p>Pp-token が token と違っているのは次の点です。</p>
|
||
<ol>
|
||
<li>Keyword が存在しない。
|
||
keyword と同じ名前は identifier として扱われる。<br>
|
||
<li>Constant のうち character-constant は同じであるが、floating-constant, integer-constant, enumeration constant が存在せず、floating-constant, integer-constant の代わりに preprocessing-number というものがある。<br>
|
||
<li>Header-name は pp-token としてしか存在しない。<br>
|
||
<li>Operator と punctuator はほぼ同じであるが、operator の #, ## と punctuator の # は pp-token としてしか存在しない(いずれも preprocessing directive 行でだけ有効)。<br>
|
||
</ol>
|
||
<p>何と、同じなのは string-literal と character-constant だけなのです。中でも重要なのは keyword が存在しないことと、数値 token に代わる pre-processing-number の存在です。この2点について、さらに検討します。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.1 Lexical elements 字句要素
|
||
C99 6.4 Lexical elements
|
||
</pre>
|
||
C99 では pp-token でも token でも operator は punctuator に吸収された。Operator という用語は token の種類としてではなく、単に「演算子」という機能を表す用語となった。同じ punctuator token (punctuator pp-token) が文脈によって punctuator (区切り子)として機能したり、operator として機能したりすることになる。また、pp-token の演算子として _Pragma が追加された。</p>
|
||
|
||
<h3><a name="2.3.1" href="#toc.2.3.1">2.3.1. Keyword がない</a></h3>
|
||
<p>Keyword は phase 7 で初めて認識されます。プリプロセスの phases では keyword は identifier として扱われます。そして、プリプロセスにとっては identifier はマクロ名であるか、マクロとして定義されていない identifier であるかのどちらかです (*1)。ということは、keyword と同名のマクロさえも使えるということになります。</p>
|
||
<p>この規定は、プリプロセスを処理系依存部分から切り離すために不可欠のものだと思われます。これによって例えば #if 式にキャストや sizeof を使うことが禁止されます。*2</p>
|
||
<p>注:</p>
|
||
<p>*1 もっと正確に言えば、マクロ定義中のパラメータ名も identifier である。また、preprocessing directive 名は特殊な identifier であり、keyword と似た性格を持っている。しかし、これが directive であるかどうかは構文から判断されるものであり、directive でない場所にあれば、マクロ展開の対象ともなりうる単なる identifier である。</p>
|
||
<p>*2 <a href="#3.4.14.7">3.4.14.7</a>, <a href="#3.4.14.8">3.4.14.8</a> 参照。
|
||
</p>
|
||
|
||
<h3><a name="2.3.2" href="#toc.2.3.2">2.3.2. Preprocessing-number</a></h3>
|
||
<p>Preprocessing-number(以後、pp-number と略す)は次のように規定されています。
|
||
*1, *2</p>
|
||
<blockquote>
|
||
digit<br>
|
||
. digit<br>
|
||
pp-number digit<br>
|
||
pp-number nondigit<br>
|
||
pp-number e sign<br>
|
||
pp-number E sign<br>
|
||
pp-number .<br>
|
||
</blockquote>
|
||
<p>Non-digit というのは letter と underscore です。
|
||
</p>
|
||
<p>要約するとこうなります。
|
||
</p>
|
||
<ol>
|
||
<li>先頭が digit または . digit である。
|
||
<br>
|
||
<li>あとは letter (alphabet), underscore, digit, period および e+, e-, E+, E- の sequence がいくつどういう順序で並んでいてもかまわない。<br>
|
||
</ol>
|
||
<p>Pp-number は floating-constant, integer-constant のすべてを含みますが、さらに例えば 3E+xy といった数値ではないものを幅広く含んでいます。Pp-number はプリプロセスを簡単にするために採用されたもので、意味解釈に先立つこの種の sequence の tokenization に役立つとされています。*3</p>
|
||
<p>Tokenization が簡単になるのは確かですが、非数値 pp-number は有効な token ではありません。したがって、それらはプリプロセスの終わるまでに消滅していなければなりません。非数値 pp-number というものをわざわざソース中で使う必要はまずありませんが、あえて使うとすれば唯一考えられるのは、## 演算子を使って定義されたマクロで数値 pp-number と何かの pp-token とが連結されて非数値 pp-number となり、さらに # 演算子を使って定義されたマクロによってそれが文字列化される場合です。文字列リテラルの中に入れてしまえば、どんな pp-token も valid な token になります。しかし、非数値 pp-number の存在を認めないと、連結によって生成されたものが valid な pp-token ではなくなってしまいます(その結果は undefined となる)。</p>
|
||
<p>こういう使い道は極めて特殊なものであり詳細に検討する必要はありませんが、ただ pp-number は token-oriented なプリプロセスを考えるうえで興味深い題材を提供してくれます。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.1.8 Preprocessing numbers 前処理数
|
||
C99 6.4.8 Preprocessing numbers
|
||
</pre>
|
||
<p>*2 C99 では浮動小数点数の16進表記を可能にするために、pp-number p sign, pp-number P sign という sequence が追加されている。</p>
|
||
<p>また、上記の nondigit が identifier-nondigit に置き換えられた。これは identifier 中に UCN (universal character name) と処理系定義の multi-byte character の使用が認められたのに伴う変更である(<a href="#2.8">2.8</a> 参照)。すなわち、pp-number 中に UCN を使うことができ、multi-byte character を使う実装も可能である。数値トークンに UCN や multi-byte character が含まれることはないのであるが、やはり ##, # 演算子を使って文字列化する場合のことを考えて、これが認められたのであろう。</p>
|
||
<pre>
|
||
*3 C89 Rationale 3.1.8 Preprocessing numbers
|
||
C99 Rationale 6.4.8 Preprocessing numbers
|
||
</pre>
|
||
|
||
<h3><a name="2.3.3" href="#toc.2.3.3">2.3.3. Token-base の動作と token の連結</a></h3>
|
||
<p>C90 ではマクロ定義中の二項演算子 ## によって pp-token を連結することができるようになりました。このことは C90 の「新機能」としてよく知られています。しかし、これは追加された機能というよりは、旧来の裏技的な方法を代替するために導入されたものです。注目したいのは、これが token-oriented なプリプロセスのために必須のものとなっていることです。</p>
|
||
<p>旧来の token 連結の方法としては、いわゆる "Reiser" 型 cpp での、コメントを 0 個の character に置換するという仕様を利用したものが知られています。また、それ以外にも character-oriented な動作をするプリプロセッサでは、意図しない時にまで token の連結が発生してしまうことがあり、それを利用した裏技的方法もないではありませんでした。それらはいずれも、character-oriented なプリプロセスの欠陥を利用する方法と言えます。</p>
|
||
<p>これに対して C90 では、token-oriented な動作によって明示的に token を連結することができるようになっています。Translation phase 3 でソースが pp-token と white spaces の sequence に分解されますが、その後で pp-token が合成される場合というのは、## 演算子による連結と # 演算子による文字列化、header-name の生成、および隣接する文字列リテラルの連結とワイド文字列リテラルの連結しかありません。非数値 pp-number の存在もこの文脈の中に置いて考えると、その扱い方が明確になります。すなわち、C90 の tokenization については、次のような原則が存在していると言えるでしょう。</p>
|
||
<ol>
|
||
<li>Pp-token が暗黙のうちに連結されることはない。連結は ## 演算子によって明示しなければならない。<br>
|
||
<li>いったん連結された pp-token が再び分離されることはない。<br>
|
||
</ol>
|
||
<p>Pre-Standard の character-oriented なプリプロセスでは、マクロ呼び出しが展開されると、その結果の token sequence が前後の token と意図しない連結を引き起こすことがありました。しかし、これも token-oriented な Standard C のプリプロセスでは起こってはならないことだと考えられます。*1</p>
|
||
<p>注:</p>
|
||
<p>*1 <a href="#3.4.21">3.4.21</a> 参照。
|
||
</p>
|
||
<br>
|
||
|
||
<h2><a name="2.4" href="#toc.2.4">2.4. #if 式の評価の型</a></h2>
|
||
<p>C90 では #if 式の評価は long ないし unsigned long の1種類のサイズで行われることになりました (*1)。これも、プリプロセスを単純化するとともに、処理系依存部分を少なくするのに役立っています。int のサイズが処理系によって大きく変わるのに比べて、long / unsigned long は大半の処理系が32ビット、一部の処理系だけが64ビットまたは36ビットですから、一般の #if 式にはかなりの portability が確保できます。*2, *3</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.1 Conditional inclusion 条件付き取り込み -- Semantics 意味規則
|
||
|
||
*2 C99 6.10.1 Conditional inclusion
|
||
</pre>
|
||
<p>C99 では #if 式の型はその処理系の最大の整数型とされた。
|
||
C99 では long long / unsigned long long は必須であるので、これは long long / unsigned long long またはそれ以上ということになる。しかし、これによって #if 式の portability はかなり低下することになった。</p>
|
||
<p>*3 将来は long が64ビットの処理系が増えてくるかもしれないが、これは良いことか悪いことか・・ 余談であるが、私は整数型のサイズは次のように決めるほうが良いと思っている。</p>
|
||
<ol>
|
||
<li>short は2バイト、long は4バイトとする。<br>
|
||
<li>longlong(long long ではない)または quadra を8バイトとする。<br>
|
||
<li>int は CPU にとっての自然なサイズとする。すなわち、これは short, long, longlong のどれか1つと一致する。<br>
|
||
<li>short int, long int というふうに short, long を int の修飾に使うことはやめる。<br>
|
||
</ol>
|
||
<p>すなわち、64ビットの処理系の出現以来、sizeof (short) <= sizeof (int) <= sizeof (long) という制約があるためにすべてが不自然になり、どの型も portability がなくなってきているのである。この制約をはずして、絶対サイズで型を決めるのが良い。</p>
|
||
<br>
|
||
|
||
<h2><a name="2.5" href="#toc.2.5">2.5. Portable なプリプロセッサ</a></h2>
|
||
<p>以上のような Standard C プリプロセスの規定は、プリプロセッサそのもののソースを portable に書くことを可能にしています。プリプロセッサがコンパイラ本体の処理系依存部分について知らなければならないことは、何もないからです。実際に Standard C の処理系でプリプロセッサを portable に書こうとする時に問題となるのは、次のような周辺的な部分だけです。</p>
|
||
<ol>
|
||
<li>#include 処理のためのOSのパスリスト記述形式。標準ヘッダファイルのありか。<br>
|
||
<li>ファイル名と行番号の情報をコンパイラ本体に渡すための形式。<br>
|
||
<li>実行時オプション。<br>
|
||
<li>文字セット。<br>
|
||
<li>クロス処理系では、C90 では long / unsigned long の、C99 では最大の整数型のサイズがターゲット処理系のそれを下回っていてはならないこと。<br>
|
||
</ol>
|
||
<p>このうち2と3は既存の処理系に実装する場合に問題となるだけで、2もいずれはソースと同じ #line 123 "filename" という形式に統一されていくと期待されますし、3は無くても(使い勝手はともかく論理的には)Standard C のプリプロセスは可能です。4もソースの書き方によっては、特別な implementation を必要としない形で書けなくはありません(しかし、基本文字セットはソース中にテーブルを書くほうが実装が楽であるが)。5は実際には、ホスト処理系のほうがターゲット処理系よりも整数型のサイズが小さいということはほとんどないでしょうから、問題とはならないでしょう。</p>
|
||
<p><b>mcpp</b> ももちろん、こうした Standard C プリプロセスの仕様が処理系本体から独立していることを制作の動機としています(しかし、<b>mcpp</b> は多くの処理系に移植することを目的としているので、portability 確保のための #if section がかなり多くなっているが)。</p>
|
||
<br>
|
||
|
||
<h2><a name="2.6" href="#toc.2.6">2.6. 関数様マクロの展開方法</a></h2>
|
||
<p>引数付きのマクロの展開方法は C90 では関数呼び出しをモデルにして規定され、function-like(関数様)マクロと呼ばれるようになりました。引数にマクロが含まれていた場合は、原則として、それはパラメータとの置換に先だって展開されます。</p>
|
||
<p>Pre-Standard ではこの点も明らかではありませんでした。実際には、引数中のマクロは展開せずにパラメータと置換し、再走査時に展開するという方法をとるものが多かったのではないかと思われます。こういう展開方法の背景にあるのは、いわばエディタ様のテキスト置換の繰り返しという発想であったと推測されます。引数なしマクロの展開は一般にエディタ様のテキスト置換の繰り返しでかまわないわけですが、それを引数付きマクロの展開にも広げたものが、多くのプリプロセッサのマクロ展開方法だったのではないでしょうか。</p>
|
||
<p>しかし、この方法はソース上での関数呼び出し様の外観とはまったく異なった奇妙なマクロの使い方を誘発し、ネストされた引数付きマクロの呼び出しの際には、どれがどれの引数なのかわからなくなる事態も発生します。それらの点をめぐって処理系依存の部分も増えてきます。一言で言えば、Cのマクロ展開は高度な機能を担うようになったために、エディタ様のテキスト置換の繰り返しという発想ではもはや荷が重くなりすぎたと言えるでしょう。</p>
|
||
|
||
<h3><a name="2.6.1" href="#toc.2.6.1">2.6.1. 関数呼び出しと同等</a></h3>
|
||
<p>こうした混乱を踏まえて Standard C は、function-like マクロの呼び出しを関数呼び出しに代替できるものとして位置付けることで、文法を整理したと考えられます。Rationale は Standard C のマクロに関する規定がよりどころとした原則をいくつか挙げている中で、こういう原則を示しています。*1</p>
|
||
<ul>
|
||
<li><samp>関数を使えるところではどこでもマクロを使えるようにすること。</samp><br>
|
||
<li><samp>マクロ呼び出しはどこにあっても、すなわち地の文の中にあっても、マクロの引数中にあっても、マクロ定義の中にあっても、同じ token sequence を生成するようにマクロ展開を規定すること。</samp><br>
|
||
</ul>
|
||
<p>これは関数呼び出しなら当然のことですが、しかし引数付きマクロの呼び出しでは当然ではなかったのです。エディタ様テキスト置換の繰り返しでは、こうならないことは明らかです。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C89 Rationale 3.8.3 Macro replacement
|
||
C99 Rationale 6.10.3 Macro replacement
|
||
</pre>
|
||
|
||
<h3><a name="2.6.2" href="#toc.2.6.2">2.6.2. 引数は置き換えの前に展開される</a></h3>
|
||
<p>関数呼び出しとパラレルなマクロ展開という原則を実現するために肝心なのは、引数中のマクロを先に展開してからパラメータを引数に置き換えることです。そして、そのためには引数中のマクロ呼び出しはその引数中で完結していなければなりません(完結していない場合はエラーとなる)。引数中のマクロがその後ろのテキストを食ってしまうなどということがあってはなりません。それによって、ネストされた関数様マクロの呼び出しも論理的な明快さを保つことができます。*1</p>
|
||
<p>注:</p>
|
||
<p>*1 <a href="#3.4.25">3.4.25</a> 参照。
|
||
</p>
|
||
|
||
<h4><a name="2.6.2.1" href="#toc.2.6.2.1">2.6.2.1. #, ## 演算子の operand は展開されない</a></h4>
|
||
<p>ただし、# 演算子の operand はマクロ展開されないことになっています。
|
||
また、## 演算子の operand もマクロ展開されず、連結によって生成された pp-token は再走査時にマクロ展開の対象となります。この規定はなぜ必要なのでしょうか?</p>
|
||
<p>この規定が意味を持つのは、もちろん引数がマクロを含んでいる場合です。マクロを含む token sequence をそのまま文字列化したり連結したりしたい場合に、この規定が役立つことになります。逆に展開してから文字列化したり連結したりしたい場合は、#, ## 演算子を使わないマクロをもう一つかぶせることになります。このどちらでもプログラマが選択できるようにするためには、#, ## 演算子の operand はマクロ展開しないという規定が必要なのです。*1</p>
|
||
<p>注:</p>
|
||
<p>*1 <a href="#3.4.25">3.4.25</a>, misc.t / PART 3, 5 参照。# 演算子の operand がマクロ展開されないという規定が役に立つ代表的な例は assert() マクロである。<a href="#5.1.2">5.1.2</a> 参照。</p>
|
||
|
||
<h3><a name="2.6.3" href="#toc.2.6.3">2.6.3. 再走査とは</a></h3>
|
||
<p>さて、マクロ展開はマクロ呼び出しが置換リストに置き換えられ、function-like マクロの引数が原則として展開されてから置換リスト中のパラメータと置き換えられた後、さらに置換リスト中のマクロ呼び出しをさがして再走査されることになっています。</p>
|
||
<p>この再走査は K&R 1st. 以来の仕様です。そして、その背景には「エディタ様のテキスト置換の繰り返し」という発想があったと思われます。しかし、Standard C では function-like マクロの引数は ## 演算子の operand 以外は先に完全に展開されてしまっています。再走査ではいったい何を展開するのでしょうか?</p>
|
||
<p>再走査が必要なのは、置換リスト中のパラメータ以外の部分にマクロがある場合、および ## 演算子を含むマクロの場合、もう一つはマクロの「定義」が何重にもなったいわゆる cascaded macro です。マクロ呼び出しの「引数」が何重にもネストされている場合は、それらは再走査の前に入れ子構造で展開されているので、通常は再走査で新たに展開されることはありません(しかし、例外はある。<a href="#2.7.6">2.7.6</a>, <a href="#3.4.27">3.4.27</a> 参照)。
|
||
</p>
|
||
|
||
<h3><a name="2.6.4" href="#toc.2.6.4">2.6.4. 同一マクロの再帰的展開の防止</a></h3>
|
||
<p>Cascaded macro は次々と展開されていきますが、しかし、そうなっては困る場合があります。それはマクロ定義自身が再帰的になっている場合です。これをそのまま展開すると無限再帰に陥ってしまいます。定義自身にそのマクロが含まれている直接再帰の場合はもちろん、2つ以上の定義が間接的に再帰を成している場合も同じ問題をひき起こします。これを避けるため、Standard C では「展開途中のマクロと同じ名前のマクロが置換リスト中に現れても、それは置換しない」という規定を付加しています。規定の文章は難解ですが、その意図はわかりやすいものです。</p>
|
||
<p>これは関数様マクロの場合、関数とは違った文法になる点です。エディタ様置換ともまた違っています。マクロ固有の仕様であり、マクロでしかできない便利な処理として従来も使われてきたものなので、この仕様を明確化して残すのは妥当なところでしょう。</p>
|
||
<br>
|
||
|
||
<h2><a name="2.7" href="#toc.2.7">2.7. 問題点</a></h2>
|
||
<p>さて、以上では Standard C のプリプロセス規定の良い面、単純明快な面だけを取り上げてきましたが、詳細に検討すると、不規則なところや、処理系のオーバーヘッドの割に有用性や移植性に乏しいところがあちこちに含まれています。これらの多くは pre-Standard の伝統的・暗黙的なプリプロセス方法を精算しきれずに残ってしまったものだと思われますが、こうした役に立たない盲腸的部分の存在が、仕様をわかりにくくし、実装をやっかいにしています。また、Standard C によって新たに無用に煩雑になったところもわずかながらあります。以下にそれらの問題点を洗い出してみます。</p>
|
||
|
||
<h3><a name="2.7.1" href="#toc.2.7.1">2.7.1. <stdio.h> の形の header-name</a></h3>
|
||
<p><, > で囲まれた形の header-name は K&R 1st. 以来伝統的に使われてきているものですが、トークンとしてはきわめて例外的な不規則なものです。このため Standard C では header-name という pp-token に関しては undefined な部分と implementation-defined な部分が多くなってしまっています。例えば /* という sequence が含まれていた場合は undefined です。また、<stdio.h> のように、header-name でなければ <, stdio, ., h, > といくつもの pp-token に分解されるところを、#include 行に限って1つの pp-token に合成しなければなりません。その方法は implementation-defined です。Tokenization は translation phase 3 で行われるのに、phase 4 でその行が #include directive であるとわかると、tokenization をやりなおさなければならないのです。はなはだ非論理的な仕様と言わざるをえません。Phase 3 でいったん分解された(仮の)pp-token の間に space がある場合の処理も処理系定義となります。#include <stdio.h> などというディレクティブは最も portability の高いもののように見えますが、プリプロセスの実装を考えるとかなり portability の低いものなのです。#include 行の引数がマクロであると、さらに不規則性が増します。</p>
|
||
<p>", " で囲まれた形式の header-name には、これらの問題はありません。ただし、\ を escape 文字として扱わないというところは <, > による header-name と同様で、文字列リテラルとは違った面を持っています。しかし、escape sequence は phase 6 で処理されるものなので、phase 4 で処理されてしまう header-name に escape sequence が存在しないのは、非論理的なことではありません(規格では header-name 中の \ は undefined である。これは実装を楽にするための配慮であろう。実際、", " の中では \ が " の直前にあるのでない限り、問題は生じない。<, > の中ではもう少し複雑であるが)。*1</p>
|
||
<p>また、#include <stdio.h> と #include "stdio.h" との違いは、前者は処理系定義の特定の場所だけを探すのに対して、後者はカレントディレクトリ(からの相対パス)を先に探して、無ければ <stdio.h> と同じ場所を探す、というだけのことにすぎません(Standard C ではOSに関する前提を置いていないので「カレントディレクトリ」という用語は使っていないが、多くのOSについてはそう解釈される)。すなわち、#include <stdio.h> は #include "stdio.h" と書けばすむことなのです。</p>
|
||
<p>Header-name の形式に2種あることは、ユーザ定義のヘッダとシステムの提供するヘッダとの区別が一目でわかるという readability の利点がありますが、そのためにはわざわざ不規則なトークンを用意しなくても、"stdio.h" と "usr.H" というふうに suffix で区別すれば足ります(念のために付言すると、これは readability の問題であるから、システムがファイル名の大文字と小文字を区別しなくてもかまわない。もちろん、"usr.hdr", "usr.usr", "usr.u" 等でもかまわない)。</p>
|
||
<p><, > で囲まれた形の header-name は言語仕様としては無用であり、プリプロセスの tokenization を面倒にするだけなので、廃止したほうが良いと思います。過去のソースをコンパイルするためにはいきなり廃止するわけにはゆきませんが、obsolescent feature と規定してほしいものです。</p>
|
||
<p>*1 ところが、C99 では \ で始まる UCN が導入されたために、少々やっかいになった。</p>
|
||
|
||
<h3><a name="2.7.2" href="#toc.2.7.2">2.7.2. Character-base のなごりを残す # 演算子の規定</a></h3>
|
||
<p>次の問題は、# 演算子の operand 中の pp-token の間の token separator としての white spaces の扱いです。1つ以上の white spaces は1つの space に圧縮し、1つもない場合は space を挿入しないことになっています。</p>
|
||
<p>これは中途半端な規定です。Token-based な動作に徹底するためには、token separator の有無に左右されないことが必要であり、そのためには token separator をすべて削除するか、それともあらゆる pp-token の間に one space を置くか、どちらかに規定すべきでした。C89 Rationale 3.8.3.2 (C99 Rationale 6.10.3.2) The # operator はこの規定について、"As a compromise between token-based and character-based preprocessing discipline"(token-based なプリプロセス法と character-based なそれとの折衷案として)決定されたと述べています。</p>
|
||
<p>この折衷はプリプロセッサの実装を容易にするどころか、余計な負担をかける結果になっており、また複雑なマクロの展開方法にあいまいさをもたらしています。C90 6.8.3, C99 6.10.3 Macro replacement マクロ置換え -- Examples 例4. にこういう例が載っています。</p>
|
||
<pre style="color:navy">
|
||
#define str(s) # s
|
||
#define xstr(s) str(s)
|
||
#define INCFILE(n) vers ## n
|
||
|
||
#include xstr(INCFILE(2).h)
|
||
</pre>
|
||
<p>この #include 行はこう展開されるというのです。</p>
|
||
<pre style="color:navy">
|
||
#include "vers2.h"
|
||
</pre>
|
||
<p>この例は多くの問題をはらんでいます。
|
||
INCFILE(2) が vers2 に置換されることにはあいまいさはありません。しかし、xstr() の引数である INCFILE(2).h の展開結果は vers2, ., h という3つの pp-token の sequence です。規格書の展開例はこの3つの pp-token の間に white spaces がないものとして扱っています。ここには次のような問題があります。</p>
|
||
<ol>
|
||
<li>vers2 はソース中にあった pp-token ではなく、マクロ置換によって生成されたものである。vers2 の後ろに white spaces がないことを保証するためには、マクロ置換がその前後に white spaces を生成しないようにしなければならない。しかし、マクロ置換が常にそうであると、少なくともプリプロセッサがコンパイラから独立したプログラムである場合、マクロ展開の結果として pp-token が暗黙のうちに連結してしまうことが発生する。これは token-based なプリプロセスの原則に反する。<br>
|
||
<br>
|
||
<li># 演算子の operand になりうるマクロの置換で前後に white spaces を生成せず、しかも pp-token が暗黙のうちに連結するのを防ぐためには、関数様マクロ呼び出しの引数中に存在するマクロの置換では、内部的に仮の white space で置換結果をくるんでおき、それが # 演算子の operand になった場合は仮の white space を削除し、すべての置換が終わってから最後に残った仮の white space だけを本物の space に置き換える、といった小細工が必要になる (*1)。これはプリプロセスの実装にとってかなりの負担である。そして、それに見合うメリットがない。しかも、こうした処理が必要であることは規格書の本文からは明らかではなく、何が正しい処理であるのかが不明確である。<br>
|
||
</ol>
|
||
<p>こうしたあいまいさないしややこしさはすべて、# 演算子の operand 中の token separator の扱いの中途半端さに由来しています。</p>
|
||
<p># 演算子ではすべての pp-token を single space で分離した上で文字列化するという仕様が、pp-token の暗黙の連結を防ぎ、ややこしい問題を発生させることがなく、文字列化された引数がどういう pp-token sequence であったかがわかるので、良いと思われます。そう規定すると、このマクロは "vers2 . h" と展開されることになります。もちろん、これは適切なマクロではありません。</p>
|
||
<p>この例でわかるように、space のないところにそれが挿入されては困るほとんど唯一の場合が、#include 行のマクロで #, ## 演算子が使われた場合です。Translation phase 4 で処理される #include 行では、phase 6 で処理される文字列リテラルの連結は使えません。しかし、#include 行のマクロは強いて #, ## 演算子を使ってパラメータ化しなくても、単純に文字列リテラルで定義すればそれですむことです。たったこれだけのために token-based な原則を崩すのは、あまりにもバランスを失しています。</p>
|
||
<p>Standard C のプリプロセス規定では syntax は token-based なものでありながら、# 演算子の semantics のところで突然 character-based な規定が現れて、論理的な一貫性を損なっています。*2</p>
|
||
<p>その上、規格書のこの例は規格本文からは必ずしも明らかではない仕様を前提としています。不適切な例であり、削除すべきでしょう。</p>
|
||
<p>注:</p>
|
||
<p>*1 <b>mcpp</b> もしょうがないのでそうしている。</p>
|
||
<p>*2 JIS C は ISO C を翻訳しただけのもので、内容に変更は加えていないことになっている。しかし、「JIS ハンドブック」の X 3010 / 6.8.3 例4. の印刷には、fputs( ..) というマクロの展開結果が原文とは space の有無が違っているというミスがある。この印刷は space の扱いに無神経であり、# 演算子の規定をよく理解している人が原稿あるいは印刷をチェックしたとは思えない。しかし、規定そのものが無用に煩雑であることも確かである。</p>
|
||
|
||
<h3><a name="2.7.3" href="#toc.2.7.3">2.7.3. マクロ再定義時の white spaces の扱い</a></h3>
|
||
<p># 演算子の operand 中の white spaces の扱いと同様の規定がマクロ再定義に関してもあります。--マクロの再定義は元のマクロと等価なものでなければならない。等価であるためには、パラメータの数と名前が同じで、置換リストも同じ spelling でなければならない。ただし、置換リスト中の white spaces については、その有無は同じでなければならないが、数は違っていても良い。--と規定されています。</p>
|
||
<p># 演算子の規定が上記のようであれば、置換リスト中の white spaces についても同じ扱いをしなければならないので、これは当然の帰結です。やはり、問題の由来は # 演算子の規定にあります。</p>
|
||
<p># 演算子は operand 中の pp-token の間にはすべて one space があるものとして扱うということにすれば、マクロの再定義に際しても white spaces の有無は問題にならなくなります。</p>
|
||
<p>さらに、プリプロセッサの実装ではこれを一般化して、ソース中のすべての pp-token の間を原則として one space に置き換えるようにすることもできます。こうすることによって、マクロ展開に際しての tokenization を簡単かつ正確に行うことができます。ただし、この原則の例外が2つあります。1つは preprocessing directive 行での改行コードであり、もう1つはマクロ定義でのマクロ名とそれに続く '(' の間の white spaces の有無です。こればかりは伝統的にCのプリプロセスの根幹を成しているものであり、いまさら変えるわけにはいきません。</p>
|
||
|
||
<h3><a name="2.7.4" href="#toc.2.7.4">2.7.4. 関数様マクロ再定義時のパラメータ名</a></h3>
|
||
<p>マクロの再定義に際してはパラメータの名前も一致していなければならないと規定されていることは <a href="#2.7.3">2.7.3</a> で触れましたが、これは過剰な規定だと思われます。パラメータ名は、もちろんマクロの展開に何の違いももたらしません。しかし、再定義に際してこれをチェックするためには、プリプロセッサはすべてのマクロ定義のパラメータ名を記憶しておく必要があります。ところが、記憶してもその使い道は、少なくとも規定の範囲では、再定義のチェック以外に何もありません。たったこれだけのほとんど意味のないチェックのために、処理系にバカにならないオーバーヘッドをかけるのは、感心しません。</p>
|
||
<p>マクロの再定義ではパラメータ名も一致していなければならないという規定は、削除したほうが良いと思います。</p>
|
||
|
||
<h3><a name="2.7.5" href="#toc.2.7.5">2.7.5. 何を評価するのかわからない #if 式の文字定数</a></h3>
|
||
<p>#if 行の引数である #if 式は整数型の定数式ですが、その評価はプリプロセスで行われるので実行時環境から独立したものでなければなりません。そのため、一般の整数定数式から、実行時環境に対する問い合わせを必要とするキャスト、sizeof 演算子、列挙定数が除外されています(これらは translation phase 7 で初めて評価される)。しかし、文字定数(およびワイド文字定数)は除外されていません。</p>
|
||
<p>文字定数の評価は次のように何重にも処理系定義であり、portability はほとんどありません。</p>
|
||
<ol>
|
||
<li>基本文字の値さえも基本文字セット(ASCII, EBCDIC 等)によって異なる。<br>
|
||
<br>
|
||
<li>基本文字セットが同じであっても、single-character character con-stant さえも符号の扱いは処理系定義である(コンパイラ本体では char が符号なしか符号つきかによる)。<br>
|
||
<br>
|
||
<li>Multi-character character constant の評価は処理系定義であり、基本文字セットと符号の扱いが同じでも、値が同じとは限らない。<tt>CHAR_BIT</tt> が8で char が符号なしだとしても、'ab' が 'a' * 256 + 'b' となるのか、それとも 'a' + 'b' * 256 となるのかは、規定されていない。<br>
|
||
<br>
|
||
<li>Multi-byte character の encoding は処理系定義である。Wide char-acter の encoding は multi-byte character の encoding に従う。wchar_t のサイズも符号の有無も処理系定義である。<br>
|
||
<br>
|
||
<li>Multi-byte character の encoding が同じであっても、その値の評価は同じとは限らない。3と同じ問題がある。<br>
|
||
<br>
|
||
<li>以上はいずれもコンパイラ本体での文字定数の評価にも共通する問題であるが、さらにプリプロセスでの文字セットはコンパイラ本体と違っていてもよいことになっている。<br>
|
||
Translation phase 4 までが対象とするのはソース文字セットであり、phase 6 以降が対象とするのは実行時文字セットである。phase 5 が文字定数と文字列リテラル中の文字のソース文字セットから実行時文字セットへの変換を行う。すなわち、基本文字セットも、multi-byte character encoding も、ソースと実行時とで一方または双方が違っているかもしれない。<br>
|
||
#if 式の文字定数が評価されるのは phase 4 であるが、これはソース文字セットの値でも実行時文字セットの値をシミュレートしても、どちらでもよいことになっている。ソース文字セットとも決められていない。<br>
|
||
<br>
|
||
<li>#if で評価する文字セットと実行時文字セットが同じであっても、その評価のしかたは違っていてもよいことになっている。すなわち、符号の扱いも、multi-character character constant, multi-byte character constant の評価のバイトオーダーも、phase 4 と phase 7 とで違っているかもしれない。<br>
|
||
<br>
|
||
<li>しかも、文字定数の評価の型は、phase 7 では multi-byte character を含めて character constant は int、wide character constant は wchar_t であるのに対して、phase 4 では C90 では long または unsigned long である。すなわち、phase 4 では int は long と同じ内部表現を持つかのように扱われ、unsigned int は unsigned long と同じ内部表現を持つかのように扱われる。したがって、文字セット、符号の扱い、評価のバイトオーダーが phase 4 と 7 とでまったく同じであっても、<tt>INT_MAX</tt> < <tt>LONG_MAX</tt> の処理系では、phase 4 でオーバーフローしない文字定数が phase 7 ではオーバーフローすることもありうるし、phase 7 で int の負数になるものは phase 4 では long の正数になるかもしれず、正数になるか負数になるかさえも決まってはいない。文字定数でない整数定数トークンは負数になることはないが、文字定数については正負は一般的にはほとんど予測できない。<br>
|
||
<br>
|
||
<li>C99 では #if 式の型はその処理系の最大の整数型とされた。すなわち、評価の型も処理系によって異なるかもしれない。<br>
|
||
<br>
|
||
<li>さらにプリプロセスだけでなくコンパイルにも共通する問題であるが、multi-byte character には encoding の問題がある。たとえば UTF-8 は2バイトの unicode の文字を1バイトないし3バイトで encode するが、その文字定数の「値」とはいったい何であろうか? 漢字の値は UTF-8 の3バイトの sequence を評価した「値」であろうか、それとも元の unicode の「値」であろうか。これも処理系定義ということになるのであろうが、どういう仕様が合理的であるのかも判然としない。<br>
|
||
<br>
|
||
<li>これもコンパイルと共通する問題であるが、C99, C++98 では UCN というものまで導入された。同じ文字を UCN で表記したものと multi-byte character として書いたものとは同じ「文字」であろうか。本来は同じ文字のはずであるが、その「値」は multi-byte character の encoding によってそれぞれ違ってくるであろう。<br>
|
||
</ol>
|
||
<p>こういうことで、#if 式の文字定数の値は処理系間での portability もなければ、同じ処理系のコンパイルフェーズとの間でも違うかもしれないところが多く、どう評価されるかほとんど予測できません。</p>
|
||
<p>一般にはC言語の整数型の規定にはあいまいな部分は少なく、演算に関しては負数の扱いが処理系定義であるものの、それは CPU しだいで決まるものであり、処理系作成者が任意に決めることのできる部分はあまりないのですが、文字定数の評価だけが例外です。これは CPU の仕様、基本文字セット、multi-byte character encoding というシステム側の要因で決まる側面のほかに、処理系作成者の裁量にまかされている面が多くあります。</p>
|
||
<p>これが #if 式の文字定数となると、処理系の裁量の範囲がさらに大幅に増え、コンパイルフェーズとの一致は保証されず、これを評価してもいったい何を評価したのかほとんどわかりません。文字定数の評価は本来は実行時環境への問い合わせを必要とするものだと考えられます。Standard C のプリプロセスではこの問い合わせを必要とする処理は除外されたにもかかわらず、なぜか文字定数だけは除外されませんでした。そして、問い合わせをしないでもすむ規定を無理に作ったために、意味不明なものとなってしまったと思われます。</p>
|
||
<p>こういう #if 式の文字定数にどんな使い道があるのでしょうか? コンパイルフェーズでは char 型変数の値を文字定数と比較することがよく行われますが、変数が使えないプリプロセスフェーズではその使い道もありません。#if 式の文字定数の使い道としては、私には適切な例は思い付きません。これは無用の長物であり、キャストや sizeof と同様に、#if 式の対象から除外すべきでしょう。除外しても、キャストや sizeof が除外されたのに比べれば、困るソースははるかに少ないはずです。</p>
|
||
|
||
<h3><a name="2.7.6" href="#toc.2.7.6">2.7.6. マクロ再走査の関数様でない規定</a></h3>
|
||
<p>マクロ呼び出しはいったん置換リストに置き換えられた後、それがさらに再走査されますが、Standard C の再走査の規定できわめて汚いのは、そのマクロ呼び出しの後ろのトークン列があたかも置換リストの続きを成しているかのように、置換リストと連続して走査されることです。これは関数呼び出しをモデルとした関数様マクロの規定の原則からまったく逸脱しており、マクロ展開をわかりにくくする最大の要因となっています。私は、後続のトークン列も再走査の対象とするというこの規定は削除し、再走査の対象は置換リストだけに限定すべきだと考えています。</p>
|
||
<p>実は後続のトークン列も再走査の対象とするのは、おそらく K&R 1st. のころからの長年の暗黙の仕様だったと思われます。Standard C ではこの仕様は必要なくなったはずなのですが、しかし盲腸のように生き残ってしまっているのです。これはマクロ展開の根幹にかかわる問題であるので、以下に詳細に検討することにします。</p>
|
||
<p>マクロ rescan(再走査)の方法を文章で正確に記述するのは、容易なことではありません。規格書の文章も、K&R 2nd. 等の文章もわかりやすいものではありません。たとえば、K&R 2nd. A.12 には「置換リストは繰り返し再走査される」とあります。しかし、規格書には繰り返すとは書かれていません。Rescan は1回だけ行われるようにも読めます。再帰的に行われるようにも読めますが、そう明記されているわけではありません。</p>
|
||
<p>これは実例を使わないと、正確に説明することができないのではないかと思われます。さらに、実装方法も説明しないと、直観的に理解できるようにはならないでしょう。そのくらい、マクロの rescanning というものはマクロ展開の伝統的な実装方法に密着したものなのです。</p>
|
||
<p>まず、次のバカげた例を検討してみます。問題を単純にするために、この x, y はマクロではないとします。このマクロ呼び出しはどう展開されるのでしょうか?</p>
|
||
<pre style="color:navy">
|
||
#define FUNC1( a, b) ((a) + (b))
|
||
#define FUNC2( a, b) FUNC1 OP_LPA a OP_CMA b OP_RPA
|
||
#define OP_LPA (
|
||
#define OP_RPA )
|
||
#define OP_CMA ,
|
||
|
||
FUNC2( x, y)
|
||
|
||
1: FUNC2( x, y)
|
||
2: FUNC1 OP_LPA x OP_CMA y OP_RPA
|
||
3: FUNC1 ( x , y )
|
||
</pre>
|
||
<p>1: が 2: に置換され、2: の rescan によって 3: が生成されることは、すぐわかります。では、3: はマクロ呼び出しでしょうか? すなわち、これは先頭からもう一度 rescan されるべきでしょうか?</p>
|
||
<p>Rescan は先頭から何度でも繰り返されるものなのでしょうか、それとも対象範囲が再帰的にしだいに狭められてゆくものなのでしょうか? 実は、そのどちらでもないのです。</p>
|
||
<p>Rescan というものの実際は伝統的にある種の変則的な再帰で、あるいはそれと同じ結果になるある種の繰り返しで行われてきたと思われますが、その古典的な例が Kernighan & Plauger "Software Tools"(「ソフトウェア作法」)の Macro Processing の章に載っているものです。これは後に M4 マクロプロセッサに発展してゆくもので、これ自体はCプリプロセッサではありませんが、Ritchie がCで書いたマクロプロセッサから「盗んだ」ものだと書いてあり、Cプリプロセッサの実装方法の原型をうかがい知ることができます。</p>
|
||
<p>このマクロプロセッサでは、マクロ呼び出しがあるとその置換リストを入力に送り返して読み直すという方法で rescanning を実現しています。置換リスト中にまたマクロ呼び出しがあった時は、「それが元の入力にあったかのように」その新たなマクロの置換リストを同じように送り返して読み直します。「これはマクロの置換テキストを再走査するエレガントな方法を提供する」「入力の入れ子構造に内在する再帰を、われわれはこの手で処理しようというのである」等とあるように、この方法はマクロプロセッサのプログラムを構造化しわかりやすくするために、大いに役立っています。</p>
|
||
<p>Cプリプロセッサの多くもこれと同じように、疑似的な入力、すなわちある種のスタックに置換リストを積んで、それを読み直すという方法でマクロの re-scan をしているものと思われます。</p>
|
||
<p>上記の例では、2: を rescan する時に、FUNC1 がこの時点ではマクロ呼び出しでないことがわかると、同時にこのトークンが確定し、以後の置換は繰り返しにしろ再帰にしろ OP_LPA 以降が対象となります。OP_LPA が ( に置換されて、それがマクロでないことがわかると、次は x 以降が対象となる、というふうにして先頭から順次、確定してゆき、3: が最終結果となります。これはもはやマクロ呼び出しではありません。</p>
|
||
<p>"Software Tools" 以来の(あるいはそれ以前からの)この方法は確かに re-scan の簡明な実装方法です。しかし、"Software Tools" は触れてはいませんが、そこにまた落とし穴もあります。問題は、入力に送り返された置換リストはソースと連続して読み込まれるために、rescan が置換リストを越えて元のマクロ呼び出しの後ろの部分までスキャンしてしまう可能性があることです。ネストされたマクロでは、rescan しているうちにいつの間にかネストレベルがズレてきてしまうこともありえます。引数つきマクロの名前に展開される引数なしマクロや、置換リストが別の引数つきマクロの呼び出しの前半部分を構成するという変態マクロが、この事態をひき起こします。</p>
|
||
<pre style="color:navy">
|
||
#define add( x, y) ((x) + (y))
|
||
#define head add(
|
||
|
||
head a, b)
|
||
</pre>
|
||
<p>これがその例です。この奇妙なマクロ呼び出しは ((a) + (b)) と展開されます。あろうことか、Standard C ではこれが公式に認知されてしまいました。つまり、このマクロは undefined どころか合法的なものなのです。</p>
|
||
<p>本来、Cプリプロセッサはまさかこんな変態マクロの展開を意図していたとは思えません。しかし、たぶん原初のCプリプロセッサの実装が上記のようなものであったために、こうしたマクロを黙ってそれらしく展開する結果となり、この穴を意識的に利用するプログラムまで現れ、これが事実上の標準仕様となってしまい、ついに Standard C でもこれを追認することになったのではないでしょうか。すなわち、原初のCプリプロセッサの実装のささいな欠陥が、奇妙な de facto standard を誘導し、Standard C にまで尾を引いているのです。これが盲腸の盲腸たるゆえんです。</p>
|
||
<p>さて、rescan は再帰か繰り返しかという話に戻ると、これは変則的な再帰であり、繰り返しと言ってもあながち間違いではないものである、ということになるかと思います。再帰は再帰ですが、普通の再帰のように対象範囲がしだいに狭められてゆくとは限らず、むしろ対象範囲がしだいに後ろにズレてゆくという奇妙な性質をもった再帰なのです。これは再帰でなくても繰り返しで実現することもできます。ただし、先頭からの繰り返しではなく、途中からの繰り返しで、しだいに後ろの部分を取り込んでゆくものです。</p>
|
||
<p>したがって、ソースファイル中のコメントやプリプロセスディレクティブがすべて処理された後のテキストを考えると、このズレズレ rescan だけでテキストの最初から最後まで通して処理してしまうことも可能です。実際、"Software Tools" ではそういう方法をとっており、現在のCプリプロセッサのソースにも似たやり方をしているものがあります。すなわち、rescan とはマクロの展開(expand)と同義であり、それはまたテキスト全体のマクロの展開とも同義なのです。</p>
|
||
<p>Rescan の対象がしだいに後ろにズレてゆくことは、多くの問題を引き起こします。次の例はどう展開されるべきかが不明確なマクロの例として C89 Rationale 3.8.3.4. (C99 Rationale 6.10.3.4) に載っているものですが、この処理が規定されなかったのは「プリプロセスのこういう quirks(気まぐれ、奇癖)までいちいち規定しても何の役にも立たないからだ」とされています。しかし、この例は示唆的です。これはむしろ規定することができなかったのです。</p>
|
||
<pre style="color:navy">
|
||
#define f(a) a*g
|
||
#define g(a) f(a)
|
||
|
||
f(2)(9)
|
||
</pre>
|
||
<p>この例では、まず f(2) が 2*g に置換されます。「後続するトークン列」を rescan の対象としなければ、マクロ展開はこれでおしまいで、f(2)(9) は 2*g(9) というトークン列となります。ところが「後続するトークン列」も対象とされるために、この g(9) がマクロ呼び出しを形成してしまい、f(9) に置換されます。ここで、この f(9) はさらに 9*g と置換されるべきか、それとも同名マクロの再置換禁止の規定を適用して置換しないでおくべきかが不明確です。f(9) というトークン列は f(2) の最初の置換結果の後尾の g と「後続するトークン列」である (9) のつながったものの rescan によって生成されたものであるので、これが f(2) の呼び出しのネストの中にあるのか外にあるのかが判然としないからです。</p>
|
||
<p>この問題については C90 Corrigendum 1 で訂正が行われました。Annex G.2 Undefined behavior に次の例を追加するというのです。</p>
|
||
<p>-- A fully expanded macro replacement list contains a function-like macro name as its last preprocessing token (6.8.3).<br>
|
||
-- 完全に展開されたマクロ置換リストの最後の前処理字句が関数形式マクロの名前である場合 (6.8.3)。</p>
|
||
<p>しかし、この訂正は混乱に輪をかけるものでしかありません。</p>
|
||
<p>まず、"fully expanded macro replacement list" という文言が意味不明です。これは「引数があれば引数中のマクロが展開された後の置換リスト」と解釈するしかありませんが、そうすると f(2)(9) の例では、同名マクロの再置換を云々する前に、f(2) が 2*g に置換され、それを再走査して g が function-like マクロの名前であるとわかったところですでに undefined となります。すなわち、この f と g のマクロ定義では、f の呼び出しがあると必ず undefined となるのです。</p>
|
||
<p>この「訂正」を適用すると、ISO/IEC 9899:1990 6.8.3 Examples にある次のようなマクロ再走査の例示が、そもそも undefined となってしまいます。</p>
|
||
<pre style="color:navy">
|
||
#define f(a) f(x * (a))
|
||
#define x 2
|
||
#define g f
|
||
#define w 0,1
|
||
#define t(a) a
|
||
|
||
t(t(g)(0) + t)(1); /* f(2 * (0)) + t(1); */
|
||
g(x+(3,4)-w) /* f(2 * (2+(3,4)-0,1)) */
|
||
</pre>
|
||
<p>規格書はこれらのマクロ呼び出しはコメントに書いたように展開されるとしていますが、Corrigendum を適用するとそうはなりません。この f と g のマクロ定義では、g という identifier が出てくると必ず undefined となります。g の置換リストでは function-like マクロの名前である f が唯一で最後の pp-token ですから。</p>
|
||
<pre style="color:navy">
|
||
t(t(g)(0) + t)(1)
|
||
</pre>
|
||
<p>これはまず、初めの t の呼び出しの引数が展開されます。</p>
|
||
<pre style="color:navy">
|
||
t(g)(0) + t
|
||
</pre>
|
||
<p>ここにまた t(g) というマクロ呼び出しがあるので、これが展開されますが、そのためにはまず引数を展開しなければなりません。</p>
|
||
<pre style="color:navy">
|
||
g
|
||
</pre>
|
||
<p>そしてこれが f に置換されると、ここで undefined となるのです。</p>
|
||
<p>もしこれをこのままにして、さらに置換を続けても、</p>
|
||
<pre style="color:navy">
|
||
t(f)
|
||
f
|
||
</pre>
|
||
<p>となり、t(f) の展開結果の最後の pp-token が f であるので、再び undefined です。さらに置換を続けると、こうなります。</p>
|
||
<pre style="color:navy">
|
||
f(0) + t
|
||
f(x * (0))
|
||
f(2 * (0))
|
||
f(2 * (0)) + t
|
||
t(f(2 * (0)) + t)
|
||
f(2 * (0)) + t
|
||
</pre>
|
||
<p>これで初めの t の呼び出しの展開が一応終わりますが、この置換リストの最後がやはり t という function-like マクロの名前であるので、三たび undefined となります。</p>
|
||
<p>では、次はどうでしょうか。</p>
|
||
<pre style="color:navy">
|
||
g(x+(3,4)-w)
|
||
</pre>
|
||
<p>これも g が f に置換されたところですでに undefined です。</p>
|
||
<p>Examples と G.2 とが矛盾するという混乱に陥ってしまっています。
|
||
</p>
|
||
<p>もし Examples のこれらの例が取り消されたとしても、この Corrigendum の訂正では決して混乱は解消しません。第一に、G.2 は規格本文ではなく、この追加は本文中に根拠を持たないものです。本文では「後続するトークン列」も rescan の対象とするとしか書いてないのです。第二に、もしこの Corrigendum を本文中に取り込んだとしても、先の例の</p>
|
||
<pre style="color:navy">
|
||
#define head add(
|
||
</pre>
|
||
<p>は add が置換リストの最後ではないので正しく</p>
|
||
<pre style="color:navy">
|
||
#define head add
|
||
</pre>
|
||
<p>は undefined というのでは、あまりにもアンバランスです。"fully expanded" という文言が意味不明であるという問題もあります。*1, *2</p>
|
||
<p>これが、「後続するトークン列」も rescan の対象とするという規定によってもたらされた quirks であることは、言うまでもありません。つじつまを合わせようとすればするほど、混乱してしまうのです。規格書には同名マクロ再置換禁止の規定がきわめて難解な文章で書かれていますが、この難解さの一因もここにあります。</p>
|
||
<p>他方で Standard C は function-like マクロの呼び出しに際しては、引数中のマクロの展開はその引数の中だけで行うと規定しています。引数中のマクロの展開がその後ろのテキストまで食ってしまったのでは大混乱ですから、この規定は当然と言えば当然です。</p>
|
||
<p>しかし、この結果、同じマクロでも引数中にある時とそうでない時とで違った結果になるというアンバランスが発生します。</p>
|
||
<pre style="color:navy">
|
||
#define add( x, y) ((x) + (y))
|
||
#define head add(
|
||
#define quirk1( w, x, y) w x, y)
|
||
#define quirk2( x, y) head x, y)
|
||
|
||
head a, b);
|
||
quirk1( head, a, b);
|
||
quirk2( a, b);
|
||
</pre>
|
||
<p>この quirk1() の呼び出しでは、第1引数である head が add( に置換された後、その rescan で完結しないマクロ呼び出しとして violation of constraint、平たく言えばエラーとなるはずです。しかし、quirk2() および head a, b) はエラーにならずに</p>
|
||
<pre style="color:navy">
|
||
((a) + (b))
|
||
</pre>
|
||
<p>と展開されることになります。</p>
|
||
<p>しつこいようですが、こうしたバカげたことはすべて、一般にマクロの re- scan で「後続のトークン列」までが対象となってしまうことから発生しているのです。実装上は、引数の展開が他のテキスト部分から独立して行われるためには、たとえ置換リストを入力に送り返す方法であっても、どのネストレベルであるかという情報を付加する必要があります。それを使えば一般にも「後続のトークン列」を rescan の対象に取り込まないようにすることは容易に実現できるはずです。むしろ、現在の中途半端な規定では、引数中とそうでない場合とで処理を変える必要があり、実装にも余計な負担をかける結果になっています。</p>
|
||
<p>Cのマクロ展開は伝統的にエディタ様の文字列置換の痕跡をとどめてきました。エディタ様の文字列置換が極端に高機能化・複雑化したのが pre-Standard のマクロ展開だと言えるでしょう。</p>
|
||
<p>これに対して Standard C では、引数つきマクロにわざわざ function-like マクロという名前まで付けて、その呼び出しの構文を関数呼び出しに近付けようとしたと考えられます。引数中のマクロは完全に展開してからパラメータと置き換えるという規定も、その展開は引数の中だけで行われるという規定も、この原則に合ったものです。ところが、一般にマクロの rescan は後続のトークン列を取り込むという規定が、この原則をブチコワシています。テキスト置換の繰り返しという先祖の遺物です。</p>
|
||
<p>後続のトークン列を rescan の対象から除外してしまえば、マクロ展開は完全に再帰的に、すなわち対象範囲が前方にも後方にも再帰のたびに狭められてゆくように(少なくとも広げられることはないように)規定することができたのです。そして、function-like マクロはその名にふさわしいマクロとして、明快なものになったことでしょう。そう決められては困るというソースはめったにあるとは思われないので、そうならなかったのは単に ANSI C 委員会が、先祖から受け継いだ盲腸を切り落とす決断がつかなかったからとしか考えられません。*3</p>
|
||
<p>C99 ではこれをスッパリと切り落としてもらいたかったのですが、またもやこの盲腸は生き残ってしまいました。</p>
|
||
<p>注:</p>
|
||
<p>*1 後者のような function-like マクロの名前に展開される object-like マクロというのは、実際のプログラムでも時々目にするものである。次のようなものである。</p>
|
||
<pre style="color:navy">
|
||
#define add( x, y) ((x) + (y))
|
||
#define sub( x, y) ((x) - (y))
|
||
#define OP add
|
||
OP( x, y);
|
||
</pre>
|
||
<p>これは前者のような function-like マクロの呼び出しの前半部分に展開されるものほど変態的なものではないが、こうしなければならない理由は何もない。Function-like マクロのネストは次のように function-like マクロでするのが良い。</p>
|
||
<pre style="color:navy">
|
||
#define OP( x, y) add( x, y)
|
||
</pre>
|
||
<p>*2 この Corrigendum による正誤訂正が出てきた理由は、C90 ISO C 委員会(SC 22 / WG 14)の "Record of Responses to Defect Reports" という文書でわかる(#017 / Question 19)。ANSI C Rationale 3.8.3.4 の f(2)(9) のマクロ展開に関する議論がここで蒸し返されたのである。この例の直接の問題は、「同名マクロ再置換禁止」の規定の適用範囲だったはずであるが、委員会は同名マクロに限らない一般的な問題として回答してしまったのである。しかし、この解釈が Examples との間に矛盾を発生することには気が付かなかったようである。</p>
|
||
<p>さらに、この "fully expanded" という文言が奇妙である。f(2) が 2*g に置換され、g まで再走査された時、これは fully expanded なのであろうか?</p>
|
||
<p>もしそうなら、それ以上の置換は起こらないから、undefined にもならないはずである。もしまだ fully expanded でないなら、g が後続の (9) とともに再走査され、f(9) に置換される。もしこれが fully expanded であるなら、2*f(9) の最後の pp-token は function-like マクロの名前ではないから、この回答はあてはまらない。</p>
|
||
<p>すなわち、マクロ展開はいつ終わるのかが問題となっているところで、「マクロ展開が終わったら」と言っているのである。これは循環論法である。かくして、マクロ展開はいつ終わるのか、ますますわからなくなってしまったのである。</p>
|
||
<p>C99 1997/11 draft では Corrigendum のこの項目が Annex K.2 Undefined behavior に取り込まれていたが、1998/08 draft では削除されて、替わって Annex J.1 Unspecified behavior に次のような一節が追加された。そして、結局これが C99 に採用された。</p>
|
||
<blockquote>
|
||
<p>When a fully expanded macro replacement list contains a function-like macro name as its last preprocessing token and the next preprocessing token from the source file is a (, and the fully expanded replacement of that macro ends with the name of the first macro and the next preprocessing token from the source file is again a (, whether that is considered a nested replacement.</p>
|
||
</blockquote>
|
||
<p>委員会はようやく Corrigendum の矛盾に気づいたようである。しかし、規格本文の根本的な問題はそのままである。また、マクロ展開がいつ終わるのかも、結局は unspecified である。しかも、'(' がソース中にあるかどうかで区別するというのでは、同じマクロがソース中にある時と他のマクロの置換リスト中にある時とで結果が違ってくるということであり、一貫しない仕様である。</p>
|
||
<p>この問題については、<a href="#3.4.26">3.4.26</a> も参照のこと。
|
||
</p>
|
||
<p>なお、C++ Standard ではマクロ展開に関する規定は C90 と同じであり、C90 の Corrigendum 1 に相当するものも C99 の Annex J.1 で追加された規定もない。</p>
|
||
<p>*3 こう決めても、先の FUNC2( x, y) はもしこれが他のマクロ呼び出しの引数中にあった場合は、引数の展開で FUNC1 ( x, y) となり、さらに元のマクロの rescan で ((x) + (y)) と展開されることになる。すなわち、引数中にある場合とそうでない場合とで最終的な展開結果が異なる。しかし、これはまた別の次元の問題であり、不都合ではないであろう。</p>
|
||
|
||
<h3><a name="2.7.7" href="#toc.2.7.7">2.7.7. C90 Corrigendum 1, 2, Amendment 1 での追加</a></h3>
|
||
<p>ISO/IEC 9899:1990 については、1994 に Corrigendum 1(正誤訂正)が、1995 に Amendment 1(追補)が出され、さらに 1996 に Corrigendum 2 が出されました。</p>
|
||
<p>Corrigendum 1 のほうはささいな文言の訂正がほとんどですが、2つだけプリプロセスに影響を与えるものが含まれています。その1つは前記 <a href="2.7.6">2.7.6</a> のマクロの再走査に関するものです。</p>
|
||
<p>もう1つはマクロ定義中のマクロ名に $ 等が含まれていた場合についての、きめわて特殊な規定です。</p>
|
||
<p>Standard C では identifier 中の文字として $ は認めていませんが、これを認める処理系も伝統的に存在しています。test-t/e_18_4.t にある 18.9 の例は、Standard C では $ が1文字で1つの pp-token と解釈されるので、マクロ名は THIS で $ 以降が object-like マクロの置換リストとなり、THIS$AND$THAT という名前の function-like マクロというプログラムの意図とはまったく違った結果になります。</p>
|
||
<p>Corrigendum 1 では、こうした例に関して例外的規定が追加されました。すなわち、「object-like マクロの置換リストが non-basic character で始まる場合は、マクロ名と置換リストとは white-space で分離されていなければならない」というものです。この 18.9 の例に対しては Standard C は診断メッセージを出さなければならないのです。それによって、$ や @ がマクロ名中に使われているソースが黙って意図しない結果にプリプロセスされる事態を防ごうというわけです。苦心の規定ですが、こうした例外が増えるのは困ったものです。$ や @ を identifier 中に認めない処理系では、こうしたマクロはたとえプリプロセスでエラーとならなくても、コンパイルフェーズで必ずエラーになるはずなので、この例外規定の必要はないと思われます。*1</p>
|
||
<p>このほか、ISO 9899:1990 では header-name という pp-token は #include directive にしか現れてはならないという意味不明な constraint がありましたが、Corrigendum 1 で、header-name は #include directive でしか認識されないと訂正されました。</p>
|
||
<p>Amendment 1 は multi-byte character, wide character およびそれらの文字列を操作するライブラリ関数の追加が中心で、それに伴って <wchar.h>, <wctype.h> という標準ヘッダが追加されました。また、ISO 646 の文字セットに含まれない文字やそれを使った token, pp-token を表記する方法として、trigraph と並ぶもう1つの選択肢として <iso646.h> という標準ヘッダと digraph の規定が追加されました。<iso646.h> はいくつかの operator をマクロで定義するごく簡単なヘッダで、特に問題はありません。*2</p>
|
||
<p>問題は digraph です。これは trigraph とよく似ていて、用途はほとんど同じなのですが、プリプロセスでの位置づけはまったく違っています。Trigraph は character であり、translation phase 1 で通常の character に変換されるのに対して、digraph は token, pp-token なのです。Digraph sequence が # 演算子によって文字列化される場合は、変換せずにそのまま文字列化しなければなりません(この # そのものも digraph では %: と書くのであるが)。そのため、そしてそれだけのために、処理系は少なくとも phase 4 が終わるまではこれを pp-token として保っている必要があります。変換するとすれば、その後です(文字列リテラル中の digraph ではなく、token として残った digraph sequence を変換する)。</p>
|
||
<p>これは処理系に無用の負担をかけるものです。Trigraph と同様に character として認識し、phase 1 で変換してしまうほうが、実装が簡明になります。これを pp-token として保つ利点は何もありません。Amendment も、digraph と通常の token との違いは文字列化される時にしか発生しないと注記しています。Phase 1 で変換するようにすると "%:" といった文字列リテラルを書くのに困るという見方があるかもしれませんが、それは trigraph でも同様であり、取り上げるにはあまりにも特殊な問題です。どうしても書きたければ "%" ":" とすればすみます。Digraph は trigraph の alternative として phase 1 で変換するように位置づけしなおすべきでしょう。</p>
|
||
<p>注:</p>
|
||
<p>*1 C99, C++ Standard ではこの規定は消えている。</p>
|
||
<p>C99 ではそのかわりに、6.10.3 Macro replacement / Constraints に次の一般化した規定が付け加えられた。</p>
|
||
<blockquote>
|
||
<p>There shall be white-space between the identifier and the replacement list in the definition of an object-like macro.</p>
|
||
</blockquote>
|
||
<p>これも tokenization の例外規定であるので、感心しない。</p>
|
||
<p>*2 C++ Standard では、これらの identifier 様 operator はマクロではなく token である。なぜそうしたのか理解しがたいが(プリプロセスすべきことを極力減らすという発想か?)、とにかく処理系にとってはやっかいなことである。</p>
|
||
|
||
<h3><a name="2.7.8" href="#toc.2.7.8">2.7.8. 冗長な規定</a></h3>
|
||
<p>C90 5.1.1.2, C99 5.1.1.2 Translation phases(翻訳フェーズ)の 3 には、実害はないものの冗長な規定があります。</p>
|
||
<blockquote>A source file shall not end in a partial preprocessing token or comment.</blockquote>
|
||
<p>Translation phase 2 で、ソースファイルは <newline> 無しでまたは <backslash> <newline> で終わってはならないとあるので、phase 2 を通ったソースファイルは必ず <backslash> のない <newline> で終わります。Partial preprocessing token で終わることは決してありません。Partial preprocessing token に類したものとしては、論理行内で " や ' や <, > の対応のとれていないものがありますが、それは C90 6.1 Lexical Elements(字句要素)/ Semantics(意味規則)で undefined とされているものであり、またソースの末尾に限った問題ではありません。"partial preprocessing token or" は不要な文言です。</p>
|
||
<p>C90 6.8.1, C99 6.10.1 Conditional inclusion(条件付き取込み)/ Constraints(制約)には誤解を招く表現があります。</p>
|
||
<blockquote>it shall not contain a cast; identifiers (including those lexically identical to keywords) are interpreted as described below;</blockquote>
|
||
<p>この "it shall not contain a cast; " は蛇足です。それに続く部分と Semantics(意味規則)で、keyword と同じ identifier を含むすべての identifier はマクロであれば展開され、残った identifier は 0 と評価されることが明らかにされています。Cast をそれと並列して別に取り上げる必要はありません。(type) という構文では type は単なる identifier として扱われることが明らかです。</p>
|
||
<p>逆に、これが constraint にあると、処理系は cast の構文を認識して、それに対する診断メッセージを出さなければならないとも、解釈されます。それは規格書の意図ではないでしょう。Translation phase 4 では keyword は存在しないので、cast は認識のしようがないのです。sizeof もこの点では同じですが、ここで sizeof に言及せず、cast だけ取り上げているのも奇妙なことです。こういう文言を「蛇足」と言います。</p>
|
||
<br>
|
||
|
||
<h2><a name="2.8" href="#toc.2.8">2.8. C99 のプリプロセス規定</a></h2>
|
||
<p>C99 では、プリプロセスに関しては次の仕様が追加されています。</p>
|
||
<ol>
|
||
<li>識別子、文字列リテラル、文字定数、pp-number の中の \uxxxx, \Uxxxxxxxx の形の 16 進 sequence は UCN (universal-character-name) と言い、Unicode の文字の値を意味する。これは basic source character set に含まれない extended character を指定するものでなければならない。# 演算子によって UCN が文字列化された時に \ を重ねるかどうかは implementation-defined とする。<br>
|
||
<br>
|
||
<li>識別子中に implementation-defined な文字を使うことができる。したがって、漢字のような multi-byte-characters を識別子中に使える imple-mentation も可能となった。<br>
|
||
<br>
|
||
<li>// から行末までをコメントとして扱う。<br>
|
||
<br>
|
||
<li>Pp-number の中に e+, E+, e-, E- と同様に p+, P+, p-, P- という sequence も認める。これは 0x1.FFFFFEp+128 というふうに、浮動小数点数のビットパターンを16進で表記するためのものである。<br>
|
||
<br>
|
||
<li>#if 式の型はその処理系の最大の整数型とする。long long / unsigned long long は必須であるので、#if 式の型は long long またはそれ以上のサイズとなる。<br>
|
||
<br>
|
||
<li>引数が可変個のマクロが使える。<br>
|
||
<br>
|
||
<li>マクロ呼び出しのカラ引数は有効な引数とする。<br>
|
||
<br>
|
||
<li>事前定義マクロ <tt>__STDC_HOSTED__</tt> を追加する。これは hosted implementation であれば 1 に、そうでなければ 0 に定義される。事前定義マクロ <tt>__STDC_VERSION__</tt> は 199901L に定義する。<br>
|
||
<br>
|
||
<li>事前定義マクロ <tt>__STDC_ISO_10646__</tt>, <tt>__STDC_IEC_559__</tt>, <tt>__STDC_IEC_559_COMPLEX__</tt> をオプションとして追加する。<br>
|
||
<br>
|
||
<li>_Pragma operator を新設する。<br>
|
||
<br>
|
||
<li>#pragma STDC で始まるディレクティブ名を規格と処理系用に予約し、浮動小数点演算の方式を表す3つの #pragma STDC ディレクティブを追加する。#pragma STDC で始まるディレクティブはマクロ展開の対象としないが、そうでない #pragma 行はマクロ展開の対象とするかどうかは implementation-defined である。<br>
|
||
<br>
|
||
<li>Wide-character-string-literal と character-string-literal とが隣接しているのは C90 では undefined であったが、これは Wide-character-string-literal として連結する。<br>
|
||
<br>
|
||
<li>#line の引数として使える行番号の範囲は [1,2147483647] に拡大する。<br>
|
||
<br>
|
||
<li>Translation limits を次のように引き上げる。<br>
|
||
<blockquote>
|
||
<table>
|
||
<tr><th>ソースの論理行の長さ </th><td>4095 バイト</td></tr>
|
||
<tr><th>文字列リテラル、文字定数、header name の長さ</th><td>4095 バイト</td></tr>
|
||
<tr><th>内部 identifier の長さ </th><td>63 文字</td></tr>
|
||
<tr><th>#include のネスト </th><td>15 レベル</td></tr>
|
||
<tr><th>#if, #ifdef, #ifndef のネスト </th><td>63 レベル</td></tr>
|
||
<tr><th>式のカッコのネスト </th><td>63 レベル</td></tr>
|
||
<tr><th>マクロのパラメータの数 </th><td>127 個</td></tr>
|
||
<tr><th>定義できるマクロの数 </th><td>4095 個</td></tr>
|
||
</table>
|
||
</blockquote>
|
||
<li>Header name は6文字 + . + 1文字までが保証されていたが、これを8文字 + . + 1文字までに変更する。<br>
|
||
</ol>
|
||
<p>可変引数マクロというのは、次のようなものです。</p>
|
||
<pre style="color:navy">
|
||
#define debug(...) fprintf(stderr, __VA_ARGS__)
|
||
</pre>
|
||
<p>というマクロ定義があると、
|
||
</p>
|
||
<pre style="color:navy">
|
||
debug( "X = %d\n", x);
|
||
</pre>
|
||
<p>というマクロ呼び出しは次のように展開されます。
|
||
</p>
|
||
<pre style="color:navy">
|
||
fprintf(stderr, "X = %d\n", x);
|
||
</pre>
|
||
<p>すなわち、パラメータ・リスト中の ... が1個以上のパラメータを意味し、置換リスト中の __VA_ARGS__ がそれに対応します。そして、マクロ呼び出し時には ... に対応する引数が複数あっても、それらを , を含めて連結したものが一つの引数のように扱われます。</p>
|
||
<p>C90 で undefined behavior とされているものの中には、十分意味のある解釈の可能なものがあります。マクロ呼び出しのカラ引数などがそうで、これを 0 個の pp-token と解釈することが有用な場合があります。C99 では、これが有効な引数とされました。</p>
|
||
<p>_Pragma( "foo bar") と書くと #pragma foo bar に変換される _Pragma という拡張演算子が C99 で取り上げられています。#pragma 行の引数は C90 ではマクロ展開されず、マクロ展開の結果として生じた #pragma ディレクティブ類似の行はディレクティブとして扱われず、マクロ定義の置換リスト中に #pragma を書くことができないのに対して、_Pragma 式はマクロの置換リスト中に書くことができ、その結果として生じた #pragma はディレクティブとして扱うという拡張を施すことで、扱いにくい #pragma の portability を向上させようというものです。</p>
|
||
<p>こうした変則的な拡張をしなくても、#pragma 行の引数はマクロ展開の対象とするという変更を加えるほうが簡明であり、それで portability 向上の意図はかなり達成できるはずですが、その場合はマクロの中に #pragma を書くことができないという制約が残り、マクロ展開されてはいけない #pragma の引数はユーザ用名前空間と切り離すため __ で始まる名前に変更しなければならないという問題が出てきます。_Pragma() operator は変則的ではあるものの、実装が面倒というほどでもなく、妥当な仕様かと思われます。</p>
|
||
<p>Unicode の導入には問題がありすぎます。まず、処理系は multi-byte character と Unicode との変換のために巨大な表を用意しなければならず、大きな overhead を生じます。16 ビット以下のシステムでは事実上、実装不可能です。Unicode を扱っていないシステムも存在します。また、Unicode と multi-byte character とでは一対一に mapping できない場合が多々あります。プログラム言語の国際化という名目で Unicode をC言語の標準の地位に置くのは、強引すぎると思われます。</p>
|
||
<p>C99 では 1997/11 draft や C++ Standard に比べて UCN の扱いが大幅に後退し、プリプロセッサの負担は比較的小さくなりました。そのため、<b>mcpp</b> でも一応の実装が可能になりました。*1</p>
|
||
<p>しかし、コンパイラ本体の負担は依然、大きいものがあります。また、可読性のない表記であるので、trigraph と同様に、あまり使われないで終わることも予想されます。*2</p>
|
||
<p>注:</p>
|
||
<p>*1 1997/11 draft では C++ Standard とほぼ同様に、translation phase 1 で basic source character set に含まれない extended character はすべて UCN に変換し、phase 5 で execution character set の文字に再変換することになっていた。</p>
|
||
<p>もしこれを実装する場合は、プリプロセスの前と後にこれらの変換をするツールを呼び出すということになると思われる。この変換は OS に依存するものなので、別のツールにするほうが現実的である。</p>
|
||
<p>*2 C99 Rationale 5.2.1 Character sets によると、この仕様は処理系付属のツールで multi-byte character のソースとの間で相互に変換して使うことを想定したものとされている。Multi-byte character の文字列リテラルの部分を切り離して別ファイルにまとめておいて処理するということであろう。どこまで実用になるものであろうか。</p>
|
||
<br>
|
||
|
||
<h2><a name="2.9" href="#toc.2.9">2.9. 明快なプリプロセス規定を</a></h2>
|
||
<p>以上に述べてきた Standard C プリプロセス規定の問題点と私が考えるものは、そのまま将来の Standard C への要望でもあります。まとめると、次のようなことになります。</p>
|
||
<ol>
|
||
<li><stdio.h> の形式の header-name は obsolescent feature(廃止予定機能)とする。次々期では header-name は文字列リテラル形式のものだけにする。<br>
|
||
<li>Token-based なプリプロセスの原則を貫き、# 演算子は引数中の token separator の有無に左右されないように、token separator がなくても pp-token 間にはすべて single space を挿入したうえで文字列化することにする。<br>
|
||
<li>同様に、マクロの再定義に際しては、置換リスト中の token separator の有無の違いは問題にしないことにする。<br>
|
||
<li>マクロの再定義に際してパラメータ名の違いをチェックすることは、処理系のオーバーヘッドを増やすだけでほとんど有用性がないので、この違いは問題にしないことにする。<br>
|
||
<li>文字定数の評価は本来は実行時環境への問い合わせを必要とするものであり、#if 式では有用性もほとんどないので、これは #if 式の対象から除外する。<br>
|
||
<li>関数様マクロの関数様の扱いを徹底し、マクロ呼び出しが引数中にあっても置換リスト中にあっても原則として同じ pp-token sequence が生成されるように、マクロの rescanning は置換リストだけを対象とし、マクロ呼び出しに続く pp-token sequence は対象としないことにする。<br>
|
||
<li>Digraph は token ではなく、trigraph と同様の character の代替 spelling とし、translation phase 1 で変換することにする。<br>
|
||
<li>Translation phase 3 の規定のうち、"partial preprocessing token or" は削除する。<br>
|
||
<li>#if 式に関する "it shall not contain a cast; " という記載は con-straint から削除し、C99 の footnote 140 に吸収する。<br>
|
||
<li>Trigraphs はヨーロッパ大陸では実際に使われているのであろうか? もしかなり使われているのであれば残すしかないが、そうでなければ廃止したい。<br>
|
||
</ol>
|
||
<p>これらはいずれも、不規則な規則を整理し、プリプロセス規定を単純明快にしようとするものです。これによってプリプロセスがわかりやすくなることは間違いないでしょう。逆に困ることはほとんどないはずです。</p>
|
||
<p><b>mcpp</b> では、Standard モードでは Standard C の気に入らないところも含めて Standard C のプリプロセス規定を完全に実装しているつもりですが、post-Standard というモードでは以上の変更を加えたプリプロセスを実現しています(そのほか、UCN, identifier 中の multi-byte characters の使用も除外している)。</p>
|
||
<p>C90 の Amendment 1, Corrigendum 1 では、プリプロセスについては不規則な規則を整理するよりは不規則性を増やす方向に動いてしまいました。</p>
|
||
<p>C99 でも、新機能がいろいろ追加されたばかりで、上記のような論理の混乱がどれも整理されなかったのは残念です。*1</p>
|
||
<p>C99 で追加された仕様については、次の点を要求したいと思います。</p>
|
||
<ol>
|
||
<li>Unicode (UCN) の導入はオプションにとどめる。
|
||
</ol>
|
||
<p>なお、上記の問題点のほかに、#if 式に適用される整数型の演算規則についてももう一つ問題があります。</p>
|
||
<ol>
|
||
<li>定数式の演算結果はその型で表現できる範囲になければならない、という constraint があるが、これはすべての定数式に適用されるものなのかどうか不明である。例外は記載されてないところをみると、文言の上ではすべての定数式に適用されると解釈するしかないが、Standard C の意図は「定数式の必要なところでは」ということであろう。明確にすべきである。また、他方で「符号なし型は決して overflow しない」という規定もあり、符号なし型の定数演算で範囲を超えた時に診断メッセージを出すべきかどうかは、ことにあいまいである(定数式はコンパイル時に評価できるものであるので、診断メッセージを出すのが適当と思われる)。
|
||
</ol>
|
||
<p>しかし、これはプリプロセス固有の問題ではないので、これ以上は論じません。</p>
|
||
<p>また、C90 では /, % 演算子による整数の割り算について、分母・分子の片方または双方が負数の場合の結果は implementation-defined というひどい規定がありましたが、C99 では div(), ldiv() と同様の規定となりました。</p>
|
||
<p>注:</p>
|
||
<p>*1 C99 への各種 defect report やそれに対する response および corrigendum の draft は次の ftp site にある。これは ISO/SC22/WG14 の official ftp server となっており、少なくとも今のところは anonymous ftp できる(SC というのは steering committee の略で、WG は working group である。SC22 というのはプログラム言語の規格を審議するところで、WG14 は C の規格を担当している)。</p>
|
||
<blockquote>
|
||
<p><a href="http://www.open-std.org/jtc1/sc22/wg14/">http://www.open-std.org/jtc1/sc22/wg14/</a></p>
|
||
</blockquote>
|
||
<br>
|
||
|
||
<h1><a name="3" href="#toc.3">3. Validation Suite 解説</a></h1>
|
||
|
||
<h2><a name="3.1" href="#toc.3.1">3.1. Validation Suite for Conformance of Preprocessing</a></h2>
|
||
<p>test-t, test-c, test-l, tool, cpp-test というディレクトリに入っているもの、およびこの cpp-test.html そのものが、私の作った "Validation Suite for Standard C Conformance of Preprocessing" (「プリプロセスの標準C適合性検証用ソウトウェア一式」)です。これは任意の処理系のプリプロセスの Standard C (ISO C) 準拠度を詳細にテストするものです。Standard C/C++ で規定されているすべてのプリプロセス仕様を網羅しているつもりです。さらに、規定外の事項に関するおまけもたくさんあります。</p>
|
||
<p>Standard C 準拠の要件としては、処理系が正しく動作することはもちろんですが、それだけでなく、ドキュメントに必要な事項が正確に記載されていることが必要です。それについては <a href="#3.5">3.5</a> で説明します。</p>
|
||
|
||
<h3>3.1.1. test-t ディレクトリのテスト用サンプル</h3>
|
||
<p>test-t というディレクトリには 183 本のサンプルテキストが入っています。そのうち 30 本はヘッダファイル、145 本は細切れのサンプルテキストで、8 本は細切れのサンプルテキストをまとめたファイルです。ヘッダファイル以外は一部は *.cc という名前ですが、あとはすべて *.t という名前がついています。これはコンパイルフェーズとは関係なく、プリプロセスフェーズだけをテストするものです。したがって、必ずしもCの正しいプログラムの形にはなっていません。プリプロセステスト専用のサンプルテキストと言うべきものです。</p>
|
||
<p>Standard C の処理系はプリプロセスとコンパイルとを圧縮して処理することもできるので、処理系によってはプリプロセスだけを切り離してテストすることができません。この *.t のサンプルそのものが Standard C に従っていないとも言えます。しかし、プリプロセスだけを切り離してテストできる処理系も多くあり、できるなら切り離してテストしたほうが仕様も問題点も明確になります。この *.t のサンプルはそのためのものです。</p>
|
||
<p>C++ のサンプルは、処理系によっては *.c, *.t という名前では C++ のソースとして扱わないものもあるので、*.cc という名前のものも用意しています。その内容は対応する *.t と同じです。</p>
|
||
<p>サンプルテキストのファイルには名前が n_ で始まるもの(normal の意)、i_ で始まるもの(implementation-dependent の意)、m_ で始まるもの(multibyte character の意)、e_ で始まるもの(erroneous の意)があります。</p>
|
||
<p>n_ で始まるものは、プリプロセスに関しては間違いも undefined な動作を引き起こすものも implementation-defined な部分も含まないサンプルです。Standard C 準拠のプリプロセッサは、これをエラーにせずに正しく処理できなければなりません。</p>
|
||
<p>i_ で始まるものは、文字セットに関する implementation-defined な仕様に依存するサンプルで、ASCII 基本文字セットを前提としています。ASCII 文字セットを持つ Standard C 準拠の処理系のプリプロセッサは、これをエラーにせずに正しく処理できなければなりません。</p>
|
||
<p>e_ で始まるものは、何らかの violation of syntax rule or constraint、すなわちエラーを含むサンプルです。Standard C 準拠のプリプロセッサは、これを見逃すことなく正しく診断できなければなりません。</p>
|
||
<p>n_, i_, m_, e_ の後に数字の続くものは C90 のプリプロセス, および C90 と C99 の共通のプリプロセス仕様をテストするサンプルです。ヘッダファイルのうち、pragmas.h, ifdef15.h, ifdef31.h, ifdef63.h, long4095.h および nest9.h から nest15.h までは C99 のプリプロセス仕様をテストするサンプルで、他のヘッダファイルは C90 と C99 の共通のものです。</p>
|
||
<p>n_, i_, e_, u_ に std, post 以外のアルファベットの続くものは C99 および C++ のサンプルです。n_dslcom.t, n_ucn1.t, e_ucn.t, u_concat.t は C99 と C++98 の共通の、n_bool.t, n_cnvucn.t, n_cplus.t, e_operat.t, u_cplus.t は C++ の、その他は C99 のプリプロセス仕様をテストするサンプルです。</p>
|
||
<p>?_std.t という名前のファイルは C90 の細切れファイルをまとめたものです。?_std99.t はその C99 版です。?_post.t, ?_post99.t という名前のファイルはオマケで、<b>mcpp</b> の post-Standard モードのテストのためのものです。</p>
|
||
<p>u_*.t という名前のファイルはオマケで、undefined behavior のテストをする細切れのファイルです。undefs.t はそれらを1本にまとめたものです。unbal?.h はそれらで使われるヘッダファイルです。unspcs.t は unspecified behavior のテストをするもので、warns.t は以上のどれにも当てはまらないが処理系がウォーニングを出すのが望ましいテキストを記載したファイルです。unspcs.t, warns.t もオマケです。m_ で始まるものは、multi-byte character, wide character として数種の encoding を持つものを用意しています。できれば、多くの encoding を正しく処理することが望まれます。m_* は規格準拠性のテストではなく、u_* 等と同様に品質の評価項目に属しています。</p>
|
||
<p>misc.t, recurs.t, trad.t はオマケ中のオマケです。この3つはプリプロセッサの評点の対象とはしません。misc.t は規格書やその他の書籍に載っているもの、整数型の内部表現によって結果が異なるテキスト、translation phase 5, 6 に関するテスト、拡張機能のテスト、等を集めたものです。recurs.t は特殊な再帰的マクロのサンプルで、trad.t は古い "Reiser model cpp" のためのサンプルです。</p>
|
||
|
||
<h3>3.1.2. test-c ディレクトリのテスト用サンプル</h3>
|
||
<p>test-c というディレクトリには 133 本のファイルが入っており、そのうち 26 本はヘッダファイル(24 本は test-t のものと同じ)、102 本は細切れのサンプルソースで、3 本は細切れのサンプルソースをまとめたファイル、他の 2 本は自動テストに使うファイルです。これらのうち 32 本はオマケのサンプルソースです。ヘッダファイル以外のソースはすべて *.c という名前がついています。これはCのプログラムの形になっています。</p>
|
||
<p>やはり、n_, i_, m_, e_ で始まる名前がついています。n_ で始まるものは、Standard C で言う strictly conforming program (間違いも処理系依存部分も持たないプログラム)です。処理系はこれらをエラーにせずに正しくコンパイルし、正しく実行できなければなりません。正しく実行できた場合はそれぞれ</p>
|
||
<pre style="color:navy">
|
||
started
|
||
success
|
||
</pre>
|
||
<p>というメッセージが出ます。n_std.c に限っては、これらのメッセージは出ず、</p>
|
||
<pre style="color:navy">
|
||
<End of "n_std.c">
|
||
</pre>
|
||
<p>といった終了のメッセージだけが出ます。そうでない場合は何らかの失敗のメッセージが出ます。i_ で始まるものは ASCII を前提とした文字定数のサンプルで、ASCII 文字セットを持つ処理系は、これを n_ で始まるものと同様に正しくコンパイルし、正しく実行できなければなりません。e_ で始まるものについては、処理系はコンパイル(プリプロセス)時に正しく診断できなければなりません。</p>
|
||
<p>コンパイルあるいは実行によるテストは最も正式のテスト方法ですが、その方法では処理系に間違いがあることはわかっても、どこに間違いがあるのかが明確にならない場合があります。これらの *.c ファイルもプリプロセッサにだけ通して、その結果を目で見てテストすることもできるので、処理系が許す限りそうしたほうが正確な評価ができます(*.t ファイルのほうがさらに端的である)。<br>
|
||
?_std.c という名前のファイルは細切れファイルをまとめたものです。<br>
|
||
u_*.c という名前のファイルはオマケで、undefined behavior のテストをする細切れのファイルです。undefs.c はそれらを1本にまとめたものです。unspcs.c は unspecified behavior のテストをするもので、warns.c は以上のどれにも当てはまらないが処理系がウォーニングを出すのが望ましいテキストを記載したファイルです。unspcs.c, warns.c もオマケです。m_ で始まるサンプルには数種の multi-byte character encoding に対応したものがあります。<br>
|
||
test-c ディレクトリには C99 のテストは含まれていません。まだ、C99 に十分対応しているコンパイラ本体が見当たらないからです。C++ のテストも test-t ディレクトリにしかありません。</p>
|
||
|
||
<h3>3.1.3. test-l ディレクトリのテスト用サンプル</h3>
|
||
<p>test-l ディレクトリに入っているのは、規定を上回る translation limits のテストをするためのサンプルです。144 本のすべてがオマケです。*.c, *.t, *.h ファイルが混ざっています。</p>
|
||
<p>*.h ファイルは test-t, test-c, test-l の各ディレクトリに重複して含まれているものがかなりあります。重複するヘッダファイルを一つのディレクトリにまとめると、例えば</p>
|
||
<pre style="color:navy">
|
||
#include "../test-t/nest1.h"
|
||
</pre>
|
||
<p>といったインクルードのしかたが必要になりますが、こうしたパスリストの形式やファイルを探す時の方法(基準ディレクトリをどこに置くか等)がすべて implementation-defined であり、互換性が保証されていないので、この問題を避けるために重複をいとわず各ディレクトリにヘッダファイルを置いています(そもそも「ディレクトリ」という概念さえも C Standard からは除外されている)。</p>
|
||
|
||
<h3>3.1.4. tool ディレクトリのツール</h3>
|
||
<p>tool というディレクトリには、自動テスト等を行うのに必要なツールが入っています。</p>
|
||
|
||
<h3>3.1.5. cpp-test ディレクトリの GCC/testsuite 用サンプル</h3>
|
||
<p>cpp-test というディレクトリにあるのは、GCC / testsuite で使うためのサンプルです。test-t, test-l ディレクトリのものを testsuite 用に書き直した版を置いています。</p>
|
||
<br>
|
||
|
||
<h2><a name="3.2" href="#toc.3.2">3.2. テスト方法</a></h2>
|
||
<p>Validation Suite でテストする時には、処理系に Standard C に近付けるためのオプションがある場合は、それらをすべて指定します(具体例は <a href="#6.1">6.1</a> 参照)。</p>
|
||
|
||
<h3><a name="3.2.1" href="#toc.3.2.1">3.2.1. 手動テスト</a></h3>
|
||
<p>test-t, test-c の各ディレクトリとも、大きくまとめたファイルとかなり細切れのファイルと、2種のサンプルがあります。プリプロセッサの Standard C 準拠度が高ければ、少なくとも n_* についてはまとめたファイルだけでテストできますが、さほど高くない場合はこれらのファイルでは途中からプリプロセッサが混乱に陥って、それ以降の項目のテストができなくなってしまうことがあるので、細切れのファイルも用意してあります。しかし、あまり細切れにするとファイルの数がやたらに増えてテストの手間がかかるので、いい加減なところで妥協しています。処理系によっては、この細切れのサンプルでさえも最後まで処理できないことがあります。その場合はそのサンプルをさらに分割してテストしてください。</p>
|
||
<p>また、#error directive は処理系によっては処理を終了させるので、#error のテストをするサンプルはまとめたファイルに入れていません。#include のエラーも処理をそこで終了させることが多いので、まとめたファイルには入れていません。</p>
|
||
|
||
<h4>3.2.1.1. 目視テスト</h4>
|
||
<p>*.t のサンプルは、プリプロセッサが独立したプログラムになっている場合、またはコンパイラにプリプロセス後のテキストを出力するオプションがある場合に使います。これらのファイルをプリプロセスした結果を直接、目で見て、コメント中に書かれている正しい結果と一致するかどうかを確かめます。プリプロセスの結果を直接見ることができるので、処理系が許す限りこちらでテストしたほうが正確な判断ができます。</p>
|
||
<p>*.c のプログラムの多くは "defs.h" というヘッダファイルを include しています。この "defs.h" には2種の assert マクロの定義が書いてあるので、そのうちのどれか1つを囲んでいる #if 0 の 0 を 1 にします。1番目のものは <assert.h> を include するだけです。2番目のものは assertion が失敗しても abort しない assert マクロです。これはもちろん正しい assert マクロではありませんが、このテストではむしろこちらのほうが便利でしょう。</p>
|
||
<p>Multi-byte character の処理は実行時環境によって処理系の動作仕様が変わる場合があるので、m_* のテストには注意が必要です(<a href="#4.1">4.1</a> 参照)。</p>
|
||
|
||
<h4>3.2.1.2. テストするターゲット</h4>
|
||
<p>こうしたテストには難しい問題があります。
|
||
ある事項をテストしようとすると、処理系の他の欠陥にひっかかってしまうことがあるからです。例えば <limits.h> が間違っている場合、それを include して #if のテストをしようとすると、<limits.h> のテストをしているのか #if のテストをしているのかわからなくなってしまいます。*.c ファイルをコンパイルし実行するテストでは、*.t ファイルをプリプロセスするテストよりもっとやっかいになります。最終的な結果が間違っていれば、処理系に何らかの欠陥があることははっきりしますが、テストしようとした事項に欠陥があるのかどうかは、必ずしも明らかではありません。</p>
|
||
<p>この Validation Suite ではターゲットとする事項に的を絞れるようにいろいろ工夫したつもりです。しかし、Validation Suite 自体は portable でなければならないという制約があり、また、ある事項をテストするためには他の言語仕様が正しく実装されていることを前提とせざるをえません。したがって、この「前提」として使われるプリプロセスの事項は、その事項をターゲットとしたテスト項目以外のところでも暗黙のうちにテストされることになります。次に述べる「配点」についても、そうした暗黙の配点があることに留意してください。また、処理系があるサンプルの処理に失敗した場合、本当にそのサンプルがターゲットとするテスト項目で失敗したのか、それとも他の要因で失敗したのかは、他のテストとも併せて見ないと判断できないことがあります。</p>
|
||
|
||
<h4>3.2.1.3. 採点</h4>
|
||
<p>各テスト項目については、それぞれ配点を決めています。採点の基準も書いてあります。Standard C にはサブセットというものはないので、全項目が規定に合致していなければ厳密には Standard C 準拠とは言えませんが、現実にはそういう処理系はなかなかないので、処理系の評価には Standard C 準拠度という物差しを使わざるをえません。そして、項目の重要度には大きな差があるので、合格項目の数だけを数えるわけにはゆかず、重要度に応じた配点をせざるをえません。</p>
|
||
<p>しかし、もちろんこの配点には客観的な基準はありません。この Validation Suite の配点は私が勝手に決めたもので、大した根拠はありません。それでも、処理系の規格合致度を客観的に評価するための目安にはなるでしょう。</p>
|
||
<p>n_*, i_*, e_*, d_* は規格準拠度に関するテストで、これらは原則として 2 点単位で配点します。規格外のテストと品質の評価では、q_* は 2 点単位で、他は 1 点単位で配点します。診断メッセージを出すべきところでは、出るには出たが見当外れなものである場合は、点はやらないことにします。まったく間違いではないもののかなりピントのずれた診断メッセージの場合は、半分だけ点をやることがあります。また、正しいプログラムを正しく処理したうえで診断メッセージを出すことは処理系の自由ですが、間違った診断メッセージが出る場合は減点の対象とします。</p>
|
||
|
||
<h3><a name="3.2.2" href="#toc.3.2.2">3.2.2. cpp_test による自動テスト</a></h3>
|
||
<p>tool ディレクトリにある cpp_test.c というプログラムをコンパイルして test-c ディレクトリで動かすと、C90 の n_*.c, i_*.c のテストを自動的に行うことができます。ただし、これは○×をつけるだけで、詳細を知ることはできません。e_*.? 等のテストも含まれていません。プリプロセッサの C90 準拠度について簡単に見当をつけるためのものです。C99 についてのテストも含まれていません。現在はまだコンパイラの多くが C99 には高々半分しか対応していないからです。*1, *2, *3</p>
|
||
<p>cpp_test の使い方は Visual C++ 2005 を例にとると、次の通りです。</p>
|
||
<pre style="color:navy">
|
||
cpp_test VC2005 "cl -Za -TC -Fe%s %s.c" "del %s.exe" < n_i_.lst
|
||
</pre>
|
||
<p>第二引数以下はそれぞれ " と " で囲む必要があります(shell が ", " をはがしてしまう場合は、第二引数から最後の引数までの全体を ' と ' でさらに囲む等の対策が必要)。%s は n_*, i_* 等、サンプルプログラムの名前から .c を取り除いたもので置き換えられます。</p>
|
||
<p>第一引数: 処理系の名前を指定します。これは8バイト以内で、'.' を含まないものでなければなりません。この名前に .out, .err, .sum をつけた名前のファイルが作成されます。<br>
|
||
第二引数: コンパイルするためのコマンドを書きます。<br>
|
||
第三引数以下: 用のすんだファイルを削除するコマンドを書きます。これは複数あってかまいません。</p>
|
||
<p>n_i_.lst は test-c ディレクトリにあります。n_*.c, i_*.c の各ファイル名から .c を取り除いたもののリストが書かれています。<br>
|
||
処理系によっては、どれかのソースの処理で暴走を始めるものもあります。その場合は、n_i_.lst のそのソースの名前を例えば none といった存在しないファイル名に書き替えて、再実行します。</p>
|
||
<p>こうして cpp_test を実行すると、n_*.c, i_*.c が順次コンパイルされ、実行されてゆきます。n_*.err, i_*.err というファイルに、サンプルプログラムの stderr への出力が記録されます。そして、VC2005.sum にその採点結果が縦1列に書き込まれます。と言っても、次の3種類の採点しかありません。</p>
|
||
<blockquote>
|
||
*: 合格<br>
|
||
o: コンパイルはできたが、実行結果が不合格<br>
|
||
-: コンパイルできなかった
|
||
</blockquote>
|
||
<p>VC2005.out に cpp_test の呼び出したコマンドラインが記録され、処理系が stdout に出したメッセージがあればそれもそこに記録されます。VC2005.err には、処理系が stderr に出したメッセージがあれば、それが記録されます。これらを見ることによって、もう少し何かを知ることができるでしょう。</p>
|
||
<p>さらに、次のようにします。</p>
|
||
<pre style="color:navy">
|
||
paste -d'\0' side_cpp *.sum > cpp_test.sum
|
||
</pre>
|
||
<p>こうすると、各処理系のテスト結果である *.sum ファイルが横に連結されて1つの表が作成され、cpp_test.sum に書き込まれます。side_cpp はテスト項目のタイトルを書いた表側部分で、test-c ディレクトリにあります。</p>
|
||
<p>私がこうして作った cpp_test.sum を doc ディレトクリに置いてあります。なお、<a href=#6>6</a> には手動による詳細テストの結果が書いてあります。そちらでは cpp_test.sum より多くのプリプロセッサをテストしています。それらのプリプロセッサの中にはどの処理系のコンパイラドライバにも対応していないものがあり、そうしたプリプロセッサについては cpp_test による自動テストはできません。</p>
|
||
<p>注:</p>
|
||
<p>*1 この cpp_test.c は "Plum-Hall Validation Sampler" の runtest.c と summtest.c というプログラムを下敷きにして書いたものである。</p>
|
||
<p>*2 cpp_test.c は Borland C / bcc32 でコンパイルすると期待通りの動作をしない。cpp_test は stdout, stderr をリダイレクトしたうえで system() を呼び出しているが、bcc32 では標準入出力が子孫のプロセスに継承されないようである。Visual C, LCC-Win32 でコンパイルしたものは問題なく動く。</p>
|
||
<p>*3 m_36_*.c は 0x5c ('\\') の値のバイトを持つ encoding のテストであるが、これらの encoding を通常は使わないシステムもあるので、ここでは除外する。</p>
|
||
|
||
<h3><a name="3.2.3" href="#toc.3.2.3">3.2.3. GCC / testsuite による自動テスト</a></h3>
|
||
|
||
<h4><a name="3.2.3.1" href="#toc.3.2.3.1">3.2.3.1. TestSuite とは</a></h4>
|
||
<p>GCC には testsuite というものがあります。GCC のソースをコンパイルした後で、make check とすると、この testsuite のサンプルが次々とチェックされ、結果が報告されます。<br>
|
||
私の検証セットは V.1.3 からは、GCC の testsuite としても使える形に書き直した edition が追加されました。これを testsuite に入れておくと、make check で自動的にチェックされます。<a href="#3.2.2">3.2.2</a> の cpp_test というツールでは n_*, i_* という名前のサンプルしかテストできませんが、testsuite では e_*, w_*, u_* 等の診断メッセージを要するサンプルも自動的にテストできます。この版は GCC 2.9x 以降の cpp0 (cc1, cc1plus) と <b>mcpp</b> のテストをすることができます。<br>
|
||
ここでは、GCC / testsuite での検証セットの使い方を説明します。</p>
|
||
<p>検証セットの cpp-test ディレクトリは test-t, test-l ディレクトリを GCC / testsuite に対応して書き直したもので、cpp-test の中に test-t, test-l の各ディレクトリがあります。<br>
|
||
ただし、m_*, u_1_7_* は multi-byte character の各種 encoding に対応したものですが、GCC 3.3 までのバージョンでは環境変数の設定を encoding に応じて変えることが必要で、コンパイル時の環境を変えることが testsuite ではできないので、cpp-test には含めていません。*1<br>
|
||
GCC / cpp の仕様の変更や testsuite の仕様の変更がこれまでにいろいろあり、これからもあると予想されますが、それによっては検証セットの一部修正が必要になります。ことに診断メッセージが追加されたり変更されたりした場合は、testcase にそれを取り込む必要があります。しかし、今のところは GCC のバージョンがひどく古くない限り、大幅な修正は必要ないと思われます。cpp-test の testcases は GCC 2.95.3, 3.2, 3.3.2, 3.4.3, 4.0.2, 4.1.1 の各 cpp (cpp0, cc1, cc1plus) および <b>mcpp</b> で動作を確かめてあります。<br>
|
||
testsuite では実行時オプションを処理系によって変えることはできません。複数の規格が事実上併存している現在の状況では std= というオプションで規格のバージョンを明示する必要がありますが、このオプションは GCC の古いバージョンには存在しません。したがって、私の testsuite の適用範囲は GCC 2.9x 以降と <b>mcpp</b> V.2.3 以降ということになります。</p>
|
||
<p>Testsuite はサンプルプログラム中に書かれた次のような形式のコメントを解釈して実行されます。これはコメントなので、他の処理系のテストには影響しません。</p>
|
||
<pre style="color:navy">
|
||
/* { dg-do preprocess } */
|
||
/* { dg-error "out of range" } */
|
||
</pre>
|
||
<p>dg-error とか dg-warning というコメントの書かれたサンプルでは、診断メッセージがテストされます。複数の処理系のテストをすることも、それぞれの処理系の診断メッセージを '|' (OR) をはさんで並べて書くことで、可能になります。<br>
|
||
これを実行するのは DejaGnu というツールで、直接には runtest という shell-script です。DejaGnu の設定は *.exp という名前のいくつかのファイルに書かれています。*.exp は expect というツールのための script です。そして、この expect は Tcl というコマンド言語で書かれたプログラムなのです。<br>
|
||
したがって、testsuite を使うには、その testsuite に応じて、これらの多くのツールの適切なバージョンのものがそろっていなければなりません。これは私の検証セットを使う場合でも、使わない場合と同じです。</p>
|
||
<p>注:</p>
|
||
<p>*1 実際には環境変数を設定しても GCC は正しく動作しない。</p>
|
||
|
||
<h4><a name="3.2.3.2" href="#toc.3.2.3.2">3.2.3.2. TestSuite へのインストールとテスト</a></h4>
|
||
<p>GCC / testsuite で私の検証セットを使うには、次のようにします。<br>
|
||
まず、 cpp-test ディレクトリを GCC の testsuite の適当なディレクトリにコピーします。<br>
|
||
cpp-test ディレクトリは、test-t, test-l の各ディレクトリの必要なファイルを書き直し、cpp-test.exp というテスト用の設定ファイルを加えたたものです。*.t という名前のファイルは suffix を .c と変えてあります。C++ のファイルは *.C という名前にしてあります。<br>
|
||
大半のサンプルはプリプロセッサだけをテストするものですが、2つだけは DejaGnu, Tcl の問題でそれができないので、compile & run するためのサンプルになっています(*_run.c という名前のもの)。この2つのサンプルには { dg-options "-ansi -no-integrated-cpp" } という行があります。-no-integrated-cpp というのは GCC 3, 4 のためのオプションです。GCC 2 にはこのオプションはないので、GCC 2 でテストする場合はこのオプションをはずさなければなりません。そのために、この2つの testcase については *_run.c.gcc2, *_run.c.gcc3 という2種のファイルを用意しています。その適当なほうを *_run.c にリンクして使ってください。</p>
|
||
<p>以下では、私のところの Linux での GCC 3.4.3 を例にとって説明します。GCC 3.4.3 のソースが /usr/local/gcc-3.4.3-src に置かれているとします。また、GCC のコンパイルは /usr/local/gcc-3.4.3-objs で行うとします。</p>
|
||
<pre style="color:navy">
|
||
cp -r cpp-test /usr/local/gcc-3.4.3-src/gcc/testsuite/gcc.dg
|
||
</pre>
|
||
<p>これで、gcc.dg ディレクトリに cpp-test 以下のファイルがコピーされます。<br>
|
||
こうしておくと、/usr/local/gcc-3.4.3-objs で</p>
|
||
<pre style="color:navy">
|
||
make bootstrap
|
||
</pre>
|
||
<p>として GCC のソースをコンパイルした後で、</p>
|
||
<pre style="color:navy">
|
||
make -k check
|
||
</pre>
|
||
<p>とすると、cpp-test の testcases を含めたすべての testsuite がテストされます。</p>
|
||
<p>また、cpp-test だけ使ってテストするには、/usr/local/gcc-3.4.3-objs/gcc ディレクトリで次のようにします。</p>
|
||
<pre style="color:navy">
|
||
make check-gcc RUNTESTFLAGS=cpp-test.exp
|
||
</pre>
|
||
<p>Testsuite の log は ./testsuite ディレクトリの gcc.log, gcc.sum に記録されます。<br>
|
||
make check する時は、環境によっては GCC のソースの INSTALL/test.html に説明があるように、DEJAGNULIBS, TCL_LIBRARY という環境変数の設定が必要です。<br>
|
||
また、GCC 3 では環境変数 LANG, LC_ALL を C として英語環境にしなければなりません。<br>
|
||
GCC のコンパイル時に make check で使われるのは、すでにインストールされている gcc, cc1 等ではなく、gcc ディレクトリに生成された xgcc, cc1, cc1plus, cpp0 等であることに注意してください。<br>
|
||
cpp-test によるテストは次のようにしてもできます。</p>
|
||
<pre style="color:navy">
|
||
runtest --tool gcc --srcdir /usr/local/gcc-3.4.3-src/gcc/testsuite cpp-test.exp
|
||
</pre>
|
||
<p>この場合は、実行するディレクトリは任意です。ログはカレントディレクトリに出力されます。テストされるのは、すでにインストールされている gcc, cc1, cpp0 等です。cpp-test で testsuite が必要なのは、GCC 用の各種の設定ファイル (config.*, *.exp) がそこにあるからです。</p>
|
||
<p>この runtest --tool gcc で指定する 'gcc' という名前はこのとおりでなければなりません。もし実際に使うコンパイラがたとえば cc, gcc-3.4.3 等と gcc 以外の名前の場合は、シンボリックリンクを作って gcc という名前で呼び出せるようにしておきます。<br>
|
||
なお、cpp-test にはプリプロセッサがウォーニングを出すのが望ましいと考えられるケースについてのウォーニングの testcases も多く含まれています。GCC のプリプロセッサはそれらの半分以下しか PASS しませんが、通らないからといって、動作が間違っているとか、プリプロセッサが正しくコンパイルされていないとかということではありません。これは正しいか間違っているかの問題ではなく、プリプロセッサの「品質」の問題なのです。</p>
|
||
|
||
<h4><a name="3.2.3.3" href="#toc.3.2.3.3">3.2.3.3. <b>mcpp</b> の自動テスト</a></h4>
|
||
<p>この cpp-test では標準モードの <b>mcpp</b> のテストもできるようになっています。したがって、GCC のプリプロセスを <b>mcpp</b> に置き換えて、gcc ディレクトリで</p>
|
||
<pre style="color:navy">
|
||
make check-gcc RUNTESTFLAGS=cpp-test.exp
|
||
</pre>
|
||
<p>とすると、<b>mcpp</b> の自動チェックがされます。任意のディレクトリで runtest を直接呼び出して実行することもできます。</p>
|
||
<pre style="color:navy">
|
||
runtest --tool gcc --srcdir /usr/local/gcc-3.4.3-src/gcc/testsuite cpp-test.exp
|
||
</pre>
|
||
<p><b>mcpp</b> は GCC 3, 4 で実行した場合、cpp-test の testcases が1件を除いてすべてが PASS するはずです。GCC 2 で実行した場合はもう1件、通らないものがありますが、それは gcc が -D__cplusplus=1 というオプションを付けて <b>mcpp</b> を呼び出すからであり、<b>mcpp</b> のせいではありません。<br>
|
||
プリプロセスを <b>mcpp</b> に置き換える方法については、<a href="mcpp-manual.html#3.9.5">mcpp-manual.html#3.9.5</a>, <a href="mcpp-manual.html#3.9.7">mcpp-manual.html#3.9.7</a> を見てください。
|
||
Testsuite を適用する時は、<b>mcpp</b> の起動に -23j というオプションを付けるように設定する必要があります。-2 は digraph、-3 は trigraph を有効にするオプションです。-j は診断メッセージの出力にソース行等の情報を付加しないためのオプションです。他のオプションは設定しないでください。また、testsuite でテストできるのは標準モードの <b>mcpp</b> だけで、他のモードの <b>mcpp</b> のテストはできません。</p>
|
||
<p>以上は GCC の make の後で行う方法ですが、GCC / testsuite がインストールされていて実行できる状態になっていれば、<b>mcpp</b> 自身の configure と make で <b>mcpp</b> の自動テストをすることができます。この場合は、必要な設定をすべて make check が自動的にやってくれるので、最も簡単です。この方法については、<b>mcpp</b> の INSTALL を見てください。</p>
|
||
|
||
<h4><a name="3.2.3.4" href="#toc.3.2.3.4">3.2.3.4. TestSuite と検証セット</a></h4>
|
||
<p>GCC には testsuite が古くからありましたが、プリプロセスに関しては V.2.9x まではごくわずかのサンプルしかありませんでした。いかにプリプロセスが軽視されていたかがわかります。このプリプロセスの testcases が V.3.x ではケタ違いに増えました。プリプロセッサのソースとドキュメントが一新され、up-to-date なものになったことに伴うもので、プリプロセスが重視されるようになったことがわかります。<br>
|
||
しかし、この testcases は依然としてかなり片寄ったものになっています。その原因は、testsuite の次のような性格に由来するものだと思われます。</p>
|
||
<ol>
|
||
<li>ユーザから寄せられる bug report の集積であること。すなわち、実際に発見されたバグを修正し再発を防ぐためのものが中心である。<br>
|
||
<li>そこに、開発者が新しい機能を実装した時に、そのデバッグのために書いたものが追加されている。<br>
|
||
</ol>
|
||
<p>これはオープンソースならではのデバッグ方法で、GCC が世界の多くの優れたプログラマに使われてきたことで可能となったものです。しかし、この方法は同時に、testcases のランダム性と片寄りをもたらしてきたと考えられます。<br>
|
||
また、これらの testcases には GCC でしか通用しないサンプルが多く、他の処理系には適用できません。それどころか、GCC 3 の testcases には GCC 2 / cpp にさえも適用できないものが多く含まれています。プリプロセス出力の spacing の違いや診断メッセージの違いがあるためです。<br>
|
||
これに対して私の検証セットは、初めは私のプリプロセッサのデバッグのために私が一人で書いたものですが、途中で考え方を変えて、プリプロセスの全仕様をテストするように書き直されました。多くのサンプルが全体としてシステマティックに構成されています。こうしたシステマティックな testcases が GCC / testsuite に追加されることは、少なくない意味を持つと思われます。<br>
|
||
また、私の testsuite 版検証セットは GCC 2.9x / cpp0, GCC 3.x, 4.x / cc1, <b>mcpp</b> という3つのプリプロセッサのテストができるように書かれています。すなわち、DejaGnu, Tcl の正規表現の機能を活用すれば、処理系によるプリプロセス出力の spacing の差異や診断メッセージの差異を吸収することができるのです。*1</p>
|
||
<p>Testsuite 化した検証セットをこの3つのプリプロセッサに適用した結果を以下に載せておきます(<b>mcpp</b> は 2007/03 の、他は 2006/10 のテスト)。<br>
|
||
GCC 4.1.1 でプリプロセッサを <b>mcpp</b> V.2.6.3 に置き換えて実行した場合はこうなります。</p>
|
||
<pre style="color:navy">
|
||
=== gcc Summary ===
|
||
|
||
# of expected passes 264
|
||
# of unexpected failures 1
|
||
# of expected failures 4
|
||
/usr/local/bin/gcc version 4.1.1
|
||
</pre>
|
||
<p>1つだけ FAIL があるのは、C++98 での universal-character-name <=> multi-byte character の変換を実装していないことによるものです。<br>
|
||
GCC 3.2 / cc1 ではこうなります。</p>
|
||
<pre style="color:navy">
|
||
=== gcc Summary ===
|
||
|
||
# of expected passes 216
|
||
# of unexpected failures 51
|
||
# of unexpected successes 2
|
||
# of expected failures 2
|
||
/usr/local/bin/gcc version 3.2
|
||
</pre>
|
||
<p>FAIL の多くは、ウォーニングを出してほしいところで出してくれないというものです。<br>
|
||
GCC 4.1.1 / cc1 もほとんど変わりません。</p>
|
||
<pre style="color:navy">
|
||
=== gcc Summary ===
|
||
|
||
# of expected passes 214
|
||
# of unexpected failures 53
|
||
# of unexpected successes 2
|
||
# of expected failures 2
|
||
/usr/local/bin/gcc version 4.1.1
|
||
</pre>
|
||
<p>GCC 2.95.3 / cpp0 ではこうなります。</p>
|
||
<pre style="color:navy">
|
||
=== gcc Summary ===
|
||
|
||
# of expected passes 181
|
||
# of unexpected failures 87
|
||
# of unexpected successes 3
|
||
# of expected failures 1
|
||
gcc version 2.95.3 20010315 (release)
|
||
</pre>
|
||
<p>GCC 3, 4 / cc1 よりもさらにウォーニングが少なく、ピントの外れた診断メッセージもいくつかあります。C99, C++98 の新しい仕様も半分が未実装です。<br>
|
||
GCC のバージョンによって件数が少し違うのは、1つの testcase 中で複数の FAIL が発生することがあるからです。</p>
|
||
<p>注:</p>
|
||
<p>*1 このために、私の testcases の dg script はやたらに \ や記号の多い unreadable なものになっている。DejaGnu, Tcl の正規表現の処理にはかなりの癖や欠陥があり、使うには種々の工夫が必要であるが、工夫すれば複数の処理系について一通りの自動テストが可能であることがわかった。ただし、現状では、それらの処理系の実行時オプションのうち testcases で使われるオプションが共通であることが条件である。</p>
|
||
<br>
|
||
|
||
<h2><a name="3.3" href="#toc.3.3">3.3. Violation of syntax rule or constraint と診断メッセージ</a></h2>
|
||
<p>Standard C の処理系は正しいソースを正しく処理しなければならないのはもちろんですが、間違ったソースに対しては診断メッセージを出さなければなりません。また、Standard C には、動作仕様が処理系にまかされている部分や、どういう動作をするか規定されていない部分もあります。これらのことをまとめて示すと、次のようになります。*1</p>
|
||
<ol>
|
||
<li>正しいプログラムと正しいデータで、どの処理系でも同じ処理結果になるもの。<br>
|
||
<li>正しいプログラムとデータであっても、その処理方法は規定されないもの。これはドキュメントに記載しなくても良い。これを unspecified behavior(未規定の動作)と呼ぶ。<br>
|
||
<li>正しいプログラムとデータであっても、その処理は処理系にまかされているもの。この仕様は各処理系がドキュメントに記載しなければならない。これを implementation-defined behavior(処理系定義の動作)と呼ぶ。<br>
|
||
<li>間違ったあるいは移植性のないプログラムまたはデータであるが、その処理については何も規定されないもの。処理系はこれに診断メッセージを出しても良いし、出さなくても良い。何らかの正しいプログラムとしての処理をしても良い。これを undefined behavior(未定義の動作)と呼ぶ。<br>
|
||
<li>間違ったプログラムまたはデータで、それに対して処理系が diagnostic message(診断メッセージ)を出さなければならないもの。これには violation of syntax rule(構文規則違反)と violation of constraint (制約違反)とがある。*2<br>
|
||
</ol>
|
||
<p>これらのうち 1 のプログラムとデータだけのものを strictly conforming program (規格厳密合致プログラム)と呼びます(2, 3 も、処理系や場合によって結果に違いの出ないものであれば、含まれていても良いと解釈される)。<br>
|
||
1, 2, 3 のプログラムとデータだけのものを conforming program (規格合致プログラム)と呼びます。</p>
|
||
<p>診断メッセージの出し方は implementation-defined です。何らかの violation of syntax rule or constraint を含む translation unit 1つについて1つ以上の何らかの診断メッセージを出さなければならないとされています。Violation of syntax rule or constraint を含まないプログラムに対して、診断メッセージを出すことは処理系の自由です。ただし、もちろん strictly conforming program や、その処理系の implementation-defined, unspecified の仕様に合致した conforming program は最後まで正しく処理できなければなりません。<br>
|
||
このドキュメントでは、violation of syntax rule or constraint を「エラー」と呼ぶことにします。</p>
|
||
<p>この Validation Suite の e_* ファイルには、複数のエラーを含むものがたくさんあります。以下の採点では、処理系が1つのエラーについて1つ以上の診断メッセージを出すことを期待しています。しかし、いくつエラーがあっても1つの translation unit について1つの診断メッセージ("violation of syntax rule or constraint" といった)しか出さない処理系もあるかもしれません。また、エラーがあるとその後の処理が混乱する処理系もあるかもしれません。この種の問題は処理系の「品質」の問題ですが、規格準拠度の問題ではありません。必要な場合はサンプルをバラして再テストしてください。品質の問題は <a href="#4">4</a> で別にとりあげます。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 3 Definitions of Terms 用語の定義及び規約
|
||
C90 4 Compliance 規格合致性
|
||
C90 5.1.1.3 Diagnostics 診断メッセージ
|
||
C99 3 Terms, definitions, and symbols
|
||
C99 4 Conformance
|
||
C99 5.1.1.3 Diagnostics
|
||
</pre>
|
||
<p>*2 C++98 では C90, C99 とは用語がかなり違っているが、主旨は特に違わない。</p>
|
||
<br>
|
||
|
||
<h2><a name="3.4" href="#toc.3.4">3.4. 詳細</a></h2>
|
||
<p>以下に、各テスト項目について1つずつ解説します。
|
||
これは Standard C プリプロセスそのものの解説ともなるものです。しかし、K&R 1st. と共通の仕様については、あらためて解説はしません。項目の番号は *.t ファイルと *.c ファイルとで共通です。</p>
|
||
|
||
<h3><a name="3.4.1" href="#toc.3.4.1">3.4.1. Trigraphs</a></h3>
|
||
|
||
<h4>n.1.1. 9つの trigraph sequences</h4>
|
||
<p>Cの基本文字セットには ISO 646:1983 の Invariant Code Set に含まれない文字が9つあるので、これらは次の3文字の sequence でもソース中に書くことができるようになりました。これは C90 で新設された規定です。*1</p>
|
||
<blockquote>
|
||
<table>
|
||
<tr><th>??=</th><td>#</td></tr>
|
||
<tr><th>??(</th><td>[</td></tr>
|
||
<tr><th>??/</th><td>\</td></tr>
|
||
<tr><th>??)</th><td>]</td></tr>
|
||
<tr><th>??'</th><td>^</td></tr>
|
||
<tr><th>??<</th><td>{</td></tr>
|
||
<tr><th>??!</th><td>|</td></tr>
|
||
<tr><th>??></th><td>}</td></tr>
|
||
<tr><th>??-</th><td>~</td></tr>
|
||
</table>
|
||
</blockquote>
|
||
<p>これらの9つの trigraph sequence は translation phase 1 で対応する文字に置き換えられます。9つの文字をキーボードから入力することのできるシステムでは、もちろん trigraph を使う必要はありません。しかし、そういうシステムでも Standard C に合致するプリプロセスは trigraph の変換ができる必要があります。</p>
|
||
<p>配点 : 6。9つとも正しく処理できれば 6 点、正しく処理できない trigraph が1つあるごとに 2 点減点し、0 点を下限とする。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 5.2.1.1 Trigraph sequences 3文字表記
|
||
C90 5.1.1.2 Translation phases 翻訳フェーズ
|
||
C99 5.2.1.1 Trigraph sequences
|
||
C99 5.1.1.2 Translation phases
|
||
</pre>
|
||
|
||
<h4>n.1.2. コントロール行中の trigraph sequence</h4>
|
||
<p>Trigraphs の変換は translation phase 3 での tokenization や phase 4 でのコントロール行の処理に先立って行われるので、もちろんコントロール行の中でもどこでも trigraphs を書くことができます。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>n.1.3. Trigraphs は9つしか存在しない</h4>
|
||
<p>Trigraphs は上記の9つしかないので、それ以外の ?? で始まる sequence は他の文字に変換されることはなく、?? がスキップされることもありません。Trigraph とそうでない ? を含む sequence が混在している場合も、プリプロセスは正しく処理できる必要があります。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h3><a name="3.4.2" href="#toc.3.4.2">3.4.2. <backslash><newline> による行接続</a></h3>
|
||
<p>行末に \ があってその直後に改行が続いているばあいは、この <backslash><newline> の sequence は translation phase 2 で無条件に削除されます。その結果、2つの行が接続されます。規格書では、ソースファイル上の行を physical line(物理行)、<backslash><newline> が(あれば)削除されて接続された後の行を logical line(論理行)と呼んで区別しています。Translation phase 3 の処理はこの論理行を対象として行われます。*1<br>
|
||
K&R 1st. では #define 行と文字列定数は <backslash><newline> で次のソース行に続けることができるとされていましたが、その以外の場合については触れられていません。実際の処理系ではまちまちですが、コントロール行は #define に限らず接続できるものが多かったようです。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 5.1.1.2 Translation phases 翻訳フェーズ
|
||
C99 5.1.1.2 Translation phases
|
||
</pre>
|
||
|
||
<h4>n.2.1. #define 行のパラメータリストと置換リストとの間</h4>
|
||
<p>#define 行の接続は K&R 1st. でも認められていたものであり、ほとんどの処理系で可能です。</p>
|
||
<p>配点 : 4。すなわち、正しく処理できれば 4 点、できなければ 0 点。</p>
|
||
|
||
<h4>n.2.2. #define 行のパラメータリストの中</h4>
|
||
<p>しかし、#define 行であってもパラメータリストの中のようなあまり普通ではないところに <backslash><newline> があると、正しく処理できない処理系もあります。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>n.2.3. 文字列リテラルの中</h4>
|
||
<p>文字列リテラルの中の <backslash><newline> も K&R 1st. 以来のものです。</p>
|
||
<p>配点 : 2。
|
||
</p>
|
||
|
||
<h4>n.2.4. 識別子の中</h4>
|
||
<p>Standard C では、識別子の中であろうとどこであろうと、<backslash><newline> は無条件で削除されなければなりません。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>n.2.5. Trigraph による <backslash></h4>
|
||
<p><backslash> は \ だけでなく、trigraph による ??/ もあります。ソース上の ??/ は translation phase 1 で \ に変換されていますから、phase 2 では当然 \ そのものです。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h3><a name="3.4.3" href="#toc.3.4.3">3.4.3. コメント</a></h3>
|
||
<p>Translation phase 3 では論理行が pp-token と white spaces に分解されます。その際、コメントは1個の space に変換されます。*1<br>
|
||
ここで処理系は、連続する white spaces(コメントを含む)を1個の space に変換するかもしれません。ただし、どちらにしても <newline> は変換せず、そのまま残します。それは、次の phase 4 での preprocessing directive の処理がこの「行」を対象とするからです。<br>
|
||
コメントが行をまたがっている場合は、コメントによる事実上の行接続が行われます。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 5.1.1.2 Translation phases 翻訳フェーズ
|
||
C99 5.1.1.2 Translation phases
|
||
</pre>
|
||
|
||
<h4>n.3.1. One space に変換</h4>
|
||
<p>いわゆる Reiser 型の古い cpp ではコメントは、cpp の内部でだけ token separator として機能し、出力までの間には削除されていました。そのことを利用して、トークンの連結にコメントを使う方法がありましたが、この仕様は K&R 1st. からも外れたものであり、Standard C では明確に否定されました。Standard C ではトークン連結には ## 演算子を使います。</p>
|
||
<p>配点 : 6。</p>
|
||
|
||
<h4>n.dslcom. // コメント</h4>
|
||
<p>K&R 1st. から C90 に至るまで、コメントは /* で始まり */ で終わるものでした。*1<br>
|
||
しかし、C99 では C++ の // もコメントとして扱うことになりました。*2</p>
|
||
<p>配点 : 4。
|
||
<br>
|
||
C90 ではこれはコメントではなく単なる / という pp-token 2つの sequence として処理すべきものであるが、C99 以前から // をコメントとして処理する処理系が一般的であった。<b>mcpp</b> では C90 モードでもこれをコメントとして処理した上でウォーニングを出す。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.1.9 Comments コメント
|
||
</pre>
|
||
<pre>
|
||
*2 C99 6.4.9 Comments
|
||
</pre>
|
||
|
||
<h4>n.3.3. コメントは pp-directive の処理に先立って処理</h4>
|
||
<p># で始まる preprocessing directive は「行」を対象としますが、この「行」はソースの物理行とは限りません。<backslash><newline> によって接続された論理行であることもあれば、物理・論理行をまたぐコメントによって複数の物理・論理行にまたがる「行」であることもあります。これは translation phase 1 から 4 の順序を考えてみれば、当然のことです。</p>
|
||
<p>配点 : 4。</p>
|
||
|
||
<h4>n.3.4. コメントと <backslash><newline></h4>
|
||
<p><backslash><newline> とコメントの双方によっていくつかの物理行にまたがる pp-directive 行も存在します。Translation phase を正しく実装していないプリプロセッサでは、これを正しく処理することができません。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h3><a name="3.4.4" href="#toc.3.4.4">3.4.4. 特殊なトークン (digraphs) と文字 (UCN)</a></h3>
|
||
<p>C90 / Amendment 1 (1994) では、いくつかの operator, punctuator について digraph という代替 spelling が追加されました。*1<br>
|
||
C99 では UCN (universal character sequence) という文字表記が追加されました。*2<br>
|
||
e.4.? ではトークンエラーを取り上げます。</p>
|
||
<p>注
|
||
:</p>
|
||
<pre>
|
||
*1 Amendment 1 / 3.1 Operators, 3.2 Punctuators (ISO 9899 / 6.1.5, 6.1.6 への追加)
|
||
C99 6.4.6 Punctuators
|
||
|
||
*2 C99 6.4.3 Universal character names
|
||
</pre>
|
||
|
||
<h4>n.4.1. Preprocessing directive 行中の digraph spelling</h4>
|
||
<p>Digraph は token (pp-token) として扱われます。
|
||
%: は # の別 spelling です。もちろん、preprocessing directive 行の先頭 pp-token としても、文字列化演算子としても使えます。</p>
|
||
<p>配点 : 6。</p>
|
||
|
||
<h4>n.4.2. Digraph spelling の文字列化</h4>
|
||
<p>Digraph は trigraph と違って token (pp-token) なので、文字列化に際しては変換されずにそのままの spelling で文字列化されます(無意味な仕様である)。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>n.ucn1. UCN の認識 1</h4>
|
||
<p>UCN は文字列リテラル、文字定数、識別子の中で認識されます。
|
||
</p>
|
||
<p>配点 : 8。文字列リテラル中の UCN がプリプロセスをそのまま通過すれば 4 点、文字定数、identifier の中の UCN が正しく処理されれば各 2 点。UCN であることを認識せず黙ってそのまま出力するものは不可。</p>
|
||
|
||
<h4>n.ucn2. UCN の認識 2</h4>
|
||
<p>UCN は pp-number 中でも使えます。しかし、プリプロセスの終りまでに number-token からは消滅していなければなりません。これは C99 だけの規定で、C++ にはありません。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.ucn. UCN の誤り</h4>
|
||
<p>UCN は \U で始まるものは8ケタ、\u で始まるものは4ケタの16進数でなければなりません。<br>
|
||
UCN は [0..9F], [D800..DFFF] の範囲にあってはなりません。ただし、24($), 40(@), 60(`) は有効です。</p>
|
||
<p>配点 : 4。4つのサンプルのそれぞれについて、正しく診断できれば各 1 点。</p>
|
||
|
||
<h4>e.4.3. 空の文字定数</h4>
|
||
<p>Cのトークンの形をしていない sequence でもたいていは pp-token として認められます。したがって、プリプロセスの tokenization でエラーとなる場合は、あまりありません。<br>
|
||
ただし、このほかに undefined behavior となる場合がいくつかあります(<a href="#4.2">4.2</a> 参照)。<br>
|
||
カラの文字定数はプリプロセスの #if 行でもコンパイルでも、violation of syntax rule です。*1</p>
|
||
<p>配点 : 2。
|
||
</p>
|
||
<p>注
|
||
:</p>
|
||
<pre>
|
||
*1 C90 6.1.3.4 Character constants 文字定数 -- Syntax 構文規則
|
||
C99 6.4.4.4 Character constants -- Syntax
|
||
</pre>
|
||
|
||
<h3><a name="3.4.5" href="#toc.3.4.5">3.4.5. Preprocessing directive line 中の space と tab</a></h3>
|
||
<p>Space, tab, vertical-tab, form-feed, carriage-return, new-line は white space として一括されます。文字列リテラル・文字定数・ヘッダ名・コメントの中にない white space は通常は token separator としての意味しかもっていませんが、translation phase 4 まで残った newline は特別なもので、pp-directive の区切りとなります。そして、pp-directive line 中では使える white space に若干の制限があります。</p>
|
||
|
||
<h4>n.5.1. # の前後の space と tab</h4>
|
||
<p>Standard C では、Preprocessing directive line の最初の pp-token である # の前後の space, tab は、あってもなくても同じ結果になることが保証されています(*1)。K&R 1st. ではこれが明確ではなく、実際にも # の前後の space, tab を認めない処理系がありました。<br>
|
||
その行のそれ以降の space, tab は、K&R 1st. でも Standard C と同様に、単なる token separator として認められていたと解釈されます。<br>
|
||
しかし、pp-directive line に space, tab 以外の white space があった場合は undefined です(4.2 の <a href="#u.1.6">u.1.6</a> 参照)。</p>
|
||
<p>配点 : 6。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8 Preprocessing directives 前処理指令 -- Description 補足規定, Constraints 制約
|
||
C99 6.10 Preprocesssing directives -- Description, Constraints
|
||
</pre>
|
||
|
||
<h3><a name="3.4.6" href="#toc.3.4.6">3.4.6. #include</a></h3>
|
||
<p>#include は K&R 1st. 以来の最も基本的な pp-directive です。しかし、Standard C のこの directive の規定には undefined な部分と implementation-defined な部分とが多くなっています(*1)。その理由は次の点にあります。</p>
|
||
<ol>
|
||
<li>この directive の引数である header-name という pp-token はOSのファイルシステムに依存するものであり、標準化しがたい。<br>
|
||
<li>ファイルをさがす「標準」の場所は処理系に依存する。<br>
|
||
<li>", "
|
||
で囲まれた文字列リテラル類似の形式の header-name でさえも、\ は escape 文字ではない等、文字列リテラルとは別の pp-token であり、さらに <, > で囲まれた header-name にいたっては最も変則的な pp-token である。<br>
|
||
</ol>
|
||
<p>n.6.* では、次のような最も基本的なテストは、項目を分けて行うことはしません。これは他のテスト項目に前提として含まれており、これが処理できないのでは多くのテストが実施できないので、論外です。</p>
|
||
<pre style="color:navy">
|
||
#include <ctype.h>
|
||
#include "header.h"
|
||
</pre>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.2 Source file inclusion ソースファイル取り込み
|
||
C99 6.10.2 Source file inclusion
|
||
</pre>
|
||
|
||
<h4>n.6.1. 2つの形式による標準ヘッダの include</h4>
|
||
<p>Header-name には2つの形式があります。
|
||
その違いは、<, > で囲まれた形式では処理系定義の特定の場所(複数でも良い)からその header をさがし、", " で囲まれた形式ではそのソースファイルをまず処理系定義の方法でさがして、それが失敗した時は <, > で囲まれた形式と同様の処理をする、ということにすぎません。したがって、", " で囲まれた形式で標準ヘッダを include することもできます。この点は K&R 1st. でも同様でした。<br>
|
||
なお、Standard C では、同じ標準ヘッダを複数回 include してもかまわないことになっています。しかし、それはどちらにしてもプリプロセッサの問題ではなく、標準ヘッダの書き方の問題です(<a href=#4.1.1>4.1.1</a> 参照)。</p>
|
||
<p>配点 : 10。2つのサンプルの片方しか成功しないのは 4 点。</p>
|
||
|
||
<h4>n.6.2. マクロによる header-name・その1</h4>
|
||
<p>K&R 1st. では #include 行にマクロが使えるとはされていませんでしたが、Standard C ではマクロが公認されました。#include の引数が2つのどちらの形式とも一致しない場合は、そこに含まれるマクロが展開されます。その結果は2つの形式のどちらかに一致する必要があります。<br>
|
||
この規定には微妙なところもあります。例えば、次のようなソースはどう扱われるべきでしょうか。</p>
|
||
<pre style="color:navy">
|
||
#define MACRO header.h>
|
||
#include <MACRO
|
||
</pre>
|
||
<p>これは、MACRO をまず展開して</p>
|
||
<pre style="color:navy">
|
||
#include <header.h>
|
||
</pre>
|
||
<p>とすべきでしょうか。それとも、マクロ展開の前に < に対応する > がないとしてエラーとすべきでしょうか。</p>
|
||
<p>規格はこういうところまで意識して書かれているとは考えられません。
|
||
したがって、<, > は ", " と同様の quotation delimiter として扱うのがすなおだと思われます。もちろん、先にマクロを展開するのも、規格違反とは言えませんが。この Validation Suite には、こうした規格の穴をつつくテストは含まれていません。</p>
|
||
<p>配点 : 6。</p>
|
||
|
||
<h4>n.6.3. マクロによる header-name・その2</h4>
|
||
<p>つまらないことですが、マクロは必ずしも1つである必要はありません。
|
||
</p>
|
||
<p>配点 : 2。
|
||
</p>
|
||
|
||
<h3><a name="3.4.7" href="#toc.3.4.7">3.4.7. #line</a></h3>
|
||
<p>#line という pp-directive はユーザが使うことは普通はないものですが、他の何かのツールで(Cとは限らない)ソースを pre-preprocess した場合などに、元のソースのファイル名とその時々の行番号を伝えるために使われるもののようです。K&R 1st. 以来あるところを見ると、伝統的にそれなりの用途があるのでしょう。<br>
|
||
また、プリプロセッサの出力にも一般に #line またはその変形が、ファイル名と行番号情報をコンパイラ本体に伝えるために使われます。しかし、これは規定されていることではありません。<br>
|
||
#line で指定されたファイル名と行番号は事前定義マクロ <tt>__FILE__</tt>, <tt>__LINE__</tt> の値となります(<tt>__LINE__</tt> はさらに物理行1行ごとにインクリメントされてゆく)。*1</p>
|
||
<pre>
|
||
*1 C90 6.8.4 Line control 行制御
|
||
C99 6.10.4 Line control
|
||
C90 6.8.8 Predefined macro names あらかじめ定義されたマクロ名
|
||
C99 6.10.8 Predefined macro names
|
||
</pre>
|
||
|
||
<h4>n.7.1. 行番号とファイル名の指定</h4>
|
||
<p>Line number と filename を指定した #line は K&R 1st. にもありましたが、K&R 1st. では filename は ", " で囲みませんでした。<br>
|
||
Standard C では filename は文字列リテラルです。ただし、これは #include の header-name とは違ったトークンであり、厳密に考えると \ の扱い等で微妙な問題を含んでいます。しかし、正しいソースであれば問題は生じないでしょう(#line の filename に <stdio.h> の形のものがないのは幸いである)。</p>
|
||
<p>配点 : 6。</p>
|
||
|
||
<h4>n.7.2. ファイル名指定は無くても良い</h4>
|
||
<p>Filename の引数はオプションであり、なくてもかまいません。
|
||
これも K&R 1st. と同じです(line number の指定のないのは undefined)。</p>
|
||
<p>配点 : 4。</p>
|
||
|
||
<h4>n.7.3. マクロによる行番号とファイル名の指定</h4>
|
||
<p>K&R 1st. では #line の引数にマクロが使えるとはされていませんでしたが、Standard C では使えることが保証されました。</p>
|
||
<p>配点 : 4。</p>
|
||
|
||
<h4>n.line 行番号の範囲</h4>
|
||
<p>#line ディレクティブの line number の範囲は C90 では [1..32767] でしたが、C99 では [1..2147483647] に拡大されました。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.7.4. ファイル名がワイド文字列リテラル</h4>
|
||
<p>Filename の引数は文字列リテラルでなければならず、つまらないことですが、ワイド文字列リテラルでは violation of constraint となります(それ以外の pp-token ではなぜか undefined。アンバランスな規定である)。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h3><a name="3.4.8" href="#toc.3.4.8">3.4.8. #error</a></h3>
|
||
<p>#error は C90 で新設された directive です。
|
||
プリプロセス時に、引数をその一部として含むエラーメッセージを表示します。古い処理系では #assert 等の directive を持つものもありましたが、標準的なものはありませんでした。<br>
|
||
なお、#error では処理を終了すべきかどうかは規定されていませんが、Rationale によると、規格ではそこまで要求できないので規定しなかっただけで、中止するのが ANSI C 委員会の意図であるとのことです。*1</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.5 Error directive 表示指令
|
||
ANSI C Rationale 3.8.5
|
||
C99 6.10.5 Error directive
|
||
C99 Rationale 6.10.5
|
||
</pre>
|
||
|
||
<h4>n.8.1. マクロは展開されない</h4>
|
||
<p>#error 行中のマクロは展開されません。
|
||
コントロール行でマクロが展開されるのは、#if (#elif), #include, #line だけです。*1</p>
|
||
<p>配点 : 8。マクロを展開して処理するものは 2 点。処理を終了するかどうかは問わない。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8 Preprocessing directives 前処理指令 -- Semantics 意味規則
|
||
C99 6.10 Preprocessing directives -- Semantics
|
||
</pre>
|
||
|
||
<h4>n.8.2. メッセージは無くても良い</h4>
|
||
<p>つまらないことですが、#error 行の引数はオプションであり、なくてもかまいません。</p>
|
||
<p>配点 : 2。
|
||
</p>
|
||
|
||
<h3><a name="3.4.9" href="#toc.3.4.9">3.4.9. #pragma, _Pragma()</a></h3>
|
||
<p>#pragma も C90 で新設されたものです。処理系独自の拡張 directive はすべて #pragma sub-directive で実現することになっています。*1</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.6 Pragma directive プラグマ指令
|
||
C99 6.10.6 Pragma directive
|
||
</pre>
|
||
|
||
<h4>n.9.1. 認識できない #pragma もエラーにはならない</h4>
|
||
<p>したがって、処理系ごとに認識できる #pragma sub-directive が異なります。認識できない #pragma がいちいちエラーになったのでは portable なプログラムが書けないので、処理系が認識できない #pragma は無視することになっています。プリプロセスでは自分の認識できるプリプロセスに関する #pragma だけを処理して、他の #pragma はすべてそのままコンパイラ本体に渡すことになります。</p>
|
||
<p>配点 : 10。#pragma をエラーにしてはいけないが、ウォーニングを出すことはかまわない。</p>
|
||
|
||
<h4>n.pragma. _Pragma() operater</h4>
|
||
<p>C99 では、#pragma と同じ効果を持ち、#pragma と違ってマクロ定義中にも書くことのできる _Pragma() operator が追加されました。<br>
|
||
また、pragma に続く pp-token が STDC であれば規定された標準の機能を持ち、この場合はマクロ展開が禁止されるが、他の場合はマクロ展開の対象とするかどうかは implementation-defined とされています。</p>
|
||
<p>配点 : 6。</p>
|
||
|
||
<h4>e.pragma _Pragma() の引数は文字列リテラル</h4>
|
||
<p>_Pragma() operator の引数は文字列リテラルでなければなりません。</p>
|
||
<p>配点 : 2。
|
||
</p>
|
||
|
||
<h3><a name="3.4.10" href="#toc.3.4.10">3.4.10. #if, #elif, #else, #endif</a></h3>
|
||
<p>#if, #else, #endif は K&R 1st. 以来あるものです。
|
||
*1<br>
|
||
#if が使えない処理系では、n.10.1, n.11.*, n.12.*, n.13.*, e.12.*, e.14.* がどれも処理できないばかりでなく、他の多くのテストも失敗することになります。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.1 Conditional inclusion 条件付き取り込み
|
||
C99 6.10.1 Conditional inclusion
|
||
</pre>
|
||
|
||
<h4>n.10.1. #elif が使える</h4>
|
||
<p>C90 では #elif が追加されました。これによって、#if を何重にもネストする読みにくい書き方が避けられるようになりました。<br>
|
||
#if 式中にはマクロを使うこともできます。マクロとして定義されていない identifier は 0 と評価されます。</p>
|
||
<p>なお、規格書では、#if からそれに対応する #endif までを #if section、その中の #if (#ifdef, #ifndef), #elif, #else, #endif で区切られたブロックを #if group と呼んでいます。#if, #elif 行の式を #if 式と呼びます。</p>
|
||
<p>配点 : 10。</p>
|
||
|
||
<h4>n.10.2. スキップされる #if group での pp-token 処理</h4>
|
||
<p>スキップされる #if group では、#if group の対応関係をトレースするために #if, #ifdef, #ifndef, #elif, #else, #endif を調べる以外は preprocessing directive は処理されず、マクロも展開されません。</p>
|
||
<p>しかし、tokenization は行われます。それは第一に、Cのソースは初めから終わりまでコメントと pp-token の sequence だからであり、第二に、#if 等の対応関係を調べるためには少なくともコメントの処理が必要で、/* 等がコメント記号であることを確かめるためにはそれが文字列リテラルや文字定数の中にあるのではないことを確かめなければならないからです。</p>
|
||
<p>配点 : 6。</p>
|
||
|
||
<h3><a name="3.4.11" href="#toc.3.4.11">3.4.11. #if defined</a></h3>
|
||
<p>C90 では #if 式に defined という演算子が新設されました。
|
||
これは #ifdef, #ifndef を #if (#elif) に統合するもので、これによって、#ifdef が何重にもネストされた読みにくい書き方が避けられるようになりました。</p>
|
||
|
||
<h4>n.11.1. #if defined</h4>
|
||
<p>defined 演算子の operand は識別子ですが、それを (, ) で囲む書き方と囲まない書き方の双方が認められています。どちらも意味は同じで、operand がマクロとして定義されていれば 1、そうでなければ 0 と評価されます。</p>
|
||
<p>配点 : 8。2つの #if section の片方しか処理できないものは 2 点。</p>
|
||
|
||
<h4>n.11.2. defined は単項演算子</h4>
|
||
<p>defined は演算子の1つであり、1 か 0 のどちらかの値を与えるので、その結果をさらに他の式と演算することができます。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h3><a name="3.4.12" href="#toc.3.4.12">3.4.12. #if 式の型</a></h3>
|
||
<p>#if 式については K&R 1st. では漠然と定数式とされているだけで、その型がはっきりしていませんでした。C90 では #if 式は整数定数式であり、int は long と、unsigned int は unsigned long と同じ内部表現を持っているかのように扱うことが明確にされました。すなわち、#if 式はその中の副式も含めてすべて long および unsigned long で評価されます。言い換えれば、定数トークンにはすべて L または UL の接尾子が付いているのと同じように扱われます。*1</p>
|
||
<p>さらに、C99 では #if 式の型はそれぞれの処理系の最大の整数型とされました。この型は <stdint.h> という標準ヘッダで intmax_t, uintmax_t という型名に typedef されます。C99 では long long は必須とされているので、#if 式の型は long long またはそれ以上のサイズということになります。long long / unsigned long long の定数を表記するには LL (ll) / ULL (ull) という接尾子を使います。*2<br>
|
||
long long / unsigned long long の値を printf() で表示するには、%ll という length modifier と適当な conversion specifier を使います(%lld, %llu, %llx 等)。intmax_t, uintmax_t の値を表示する length modifier は %j です(%jd, %ju, %jx 等)。*3</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.1 Conditional inclusion 条件付き取り込み -- Semantics 意味規則
|
||
</pre>
|
||
<pre>
|
||
*2 C99 6.10.1 Conditional inclusion -- Semantics
|
||
</pre>
|
||
<pre>
|
||
*3 C99 7.19.6.1 The fprintf function -- Description
|
||
</pre>
|
||
|
||
<h4>n.12.1. long 型の #if 式</h4>
|
||
<p>C90 の #if 式では long 型の定数式が評価できなければなりません。</p>
|
||
<p>配点 : 6。
|
||
</p>
|
||
|
||
<h4>n.12.2. unsigned long 型の #if 式</h4>
|
||
<p>C90 の #if 式では unsigned long 型の定数式が評価できなければなりません。</p>
|
||
<p>配点 : 4。
|
||
</p>
|
||
|
||
<h4>n.12.3. 8進数</h4>
|
||
<p>#if 式では8進定数も評価できなければなりません。K&R 1st. でも同様ですが、long の最大値を越える定数が K&R 1st. では long の負の値に評価されたのに対して、C90 では unsigned long に評価されるところが違っています。*1</p>
|
||
<p>配点 : 4。8進数を認識するが負数に評価したりオーバーフローしたりするものは 2 点、8進数を認識しないものは 0 点。</p>
|
||
<pre>
|
||
*1 C90 6.1.3.2 Integer constants 整数定数
|
||
C99 6.4.4.1 Integer constants
|
||
</pre>
|
||
|
||
<h4>n.12.4. 16進数</h4>
|
||
<p>#if
|
||
式では16進定数も評価できなければなりません。K&R 1st. でも同様ですが、long の最大値を越える定数が K&R 1st. では long の負の値に評価されたのに対して、C90 では unsigned long に評価されるところが違っています。</p>
|
||
<p>配点 : 4。16進数を認識するが負数に評価したりオーバーフローしたりするものは 2 点、16進数を認識しないものは 0 点。</p>
|
||
|
||
<h4>n.12.5. 接尾子 L, l</h4>
|
||
<p>#if 式では接尾子 L, l の付いた定数トークンも評価できなければなりません。K&R 1st. でも long の最大値を越えない定数については同じです。プリプロセスではこの接尾子はあってもなくても評価は同じです。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>n.12.6. 接尾子 U, u</h4>
|
||
<p>#if 式では接尾子 U, u の付いた定数トークンも評価できなければなりません。これは K&R 1st. にはなく、C90 で公認された記法です。</p>
|
||
<p>配点 : 6。</p>
|
||
|
||
<h4>n.12.7. 負数</h4>
|
||
<p>#if 式では負の数も扱えなければなりません。K&R 1st. 以来の仕様です。</p>
|
||
<p>配点 : 4。
|
||
</p>
|
||
|
||
<h4>e.12.8. 定数トークンの値が表現できる範囲にない</h4>
|
||
<p>C90 では、#if 式に現れる整数定数トークンの値が long または unsigned long で表現できる範囲にない場合は violation of constraint、平たく言えばエラーとなります。これは #if 式について直接規定されていることではありませんが、定数式一般についての規定があり、#if 式も例外ではないと考えられます。*1<br>
|
||
なお、文字定数の overflow については、e.32.5., e.33.2, e.35.2 でテストします。</p>
|
||
<p>配点 : 2。</p>
|
||
<p>注
|
||
:</p>
|
||
<pre>
|
||
*1 C90 6.4 Constant expressions 定数式 -- Constraints 制約
|
||
C99 6.6 Constant expressions -- Constraints
|
||
</pre>
|
||
|
||
<h4>n.llong. long long / unsigned long long の評価</h4>
|
||
<p>C99 の #if 式では少なくとも long long / unsigned long long 型の定数式が評価できなければなりません。<br>
|
||
long long の最大値を越える定数は unsigned long long に評価されます。<br>
|
||
C99 では LL, ll, ULL, ull という接尾子も追加されました。*1</p>
|
||
<p>配点 : 10。5つのサンプルについて、正しく処理できればそれぞれ 2 点。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C99 6.4.4.1 Integer constants
|
||
C99 6.6 Constant expressions -- Constraints
|
||
</pre>
|
||
|
||
<h4>e.intmax. 演算の結果が intmax_t の範囲外</h4>
|
||
<p>C99 の #if 式では、intmax_t, uintmax_t の範囲を越える定数および定数式は violation of constraint となります。</p>
|
||
<p>配点 : 2。 <stdint.h> がない場合はマクロを適当に書いてもかまわない。</p>
|
||
|
||
<h3><a name="3.4.13" href="#toc.3.4.13">3.4.13. #if 式の演算</a></h3>
|
||
<p>#if 式は整数定数式の一種です。
|
||
一般の整数定数式と比べると次のような違いがあるだけです。</p>
|
||
<ol>
|
||
<li>C90 では int は long、unsigned int は unsigned long と同じ内部表現を持つかのように評価される。C99 では int, long は intmax_t、unsigned int, unsigned long は uintmax_t と同じ内部表現を持つかのように評価される。<br>
|
||
<li>defined 演算子が使える。<br>
|
||
<li>マクロを展開した後に残った identifier はすべて 0 と評価される。*1<br>
|
||
<li>プリプロセスでは keyword が存在しないので、keyword と同名の identifier は単なる identifier として扱われる。したがって、キャストや sizeof は使えない。<br>
|
||
</ol>
|
||
<p>関数呼び出し、コンマ演算子は、一般の整数定数式でも使えません。定数式は変数ではないので、代入、インクリメント、デクリメント、配列も使えません。<br>
|
||
n.13 では一般の整数定数式と共通の評価ルールについてテストします。このうち n.13.5 は K&R 1st. とは変わったところで、n.13.6. は pre-Standard の #if 式ではまちまちだったところで、n.13.13., n.13.14 は K&R 1st. では明確でなかったものですが、他は K&R 1st. 以来まったく変わっていません。*2<br>
|
||
なお、#if 式で int の値しか評価できない処理系についてもこのルールのテストができるように、n.13. ではすべて小さい値しか使っていません。また、演算子 defined, >= は n.13 に出てきませんが、他のどこかに出てきています。</p>
|
||
<p>注:</p>
|
||
<p>*1 C++ Standard では true, false は特別扱いされ、それぞれ 1, 0 と評価される。これはマクロではなく keyword であるが、プリプロセスでは boolean literal として扱われることになっている。</p>
|
||
<pre>
|
||
*2 C90 6.3 Expressions 式
|
||
C90 6.4 Constant expressions 定数式
|
||
C99 6.5 Expressions
|
||
C99 6.6 Constant expressions
|
||
</pre>
|
||
|
||
<h4>n.13.1. <<, >></h4>
|
||
<p>ビットシフト演算は少なくとも正数に関しては、面倒な問題は何もありません。</p>
|
||
<p>配点 : 2。
|
||
</p>
|
||
|
||
<h4>n.13.2. ^, |, &</h4>
|
||
<p>正の整数はその型の範囲におさまっている限り、同じ値のビットパターンは CPU や処理系のいかんにかかわらず同じなので、^, |, & という一見 CPU の仕様に依存しそうな演算も、その範囲ではどの処理系でもまったく同じ結果になります。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>n.13.3. ||, &&</h4>
|
||
<p>これを処理できない処理系はないでしょう。
|
||
</p>
|
||
<p>配点 : 4。
|
||
</p>
|
||
|
||
<h4>n.13.4. ? :</h4>
|
||
<p>これも処理できない処理系はないでしょう(と思われたが、実際にはなくもない)。
|
||
</p>
|
||
<p>配点 : 2。
|
||
</p>
|
||
|
||
<h4>n.13.5. <<, >> では通常の算術変換は行われない</h4>
|
||
<p>多くの2項演算子では両辺の型をそろえるため usual arithmetic conversion (通常の算術変換)が行われます。K&R 1st. ではシフト演算子でもこれが行われることになっていましたが、Standard C では行わないことになりました。右辺の値は常に小さい正数であること、2の補数以外の内部表現では、負数が正数に変換されたのではビットパターンも変わってしまい、何をやっているのかわからなくなってしまうことを考えると、これは妥当な規定でしょう。*1</p>
|
||
<p>配点 : 2。</p>
|
||
<p>注
|
||
:</p>
|
||
<pre>
|
||
*1 C90 6.3.7 Bitwise shift operators ビット単位のシフト演算子 -- Semantics 意味規則
|
||
C99 6.5.7 Bitwise shift operators -- Semantics
|
||
</pre>
|
||
多くの2項演算子に関しては「両辺が算術型であれば、それらに対して通常の算術変換が行われる」といちいち書いてあるが、<<, >> に関してはこの記載がない。C89 Rationale 3.3.7 (C99 Rationale 6.5.7) にこれについての解説がある。</p>
|
||
|
||
<h4>n.13.6. 通常の算術変換による負数の正数への変換</h4>
|
||
<p>2項演算子 *, /, %, +, -, <, >, <=, >=, ==, !=, &, ^, |, に関しては、両辺の型をそろえるため、両辺の operand に対して通常の算術変換が行われます。3項演算子 ? : の第2、第3 operand についても同様です。したがって、片方が符号なし型であると、他方も符号なし型に変換され、負数が正数になってしまいます。<br>
|
||
なお、一般の整数定数式では通常の算術変換の前に、各 operand が int より短い整数型であった場合はそれに対して integer promotion (汎整数拡張)が行われますが、#if 式ではすべての operand が同じサイズの型として扱われるので、integral promotion は発生しません。</p>
|
||
<p>配点 : 6。3つのテストを1つ正しく処理できるごとに 2 点。<br>
|
||
この評価規則は K&R 1st. でも同じなのであるが、このサンプルは 0U という定数トークンが U 接尾子を使っているので、K&R 1st. の処理系では処理できない。</p>
|
||
|
||
<h4>n.13.7. ||, &&, ? : での評価の打ち切り</h4>
|
||
<p>||, && 演算子では評価順序が決まっており、左辺の評価で結果が確定すれば右辺は評価されません。? : では第一 operand の評価結果によって第二か第三のどちらか一方が評価され、他方は評価されません。*1<br>
|
||
したがって、評価されない項にはたとえば 0 による除算があってもエラーにはなりません。</p>
|
||
<p>配点 : 6。5つのサンプルのうち失敗が1つあるごとに 2 点を減点。2つ以下しか成功しないのは 0 点。間違った診断メッセージが出るのは 2 点減点。</p>
|
||
<p>注:</p>
|
||
<p>*1 ? : 演算子ではしかし、第二 operand と第三 operand との間の通常の算術変換は行われることになっている。評価しないのに変換するとは妙なことである。ことに #if 式で使われる整数定数トークンは値を評価しないと型が決まらないので、型(と言っても符号付きか符号無しかだけであるが)を判定するためには、値を評価せざるをえない。しかし、0 による除算はしてはいけないのである。やっかいなことである。</p>
|
||
|
||
<h4>n.13.8. 単項演算子 -, +, !, ~ のグループ化</h4>
|
||
<p>n.13.8 から n.13.12 までは #if 式中の副式のグループ化のテストです。副式は演算子の優先順位と結合規則に従ってグループ化されます。一般の整数定数式では優先順位の前に構文で決まる部分がありますが、#if 式はグルーピングの (, ) 以外には構文が問題となるところはありません。n.13.8. から n.13.10 までは結合規則のテストで、n.13.8. は単項演算子 -, +, !, ~ の結合規則のテストです。単項演算子はいずれも右から左へ結合されます。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>n.13.9. ? : のグループ化</h4>
|
||
<p>条件演算子 ? : は右から左へ結合されます。</p>
|
||
<p>配点 : 2。
|
||
</p>
|
||
|
||
<h4>n.13.10. <<, >> のグループ化</h4>
|
||
<p>2項演算子はいずれも左から右へ結合されます。n.13.10. では <<, >> のテストをします。</p>
|
||
<p>配点 : 2。
|
||
</p>
|
||
|
||
<h4>n.13.11. 優先順位の異なる演算子のグループ化・その1</h4>
|
||
<p>単項演算子 -, +, ! と2項演算子 *, /, >> という、優先順位も結合規則も異なる演算子の含まれる式のテストをします。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>n.13.12. 優先順位の異なる演算子のグループ化・その2</h4>
|
||
<p>単項演算子 -, +, ~, ! と2項演算子 -, *, %, >>, &, | ^, ==, !=、それに3項演算子 ? : を含むさらに複雑な式のグループ化のテストをします。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>n.13.13. 演算子に展開されるマクロ</h4>
|
||
<p>#if 式にはマクロを含むことができます。
|
||
このマクロは通常は整数定数に展開されるものですが、それについては n.10.1, n.12.1, n_12.2, n.13.7 のテストに含まれているので、ここではあらためてテストしません。<br>
|
||
演算子に展開されるマクロというのは尋常なものではありませんが、やはり原則として演算子として扱われるべきでしょう。ISO C 1990 / Amendment 1 では <iso646.h> という標準ヘッダが規定されましたが、これはいくつかの演算子をマクロで定義するものです (*1)。&, |, !, ^, ~ といった文字を使わなくてもソースを書くことができるように、という趣旨のようです。プリプロセスは #if ではこれらのマクロを展開しながら、演算子として扱うことが求められます。<br>
|
||
しかし、他方で #if 式中のマクロが defined に展開されると undefined という規定があります。defined は identifier と紛らわしいので別扱いになったものでしょう(<a href="#u.1.19">u.1.19</a> 参照)。</p>
|
||
<p>配点 : 4。</p>
|
||
<p>注:</p>
|
||
<p>*1 C++ Standard では、なぜかこれらの identifier 様演算子はマクロではなく token である。</p>
|
||
|
||
<h4>n.13.14. 0個のトークンに展開されるマクロ</h4>
|
||
<p>0個のトークンに展開されるマクロを含む #if 式も尋常なものではありませんが、これはやはりそのマクロをとり除いた(展開した)上で評価されるべきでしょう。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h3><a name="3.4.14" href="#toc.3.4.14">3.4.14. #if 式のエラー</a></h3>
|
||
<p>e.14.1 から e.14.10 までは #if 式での violation of syntax rule と violation of constraint のテストです。処理系はこれらのどれか1つを含むすべてのソースに対して、診断メッセージを出す必要があります。*1</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.1 Conditional inclusion 条件付き取り込み
|
||
C90 6.4 Constant expressions 定数式
|
||
C99 6.10.1 Conditional inclusion
|
||
C99 6.6 Constant expressions
|
||
</pre>
|
||
|
||
<h4>e.14.1. 文字列リテラル</h4>
|
||
<p>#if 式は整数定数式であり、ポインタは使えないので、文字列リテラルは使えません。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.14.2. 演算子 =, ++, --, . 等</h4>
|
||
<p>#if 式は定数式であるので、副作用を持つ演算子や変数は使えません。
|
||
A --B は A - -B とは違って violation of constraint です。</p>
|
||
<p>配点 : 4。4つのサンプルのうち正しく診断できないものがあれば1つあれば 2 点、2つ以上あれば 0 点。</p>
|
||
|
||
<h4>e.14.3. 完結しない式</h4>
|
||
<p>2項演算子の operand の片方がなかったり、かっこの対応がとれなかったりするものは、もちろん violation of syntax rule です。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.14.4. #if defined のカッコが片方だけある</h4>
|
||
<p>#if 行の defined という演算子の引数は (, ) で囲んでも囲まなくてもかまいませんが、カッコの片方だけあるのは violation of constraint です。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.14.5. 式がない</h4>
|
||
<p>#if だけで式がないのはもちろん violation of syntax rule です。</p>
|
||
<p>配点 : 2。
|
||
</p>
|
||
|
||
<h4>e.14.6. マクロを展開すると式がなくなる</h4>
|
||
<p>マクロとして定義されていない identifier は 0 と評価されますが、マクロを展開すると何もなくなってしまう #if 行の引数はもちろん violation of syntax rule です。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.14.7. Keyword は認識されない・sizeof</h4>
|
||
<p>sizeof という pp-token はプリプロセスでは単なる identifier として扱われ、#if 式では、マクロとして定義されていなければ 0 と評価されます。int という pp-token も同様です。したがって、sizeof (int) は 0 (0) となり、violation of syntax rule となります。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.14.8. Keyword は認識されない・型名</h4>
|
||
<p>e.14.7 と同様に、(int)0x8000 は (0)0x8000 となり、やはり violation of syntax rule となります。</p>
|
||
<p>配点 : 2。
|
||
</p>
|
||
|
||
<h4>e.14.9. 0 による除算</h4>
|
||
<p>この e.14.9 と次の e.14.10 については、診断メッセージを出すべきかどうか、いくつかの解釈の余地があり、規定があいまいです。規格書には次のような規定があります。</p>
|
||
<ul>
|
||
<li><samp>C90 6.4 Constant expressions 定数式 -- Constraint 制約</samp><br>
|
||
<li><samp>C99 6.6 Constant expressions -- Constraint</samp><br>
|
||
Each constant expression shall evaluate to a constant that is in the range of representable values for its type.<br>
|
||
定数式を評価した結果は、その型で表現可能な値の範囲内にある定数でなければならない。<br>
|
||
</ul>
|
||
<p>この規定の適用範囲が明らかではありませんが、少なくとも定数式の必要なところには適用されることは自明です。#if 式はもちろん定数式でなければなりません。しかし、他方で次のような規定もあります。</p>
|
||
<ul>
|
||
<li><samp>C90 6.3.5 Multiplicative operators 乗除演算子 -- Semantics 意味規則</samp><br>
|
||
<li><samp>C99 6.5.5 Multiplicative operators -- Semantics</samp><br>
|
||
if the value of the second operand is zero, the behavior is undefined.<br>
|
||
第2オペランドの値が0の場合、動作は未定義とする。<br>
|
||
<br>
|
||
<li><samp>C90 6.1.2.5 Types 型</samp><br>
|
||
<li><samp>C99 6.2.5 Types</samp><br>
|
||
A computation involving unsigned operands can never overflow,<br>
|
||
符号無しオペランドを含む計算は、決してオーバーフローしない。<br>
|
||
</ul>
|
||
<p>0 による除算と符号なし演算では、どちらの規定を適用すべきでしょうか? どちらの解釈もありうるように思えます。</p>
|
||
<p>しかし、ここではこう解釈することにします。</p>
|
||
<blockquote>定数式の要求されるところでは 0 による除算も含めて、その型の範囲におさまらない結果となる場合は原則として診断メッセージを出さなければならない。</blockquote>
|
||
<p>定数式でこうした結果になる場合というのはプログラムの間違いしか考えられず、定数式は実行時ではなくコンパイル時に評価されるものなので、これに診断メッセージを出すのが妥当と思われるからです。また、0 による除算だけ例外扱いするのも不自然だからです。ただし、符号無し演算の結果が範囲外となる場合については規定のあいまいさが二重になるので、ここには含めず、undefined と解釈することにします。</p>
|
||
<p>ISO 9899:1990 / Corrigendum 1 では、「Violation of syntax rule or constraint があれば、たとえ他のところで undefined or implementation-defined と明示的に規定されていても、処理系は診断メッセージを出さなければならない」という規定が追加されました。これは C99 にも引き継がれています。*1</p>
|
||
<p>配点 : 2。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C99 5.1.1.3 Diagnostics
|
||
</pre>
|
||
|
||
<h4>e.14.10. 演算の結果が表現できる範囲にない</h4>
|
||
<p>C90 では、#if 式の値は long / unsigned long で表現できる範囲になければなりません。</p>
|
||
<p>配点 : 4。
|
||
4つのテストをすべて正しく診断できれば 4 点、3つか2つ正しく診断できれば 2 点、1つ以下しか正しく診断できなければ 0 点。</p>
|
||
|
||
<h3><a name="3.4.15" href="#toc.3.4.15">3.4.15. #ifdef, #ifndef</a></h3>
|
||
<p>n.15 は #ifdef, #ifndef に関するテストです。これは K&R 1st. でも Standard C でもまったく同じです。e.15 はその violation of syntax rule のテストです。*1</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8 Preprocessing directives 前処理指令 -- Syntax 構文規則
|
||
C90 6.8.1 Conditional inclusion 条件付き取り込み
|
||
C99 6.10 Preprocessing directives -- Syntax
|
||
C99 6.10.1 Conditional inclusion
|
||
</pre>
|
||
|
||
<h4>n.15.1. #ifdef によるマクロのテスト</h4>
|
||
<p>配点 : 6。</p>
|
||
|
||
<h4>n.15.2. #ifndef によるマクロのテスト</h4>
|
||
<p>配点 : 6。</p>
|
||
|
||
<h4>e.15.3. 識別子でない引数</h4>
|
||
<p>#ifdef, #ifndef 行の引数は identifier でなければなりません。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.15.4. 余計な引数</h4>
|
||
<p>#ifdef, #ifndef 行の引数に identifier 以外の余計なトークンがあってはいけません。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.15.5. 引数がない</h4>
|
||
<p>何も引数がないのも、もちろん violation of syntax rule です。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h3><a name="3.4.16" href="#toc.3.4.16">3.4.16. #else, #endif のエラー</a></h3>
|
||
<p>次は #else, #endif の violation of syntax rule のテストです。
|
||
この syntax も K&R 1st. から変わっていません(しかし、violation of syntax rule or constraint に対して診断メッセージを出さなければならないというのは、C90 で初めて規定されたことである)。*1</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8 Preprocessing directives 前処理指令 -- Syntax 構文規則
|
||
C99 6.10 Preprocessing directives -- Syntax
|
||
</pre>
|
||
|
||
<h4>e.16.1. #else に余計なトークンがある</h4>
|
||
<p>#else の行には他のどんなトークンもあってはいけません。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.16.2. #endif に余計なトークンがある</h4>
|
||
<p>#endif の行には他のどんなトークンがあってもいけません。</p>
|
||
<pre style="color:navy">
|
||
#if MACRO
|
||
#else ! MACRO
|
||
#endif MACRO
|
||
</pre>
|
||
<p>ではなく、</p>
|
||
<pre style="color:navy">
|
||
#if MACRO
|
||
#else /* ! MACRO */
|
||
#endif /* MACRO */
|
||
</pre>
|
||
<p>と書きましょう。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h3><a name="3.4.17" href="#toc.3.4.17">3.4.17. #if, #elif, #else, #endif の対応関係のエラー</a></h3>
|
||
<p>次は #if (#ifdef, #ifndef), #elif, #else, #endif の対応に関する violation of syntax rule のテストです。この syntax もほぼ K&R 1st. 以来同じですが、#elif は C90 で新しく加えられたものです。またこれらの対応はソースファイル単位で成り立っていなければならないという点は、K&R 1st. では明確ではなかったところです。*1</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8 Preprocessing directives 前処理指令 -- Syntax 構文規則
|
||
C99 6.10 Preprocessing directives -- Syntax
|
||
</pre>
|
||
|
||
<h4>e.17.1. #if のない #endif</h4>
|
||
<p>先行する #if がないのに #endif が出てくるのは、もちろん violation of syntax rule です。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.17.2. #if のない #else</h4>
|
||
<p>対応する #if のない #else ももちろん間違いです。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.17.3. #else の後にまた #else</h4>
|
||
<p>#else の後にまた #else が出てくるのも、もちろんいけません。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.17.4. #else の後に #elif</h4>
|
||
<p>#else の後に #elif が出てきてもいけません。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.17.5. インクルードされたファイル中の #if のない #endif</h4>
|
||
<p>#if, #else, #endif 等はソースファイル(preprocessing file)単位で対応がとれていなければなりません。インクルードされたファイルがあたかも初めから元ファイルの中にあったかのように扱うと初めて対応がとれる、というのではダメです。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.17.6. インクルードされたファイル中の #endif のない #if</h4>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.17.7. #endif のない #if</h4>
|
||
<p>#endif を書き忘れることは実際にもよく起こりますが、処理系はそれに対して診断メッセージを出さなければなりません。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h3><a name="3.4.18" href="#toc.3.4.18">3.4.18. #define</a></h3>
|
||
<p>#define の syntax は K&R 1st. に対して C90 では #, ## 演算子が追加されましたが、あとは変わっていません。*1<br>
|
||
C99 ではさらに可変引数マクロが追加されました(<a href=#2.8>2.8</a> 参照)。*2</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8 Preprocessing directives 前処理指令 -- Syntax 構文規則
|
||
C90 6.8.3 Macro replacement マクロ置換え
|
||
|
||
*2 C99 6.10 Preprocessing directives -- Syntax
|
||
C99 6.10.3 Macro replacement
|
||
</pre>
|
||
|
||
<h4>n.18.1. Object-like マクロの定義</h4>
|
||
<p>#define 行の最初のトークンはマクロ名ですが、その直後に white spaces があると、第2のトークンが ( であっても、それは置換リストの開始とみなされ、function-like マクロの定義とはみなされません。また、マクロ名の後に何もトークンがなければ、そのマクロは0個のトークンに定義されます。</p>
|
||
<p>配点 : 30。2つのマクロのうちの1つしか正しく定義できなければ 10 点。</p>
|
||
|
||
<h4>n.18.2. Function-like マクロの定義</h4>
|
||
<p>マクロ名の直後に white spaces をはさまずに ( があると、それは function-like マクロのパラメータリストの開始とみなされます。この規定は K&R 1st. 以来のもので、white spaces の有無に左右される character-oriented なプリプロセスの痕跡が残っていますが、いまとなってはどうしようもありません。</p>
|
||
<p>配点 : 20。</p>
|
||
|
||
<h4>n.18.3. 文字列リテラル中は置換の対象にならない</h4>
|
||
<p>いわゆる "Reiser" model のプリプロセッサでは、置換リスト中の文字列リテラルまたは文字定数の中にパラメータと同じ部分があると、その部分がマクロ展開によって引数に置き換えられました。しかし、これは Standard C はもちろん、K&R 1st. でも認められていなかったものです。この置換は character-oriented なプリプロセスの特徴をよく示す仕様ですが、token-oriented なプリプロセスでは論外です。</p>
|
||
<p>配点 : 10。</p>
|
||
|
||
<h4>n.vargs. 可変個引数マクロ</h4>
|
||
<p>C99 では可変個引数マクロが導入されました。</p>
|
||
<p>配点 : 10。
|
||
</p>
|
||
|
||
<h4>e.18.4. 名前がない</h4>
|
||
<p>#define 行の最初のトークンはもちろん identifier でなければなりません。</p>
|
||
<p>配点 : 2。
|
||
</p>
|
||
|
||
<h4>e.18.5. 引数がない</h4>
|
||
<p>#define 行にトークンが1つもないのは violation of syntax rule です。</p>
|
||
<p>配点 : 2。
|
||
</p>
|
||
|
||
<h4>e.18.6. 空のパラメータ</h4>
|
||
<p>空のパラメータも violation of syntax rule です。*1</p>
|
||
<p>配点 : 2。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.5.4 Declarators 宣言子 -- Syntax 構文規則
|
||
C99 6.7.5 Declarators -- Syntax
|
||
</pre>
|
||
|
||
<h4>e.18.7. パラメータ名の重複</h4>
|
||
<p>1つのマクロ定義のパラメータリストに同じパラメータ名が複数あるのは violation of constraint です。*1</p>
|
||
<p>配点 : 2。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.3 Macro replacement マクロ置換え -- Constraints 制約
|
||
C99 6.10.3 Macro replacement -- Constraints
|
||
</pre>
|
||
|
||
<h4>e.18.8. パラメータが識別子ではない</h4>
|
||
<p>マクロ定義のパラメータは identifier でなければなりません。 *1</p>
|
||
<p>配点 : 2。</p>
|
||
<p>注:</p>
|
||
<p>*1 C99 では ... というパラメータが追加された。置換リスト中の __VA_ARGS__ はそれに対応した特殊なパラメータ名である。</p>
|
||
|
||
<h4>e.18.9. マクロ名と置換リストの特殊な組み合わせ</h4>
|
||
<p>Standard C では identifier 中の文字として $ は認めていませんが、これを認める処理系も伝統的に存在しています。このサンプルはそうした処理系でコンパイルされるソースに見られる種類のものです。この例は Standard C では $ が1文字で1つの pp-token と解釈されるので、マクロ名は THIS で $ 以降が object-like マクロの置換リストとなり、THIS$AND$THAT という名前の function-like マクロというプログラムの意図とはまったく違った結果になります。<br>
|
||
ISO 9899:1990 の Corrigendum 1 では、こうした例に関して例外的規定が追加されました。この例に対しては Standard C の処理系は診断メッセージを出さなければならないのです。*1<br>
|
||
C99 ではそれに代わって、一般に object-like macro の定義では、マクロ名と置換リストの間に white spaces がなければならないとされました。*2</p>
|
||
<p>配点 : 2。</p>
|
||
<p>注:</p>
|
||
<p>*1 Corrigendum 1 による C90 6.8 の Constraints への追加。<br>
|
||
しかし、C++ Standard ではこの規定は消えている。</p>
|
||
<pre>
|
||
*2 C99 6.10.3 Macro replacement -- Constraints
|
||
</pre>
|
||
|
||
<h4>e.vargs1. 置換リストではないところに __VA_ARGS__</h4>
|
||
<p>C99 の可変引数マクロでは、マクロ定義のパラメータ・リスト中の ... に対応するパラメータとして置換リスト中では __VA_ARGS__ が使われます。この identifier はそれ以外のところに使ってはいけません。</p>
|
||
<p>配点 : 2。2つのサンプルを正しく診断できれば 2 点。1つだけでは 0 点。</p>
|
||
|
||
<h3><a name="3.4.19" href="#toc.3.4.19">3.4.19. マクロの再定義</a></h3>
|
||
<p>マクロの再定義については K&R 1st. では何も触れられておらず、実装もまちまちでした。Standard C では、元の定義と同じ再定義は許されるが、異なる定義は認めないことになりました。マクロは事実上、再定義されないことになります(#undef でいったん取り消さない限り)。*1, *2</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.3 Macro replacement マクロ置換え -- Constraints 制約
|
||
C99 6.10.3 Macro replacement -- Constraints
|
||
</pre>
|
||
<p>*2 しかし、ウォーニングを出した上で再定義を認める処理系が多い。<b>mcpp</b> も既存の処理系との互換性のため、V.2.4 からはそうした。</p>
|
||
|
||
<h4>n.19.1. 置換リストの前後の white spaces の違い</h4>
|
||
<p>White spaces の数だけが違っている再定義は許されます。</p>
|
||
<p>配点 :4。</p>
|
||
|
||
<h4>n.19.2. パラメータリスト中の white spaces の違いと行をまたぐ white spaces の違い</h4>
|
||
<p>White spaces には、<backslash><newline> sequence やコメントによってソース行をまたぐものも含まれます。</p>
|
||
<p>配点 : 4。</p>
|
||
|
||
<h4>e.19.3. 置換リストのトークン列が違う</h4>
|
||
<p>置換リストのトークン列が異なる再定義は violation of constraint です。</p>
|
||
<p>配点 : 4。</p>
|
||
|
||
<h4>e.19.4. 置換リスト中の white space の有無が違う</h4>
|
||
<p>置換リスト中の white space の有無が異なる再定義は violation of constraint です。ここには character-oriented なプリプロセスの尻尾が残っています。</p>
|
||
<p>配点 : 4。</p>
|
||
|
||
<h4>e.19.5. パラメータの使い方が違う</h4>
|
||
<p>パラメータの使い方の違う再定義は実質的にも異なる定義なので、violation of constraint です。</p>
|
||
<p>配点 : 4。</p>
|
||
|
||
<h4>e.19.6. パラメータ名が違う</h4>
|
||
<p>パラメータ名が違うだけの実質的に同じ再定義も violation of constraint となります。過剰な constraint だと思われます。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.19.7. Function-like, object-like マクロの名前の共用</h4>
|
||
<p>マクロ名は一つの名前空間に属するので、function-like マクロと object-like マクロとで同じ名前を使うことはできません。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h3><a name="3.4.20" href="#toc.3.4.20">3.4.20. Keyword と同名のマクロ</a></h3>
|
||
<p>プリプロセスでは keyword が存在しないので、keyword と同名の identifier もマクロとして定義し、展開することができます。*1</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.1 Lexical elements 字句要素 -- Syntax 構文規則
|
||
C99 6.4 Lexical elements -- Syntax
|
||
C89 Rationale 3.8.3 (C99 Rationale 6.10.3) Macro replacement
|
||
</pre>
|
||
|
||
<h4>n.20.1. 名前はすべて展開の対象となる</h4>
|
||
<p>配点 : 6。</p>
|
||
|
||
<h3><a name="3.4.21" href="#toc.3.4.21">3.4.21. Pp-token の分離を要するマクロ展開</a></h3>
|
||
<p>ソースファイルの pp-token への分解は translation phase 3 で行われます。
|
||
そして、そのあとで複数の pp-token が1つの pp-token に連結される場合というのは、## 演算子を使って定義されたマクロの展開で連結される場合、# 演算子を使って定義されたマクロの展開で文字列化される場合、および隣接する文字列リテラルが連結される場合しか規定されていません。したがって、複数の pp-token が暗黙のうちに連結されることは、あってはならないことだと解釈されます。これは token-oriented なプリプロセスの原則からすると、当然のことです。*1</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 5.1.1.2 Translation phases 翻訳フェーズ
|
||
C90 6.8.3 Macro replacement マクロ置換え
|
||
C99 5.1.1.2 Translation phases
|
||
C99 6.10.3 Macro replacement
|
||
</pre>
|
||
|
||
<h4>n.21.1. Pp-token が暗黙のうちに連結されることはない</h4>
|
||
<p>プリプロセスが独立したプログラムで行われる場合は、このサンプルの出力の3つの - が3つの別々の pp-token であることがコンパイラ本体にわかるように、これらを何らかの token separator で分離して渡す必要があります。</p>
|
||
<p>配点 : 4。</p>
|
||
|
||
<h4>n.21.2. マクロ引数中のマクロの分離</h4>
|
||
<p>マクロの引数中にマクロがあった場合も、その展開結果が置換リスト中の前後の pp-token とくっついてしまってはいけません。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h3><a name="3.4.22" href="#toc.3.4.22">3.4.22. Pp-number 中のマクロ類似 sequence</a></h3>
|
||
<p>Preprocessing-number は Standard C で初めて規定されたものです。
|
||
整数定数トークンと浮動小数点定数トークンを合わせたものよりも範囲が広く、identifier 類似の部分を含むこともあります。プリプロセスでの tokenization を単純にするために規定されたものですが、マクロ類似部分を持つ pp-number がある場合、この単純な tokenization をその通りにやらないと、間違いが起こります。*1</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.1.8 Preprocessing numbers 前処理数
|
||
C99 6.4.8 Preprocessing numbers
|
||
</pre>
|
||
|
||
<h4>n.22.1. Pp-number 中のマクロ類似 sequence・その1</h4>
|
||
<p>12E+EXP という sequence は1つの pp-number なので、たとえ EXP というマクロが定義されていても、展開されることはありません。</p>
|
||
<p>配点 : 4。</p>
|
||
|
||
<h4>n.22.2. Pp-number 中のマクロ類似 sequence・その2</h4>
|
||
<p>Pp-number は digit または . で始まります。
|
||
</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>n.22.3. Pp-number の外のマクロは展開される</h4>
|
||
<p>C90 では、+ または - は E または e にすぐ続く場合だけ pp-number の中に現れることができます。12+EXP は 12E+EXP と違い、12 + EXP という3つの pp-token に分解されます。これらはそれぞれ pp-number, operator, identifier です。EXP がマクロであれば、展開されます。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>n.ppnum [Pp][+-] の sequence</h4>
|
||
<p>C99 では、浮動小数点数を16進で表記するために、pp-number 中に P または p に + または - が続く sequence が追加されました。<br>
|
||
なお、printf() で浮動小数点数を16進で表示するには %a, %A という conversion specifier を使います。*1</p>
|
||
<p>配点 : 4。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C99 7.19.6.1 The fprintf function -- Description
|
||
</pre>
|
||
|
||
<h3><a name="3.4.23" href="#toc.3.4.23">3.4.23. ## 演算子を使ったマクロ</a></h3>
|
||
<p>## は C90 で新しく作られた演算子で、#define 行の置換リスト中でだけ使われます。## の前後の pp-token はマクロ展開時に連結されて1つの pp-token となります。## の前後の pp-token がパラメータの場合は、マクロ展開時に実引数で置き換えられてから連結されます。*1</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.3.3 The ## operator ##演算子
|
||
C99 6.10.3.3 The ## operator
|
||
</pre>
|
||
|
||
<h4>n.23.1. トークンの連結</h4>
|
||
<p>## 演算子を使った最も単純な function-like マクロの例です。</p>
|
||
<p>配点 : 6。</p>
|
||
|
||
<h4>n.23.2. Pp-number の生成</h4>
|
||
<p>## 演算子の operand はマクロ展開されないので、この例の xglue() のようにもう1つの一見意味がなさそうなマクロをかぶせて使うことが、しばしば行われます。これは引数中のマクロを展開してから連結するためです。このサンプルのマクロ呼び出しで生成された 12e+2 という sequence は有効な pp-number です。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.23.3. ## の前後にトークンがない・object-like マクロ</h4>
|
||
<p>置換リスト中の ## 演算子の前後には必ず何かの pp-token がなければなりません。これは object-like マクロの例です。Object-like マクロで ## を使うことは無意味ですが、それだけではエラーではありません。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.23.4. ## の前後にトークンがない・function-like マクロ</h4>
|
||
<p>これは function-like マクロの定義で置換リスト中の ## 演算子の前後に pp-token のない例です。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h3><a name="3.4.24" href="#toc.3.4.24">3.4.24. # 演算子を使ったマクロ</a></h3>
|
||
<p># 演算子は C90 で導入されたものです。Function-like マクロを定義する #define 行の置換リストでだけ使われます。# 演算子の operand はパラメータで、そのマクロの展開時に対応する実引数が文字列リテラルに変換されます。*1</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.3.2 The # operator #演算子
|
||
C99 6.10.3.2 The # operator
|
||
</pre>
|
||
|
||
<h4>n.24.1. 引数の文字列化</h4>
|
||
<p># 演算子の operand に対応する引数は両端を " と " で囲まれて文字列化されます。</p>
|
||
<p>配点 : 6。"a + b" とトークン間に space が挿入されるものは 2 点。</p>
|
||
|
||
<h4>n.24.2. 引数トークン間の white spaces の扱い</h4>
|
||
<p># 演算子の operand に対応する引数が複数の pp-token の sequence で成り立っている場合は、それらの pp-token 間の white spaces は a space に変換されてから文字列化され、white spaces がない場合は space は挿入されません。すなわち、white spaces の数には影響されないものの、その有無によって結果が違ってきます(ここに character-oriented なプリプロセスの尻尾が残っている)。引数の前後の white spaces は削除されます。</p>
|
||
<p>配点 : 4。</p>
|
||
|
||
<h4>n.24.3. \ の挿入</h4>
|
||
<p># 演算子の operand に対応する引数の中に文字列リテラルまたは文字定数がある場合は、その中にある \, " および文字列リテラルを囲む " の文字の直前に \ が挿入されます。これは文字列リテラルや文字定数をそのままの形で表示するための文字列リテラルを書く方法と同じです。</p>
|
||
<p>配点 : 6。</p>
|
||
|
||
<h4>n.24.4. <backslash><newline> を含むマクロ呼び出し</h4>
|
||
<p><backslash><newline> の sequence は translation phase 2 で削除されますから、マクロ展開時には残っていません。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>n.24.5. マクロ展開の結果の token separator を残さない</h4>
|
||
<p>一般にマクロ展開ではその結果が前後の pp-token とくっついてしまわないように何らかの token-separator を前後に挿入することが行われますが(<a href="#3.4.21">3.4.21</a> 参照)、しかし、マクロ展開の結果を文字列化する場合は、それらは削除しなければなりません。<br>
|
||
規格が token-based な処理を原則としながら character-oriented な部分を持っているために、こういうややこしい問題が発生します。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.24.6. # 演算子の operand がパラメータ名でない</h4>
|
||
<p># 演算子の operand はパラメータ名でなければなりません。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h3><a name="3.4.25" href="#toc.3.4.25">3.4.25. マクロ引数中のマクロの展開</a></h3>
|
||
<p>Function-like マクロの呼び出しに際して、引数中にマクロが含まれていた場合にそれをいつ展開するかについては、K&R 1st. では触れられていませんでした。Pre-C90 の処理系での実装もまちまちだったようです。置換リストの再走査時に展開するものが多かったかもしれません。Standard C ではこれは、引数を同定したあと、パラメータと置き換える前に展開すると規定されました。関数呼び出しでの引数の評価と同様の順序で、わかりやすくなりました。ただし、引数が #, ## 演算子の operand であるパラメータに対応するものの場合は、そこにマクロ名が含まれていても、それはマクロ呼び出しとはみなされず、展開されません。*1</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.3.1 Argument substitution 実引数の置換
|
||
C99 6.10.3.1 Argument substitution
|
||
</pre>
|
||
|
||
<h4>n.25.1. 引数中のマクロは先に展開される</h4>
|
||
<p>引数中のマクロは、引数が同定されたあとで展開され、それから置換リスト中のパラメータと置き換えられます。したがって、1つの引数として同定されたものは、たとえ展開すると複数の引数のような形になっても1つの引数です。</p>
|
||
<p>配点 : 4。</p>
|
||
|
||
<h4>n.25.2. 0個のトークンに展開される引数</h4>
|
||
<p>同様に、展開すると0個のトークンになってしまう引数も、合法的なものです。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>n.25.3. ## 演算子を使うマクロを引数中で呼び出す</h4>
|
||
<p>## 演算子の operand がパラメータの場合、それに対応する引数はマクロ展開されないので、マクロ展開したい場合はもう1つのマクロをネストさせる必要があります。<br>
|
||
この例では、xglue() は ## 演算子を使っていないので、その引数である glue( a, b) はマクロ展開されて ab となり、xglue() の置換リストは glue( ab, c) となります。これが再走査されて abc が最終結果となります。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>n.25.4. ## 演算子の operand は展開されない</h4>
|
||
<p>こちらは glue() を直接呼び出しているので、その引数中にマクロ名があっても展開されません。</p>
|
||
<p>配点 : 6。</p>
|
||
|
||
<h4>n.25.5. # 演算子の operand は展開されない</h4>
|
||
<p># 演算子の operand であるパラメータに対応する引数も、マクロ展開されません。</p>
|
||
<p>配点 : 4。</p>
|
||
|
||
<h4>e.25.6. 引数中のマクロの展開が引数の範囲で完結しない</h4>
|
||
<p>引数中のマクロの展開はその引数の中だけで行われます。完結しないマクロは violation of constraint です。Function-like マクロの名前はそれだけではマクロ呼び出しではありませんが、それに ( が続くと、それがマクロ呼び出し sequence の開始となります。開始されたら最後、この ( に対応する ) がなければなりません。*1</p>
|
||
<p>配点 : 4。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.3 Macro replacement マクロ置換え -- Constraint 制約
|
||
C99 6.10.3 Macro replacement -- Constraints
|
||
</pre>
|
||
|
||
<h3><a name="3.4.26" href="#toc.3.4.26">3.4.26. マクロ再走査中の同名マクロ</a></h3>
|
||
<p>マクロ定義が再帰的になっている場合、そのマクロをそのまま展開してゆくと無限再帰になってしまいます。このため、K&R 1st. では再帰的なマクロを使うことができませんでした。Standard C では無限再帰を防ぎながら再帰的マクロの展開を可能にするため、一度置換したマクロ名は再置換しないという規定が設けられました。この規定はかなり難解ですが、次のようにパラフレーズできます。*1</p>
|
||
<ol>
|
||
<li>マクロ A の置換リストの再走査中にまた A という名前が見つかっても、それは再置換しない。<br>
|
||
<li>マクロ A の置換リスト中にマクロ B の呼び出しがあり、マクロ B の置換の途中に A という名前が出てくるネストされた置換の場合も、その A は再置換しない。<br>
|
||
<li>ただし、マクロ B の置換で元のマクロ A の置換リストの後ろのトークン列が取り込まれた場合は、この取り込まれた部分にある A は置換する。ただし、置換するのは、この A が(何らかのマクロの置換リスト中ではなく)ソース中にある場合に限る。<br>
|
||
<li>マクロ B の置換についても、1から3を再帰的に適用する。すなわち、マクロ B の置換の途中にまた B が現れ、それが元の B の置換リストの後ろのソース中のトークン列にあるのでなければ、この B は再置換しない。<br>
|
||
<li>元のマクロ A の呼び出しの引数中にマクロ C の呼び出しがあり、その置換中に現れた C が1から3を適用して再置換を禁止された場合、この C は元のマクロ A の置換リストの再走査でも再置換されない。<br>
|
||
</ol>
|
||
<p>これだけパラフレーズしてもまだ難解です。ことに3は、後続のトークン列の取り込みという伝統的なマクロ再走査仕様に由来するもので、これが問題を無用に煩雑にしています。これについては規格は corrigendum を出したり再訂正したりしてきていますが、こんがらがる一方です。また、後続するトークン列がソース中にある場合とそうでない場合とで展開方法を変えるというのも、一貫性のない仕様です。*2</p>
|
||
<p>後続トークン列を取り込むマクロ展開というものが異常なものである上に、その部分にいったん再置換を禁止されたマクロが再度現れるというのは二重に異常なケースです。この Validation Suite では、この二重に異常なマクロについてのテスト項目は n.27.6 だけしかありません。「後続トークン列の取り込み」というマクロ展開の異常な規定が削除されることを、期待したいと思います。*3</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.3.4 Rescanning and further replacement 再走査と再置換
|
||
C99 6.10.3.4 Rescanning and further replacement
|
||
</pre>
|
||
<p>*2 <a href="#2.7.6">2.7.6</a> 参照。</p>
|
||
<p>*3 再帰的マクロの展開に関する規格の仕様については、主に2通りの解釈があり、 newsgroup comp.std.c で論争が何回か行われている。recurs.t はこの論争の素材の1つである。recurs.t 中のコメントを参照されたい。このサンプルは評点の対象とはしない。</p>
|
||
<p><b>mcpp</b> の Standard モードでは、再帰的マクロの展開については二つの方法を実装している。デフォルトでは同名マクロ再置換禁止の範囲を上記 1-5 の説明のとおり広くとり、このサンプルを NIL(42) と展開する。-@compat オプションを指定すると再置換禁止の範囲を狭くとり、42 と展開する。この仕様の違いが出てくるのは、上記の 3 で関数型マクロ A の呼び出しの前半部分が B の置換リスト中に現れる場合である。デフォルトでは、A の名前が B の置換リスト中に現れただけで A の再置換を禁止するが、-@compat オプションでは、A の名前だけでなく引数リストとそれを囲む '(' と ')' のすべてが B の置換リスト中に現れた場合だけ、再置換を禁止する。また、A の名前がソース中にあるかどうかで区別しない。</p>
|
||
|
||
<h4>n.26.1. 直接再帰の object-like マクロは再展開されない</h4>
|
||
<p>これは object-like マクロの直接再帰の例です。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>n.26.2. 間接再帰の object-like マクロも再展開されない</h4>
|
||
<p>Object-like マクロの間接再帰の例です。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>n.26.3. 直接再帰の function-like マクロも再展開されない</h4>
|
||
<p>Function-like マクロの直接再帰の例です。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>n.26.4. 間接再帰の function-like マクロも再展開されない</h4>
|
||
<p>Function-like マクロの間接再帰の例です。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>n.26.5. 引数中の再帰的マクロ</h4>
|
||
<p>Standard C には「一度再置換を禁止されたマクロは、別の文脈で再走査されても置換されない」という意味の難解な規定がありますが、具体的にこれに該当するのは引数中のマクロの親マクロ再走査時の扱いです。引数中に再帰的マクロがあった場合は、1度しか置換されませんが、それは親マクロの再走査でも置換されません。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h3><a name="3.4.27" href="#toc.3.4.27">3.4.27. マクロの再走査</a></h3>
|
||
<p>マクロの置換リストが再走査されるのは K&R 1st. 以来の仕様です。再走査時に発見されたマクロは再帰的マクロでない限り、置換されます。これによって、ネストされたマクロ定義が処理されます。Standard C では特に変更があったわけではありませんが、K&R 1st. では明確でなかったところがいくらか明確になっています。*1</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.3.4 Rescanning and further replacement 再走査と再置換え
|
||
C99 6.10.3.4 Rescanning and further replacement
|
||
</pre>
|
||
|
||
<h4>n.27.1. Object-like マクロのネスト</h4>
|
||
<p>これは K&R 1st. でも同じです。</p>
|
||
<p>配点 : 6。</p>
|
||
|
||
<h4>n.27.2. Function-like マクロのネスト</h4>
|
||
<p>これも K&R 1st. でも同じです。</p>
|
||
<p>配点 : 4。</p>
|
||
|
||
<h4>n.27.3. ## 演算子で生成された名前も展開の対象となる</h4>
|
||
<p>## 演算子の operand に対応する引数はマクロ展開されませんが、pp-token の連結によって生成された新たな pp-token は、再走査時にマクロ展開の対象となります。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>n.27.4. 置換リスト中に形成された function-like マクロ</h4>
|
||
<p>Function-like マクロの名前が出てきても、それに ( が続いていない場合は、それはマクロ呼び出しとはみなされません。引数から function-like マクロの名前が取得され、置換リスト中にその名前を使って function-like マクロの呼び出しが形成されると、それは展開されます。</p>
|
||
<p>配点 : 4。</p>
|
||
|
||
<h4>n.27.5. Function-like マクロの前半を形成する置換リスト</h4>
|
||
<p>置換リストが function-like マクロの呼び出しの前半を形成するという異常なマクロは、pre-Standard では暗黙の仕様でしたが、Standard C では公認されてしまいました。置換リストの後ろの pp-token 列が取り込まれて、マクロ呼び出しが完結します。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>n.27.6. 再置換される同名マクロ</h4>
|
||
<p>再走査では一般に同名のマクロは再置換されませんが、再置換される場合もあります。ネストされたマクロ呼び出しで再走査が置換リストの後ろのソース中の pp-token 列を取り込み、その中に同名マクロが現れるという異常な場合です(<a href=#3.4.26>3.4.26</a> を参照)。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.27.7. 再走査時に引数の数が合わない</h4>
|
||
<p>Function-like マクロの呼び出しでは引数の数がパラメータの数と一致していなければなりませんが、置換リストの再走査時に発見された function-like マクロでも、もちろん同様です。しかし、引数は , で区切られるものなので、トリッキーなマクロでは引数の数が直観的にわかりにくいことがあります。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h3><a name="3.4.28" href="#toc.3.4.28">3.4.28. 事前定義マクロ</a></h3>
|
||
<p>C90 では5つの特殊なマクロを処理系が事前定義することになりました。*1<br>
|
||
さらに C90 / Amendment 1 では、<tt>__STDC_VERSION__</tt> というマクロも追加されました。<br>
|
||
<tt>__FILE__</tt>, <tt>__LINE__</tt> は動的に定義が変化してゆく、きわめて特殊なマクロです。assert() マクロやデバッグ用ツール等での使い道があります。その他の標準事前定義マクロは1つの translation unit の処理中は変化しません。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.8 Predefined macro names あらかじめ定義されたマクロ名
|
||
|
||
*2 C99 6.10.8 Predefined macro names
|
||
</pre>
|
||
|
||
<h4>n.28.1. __FILE__</h4>
|
||
<p>プリプロセス中のソースファイル名を表す文字列リテラルに定義されます。
|
||
#include によって取り込まれたソースファイルではそのファイル名になるので、同一の translation unit 中でも変化します。</p>
|
||
<p>配点 : 4。"n_28.t" という文字列リテラルでなく n_28.t といった単なる名前になるものは 0 点。</p>
|
||
|
||
<h4>n.28.2. __LINE__</h4>
|
||
<p>プリプロセス中のソースファイルの行番号を表す10進定数に定義されます。行番号は1から始まります。行番号は物理行の番号です。</p>
|
||
<p>配点 : 4。行番号が 0 から始まるものは 2 点。</p>
|
||
|
||
<h4>n.28.3. __DATE__</h4>
|
||
<p>プリプロセスの行われている日付を表す文字列リテラルに定義されます。
|
||
asctime() 関数の生成するものとほぼ同じ "Mmm dd yyyy" の形ですが、日が 10 日未満の場合は dd の1ケタ目は 0 ではなく space になるところが違っています。</p>
|
||
<p>配点 : 4。10 日未満の場合の dd の1ケタ目が 0 となるものは 2 点。</p>
|
||
|
||
<h4>n.28.4. __TIME__</h4>
|
||
<p>プリプロセスの行われている時刻を表す文字列リテラルに定義されます。asctime() 関数の生成するものと同じ "hh:mm:ss" の形です。</p>
|
||
<p>配点 : 4。</p>
|
||
|
||
<h4>n.28.5. __STDC__</h4>
|
||
<p>C90, C99 準拠の処理系では定数 1 に定義されます。</p>
|
||
<p>配点 : 4。</p>
|
||
|
||
<h4>n.28.6. __STDC_VERSION__</h4>
|
||
<p>C90 / Amendment 1:1995 に対応した処理系では、これが 199409L に定義されます。*1</p>
|
||
<p>配点 : 4。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 Amendment 1 / 3.3 Version macro(ISO 9899:1990 / 6.8.8 への追加)
|
||
</pre>
|
||
|
||
<h4>n.stdmac. C99 の事前定義マクロ</h4>
|
||
<p>C99 では <tt>__STDC_VERSION__</tt> の値は 199901L です。
|
||
<br>
|
||
また、<tt>__STDC_HOSTED__</tt> という事前定義マクロが追加されました。
|
||
これは処理系が hosted implementation であれば 1 に、そうでなければ 0 に定義されます。</p>
|
||
<p>配点 : 4。各 2 点。</p>
|
||
|
||
<h4>n.28.7. インクルードされたファイル中の __LINE__ 等</h4>
|
||
<p><tt>__FILE__</tt>, <tt>__LINE__</tt> は translation unit ではなくソースファイルを対象とするので、include されたソース中ではその include ファイルの名前と行番号になります。</p>
|
||
<p>配点 : 4。行番号が 0 から始まるものは 2 点。</p>
|
||
|
||
<h3><a name="3.4.29" href="#toc.3.4.29">3.4.29. #undef</a></h3>
|
||
<p>#undef は K&R 1st. あるもので、大きな変化はありません。指定されたマクロの定義が取り消されます。マクロは同一の translation unit 中で、#define で定義されてから #undef で取り消されるまでの間が有効範囲です。*1</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.3.5 Scope of macro definitions マクロ定義の有効範囲
|
||
C99 6.10.3.5 Scope of macro definitions
|
||
</pre>
|
||
|
||
<h4>n.29.1. #undef によるマクロの取り消し</h4>
|
||
<p>#undef された後ではそのマクロ名はもはやマクロではありません。</p>
|
||
<p>配点 : 10。</p>
|
||
|
||
<h4>n.29.2. 定義されていないマクロの #undef</h4>
|
||
<p>マクロとして定義されていない名前を #undef することは許されています。
|
||
処理系はこれをエラーにしてはいけません。</p>
|
||
<p>配点 : 4。 </p>
|
||
|
||
<h4>e.29.3. 名前がない</h4>
|
||
<p>#undef 行には identifier が必要です。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.29.4. 余計なトークンがある</h4>
|
||
<p>#undef 行には1つの identifier 以外のものがあってはいけません。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.29.5. 引数がない</h4>
|
||
<p>#undef 行に引数がないのも violation of syntax rule です。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h3><a name="3.4.30" href="#toc.3.4.30">3.4.30. マクロ呼び出し</a></h3>
|
||
<p>マクロ呼び出しに際しては、改行は単なる white spaces の1つとして扱われます。
|
||
したがって、マクロ呼び出しは複数の行にまたがることができます。これは K&R 1st. では明確ではありませんでした。*1<br>
|
||
関数様マクロ呼び出しの引数というのは , で区切られたものです。しかし、( と ) のペアの中に入っている , は引数を区切るものとはみなされません。このことはここでは直接はテストしませんが、n.25. を初め、あちこちで暗黙のうちにテストされています。また、*.c のサンプルの多くは assert() マクロを使っているので、この点に関してはかなり複雑なテストがされることになります。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.3 Macro replacement マクロ置換え -- Semantics 意味規則
|
||
C99 6.10.3 Macro replacement -- Semantics
|
||
</pre>
|
||
|
||
<h4>n.30.1. マクロ呼び出しは複数行にまたがることができる</h4>
|
||
<p>配点 : 6。</p>
|
||
|
||
<h4>n.nularg. マクロ呼び出しのカラ引数</h4>
|
||
<p>C99 ではマクロ呼び出しでカラ引数が認められました。
|
||
これは引数の過少とは異なります。引数を分離する ',' は省略できません(パラメータが一つの場合はこの両者を区別できないが)。</p>
|
||
<p>配点 : 6。</p>
|
||
|
||
<h3><a name="3.4.31" href="#toc.3.4.31">3.4.31. マクロ呼び出しのエラー</a></h3>
|
||
<p>次はマクロ呼び出しのいくつかのエラーです。</p>
|
||
|
||
<h4>e.31.1. 引数が多すぎる</h4>
|
||
<p>引数の数がパラメータの数と違っているのは violation of constraint です。Undefined ではありません。*1</p>
|
||
<p>配点 : 2。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.3 Macro replacement マクロ置換え -- Constraint 制約
|
||
C99 6.10.3 Macro replacement -- Constraints
|
||
</pre>
|
||
|
||
<h4>e.31.2. 引数が足りない</h4>
|
||
<p>引数の数がパラメータの数より少ないのも violation of constraint です。<br>
|
||
C99 ではカラ引数が認められましたが、
|
||
これは引数の過小とは異なります。引数を分離する ',' が欠如していてはいけません。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.31.3. ディレクティブ行で完結しないマクロ呼び出し</h4>
|
||
<p>一般にはマクロ呼び出しは複数の行にまたがることができますが、# で始まる preprocessing directive line はその行(コメントを a space に変換した後の行)で完結するので、その中にあるマクロ呼び出しもその行の中で完結していなければなりません。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.vargs2. 可変引数マクロの引数がない</h4>
|
||
<p>C99 の可変引数マクロでは __VA_ARGS__ に対応する実引数は、少なくとも1つは必要です。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h3><a name="3.4.32" href="#toc.3.4.32">3.4.32. #if 式の文字定数</a></h3>
|
||
<p>#if 式では文字定数を使うことができます。しかし、その評価はほとんど implementation-defined で、ポータビリティはあまりありません。32.? では最も単純な1バイトの文字定数(single-byte character constant)を取り上げます。*1<br>
|
||
この Validation Suite の i_* のサンプルはすべて、#if 式での基本文字セットが ASCII であることを前提としています。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.1.3.4 Character constants 文字定数
|
||
C90 6.8.1 Conditional inclusion 条件付き取り込み -- Semantics 意味規則
|
||
C99 6.4.4.4 Character constants
|
||
C99 6.10.1 Conditional inclusion -- Semantics
|
||
</pre>
|
||
<p>以下、33, 34, 35 のテストの典拠も同じである。</p>
|
||
|
||
<h4>n.32.1. Character octal escape sequence</h4>
|
||
<p>文字定数では8進 escape sequence を使うことができます。
|
||
これに関してはどの処理系でも同じで、implementation-defined な部分はありません。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>n.32.2. Character hexadecimal escape sequence</h4>
|
||
<p>文字定数では16進 escape sequence も使うことができます。
|
||
これも implementation-defined な部分はありません。16進 escape sequence は K&R 1st. にはなかったものです。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>i.32.3. Single-byte character constant</h4>
|
||
<p>Escape sequence ではない1バイトの文字定数は単純なものですが、基本文字セットによって値が異なります。コンパイル時と実行時とで基本文字セットが違うクロスコンパイラでは、#if 式の評価ではそのどちらを使ってもかまわないことになっています。<br>
|
||
また、同じ基本文字セットであっても、符号の扱いは implementation-defined で、しかもコンパイラ本体(translation phase 7)とプリプロセス(phase 4)とで扱いが異なっていてもかまわないことになっています。<br>
|
||
したがって、#if 式で文字定数の値を見て基本文字セットを判定することは、保証されている方法ではありません。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>i.32.4. '\a', '\v'</h4>
|
||
<p>Standard C では '\a', '\v' という escape sequence が追加されました。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.32.5. Escape sequence の値が unsigned char の範囲外</h4>
|
||
<p>文字定数中の1つの escape sequence は1つの single-byte character の値を表すものなので、unsigned char の範囲になければなりません。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h3><a name="3.4.33" href="#toc.3.4.33">3.4.33. #if 式のワイド文字定数</a></h3>
|
||
<p>ワイド文字定数は Standard C で新設されたものです。
|
||
その値の評価は single-byte character constant にも増して implementation-defined で、バイトの評価オーダーさえも決められていません。<br>
|
||
ワイド文字には各種の encoding がありますが、それは <a href="#4.1">4.1</a> でとりあげます。
|
||
ここでは ASCII 文字に対応するワイド文字定数だけとりあげます。</p>
|
||
|
||
<h4>e.33.2. ワイド文字定数の値が範囲外</h4>
|
||
<p>ワイド文字定数でも16進または8進の escape sequence が使えますが、その値はワイド文字1字の値を符号なしで表現した範囲になければなりません。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h3><a name="3.4.35" href="#toc.3.4.35">3.4.35. #if 式の multi-character 文字定数</a></h3>
|
||
<p>文字定数には multi-character character constant というものもあります。Multi-byte character constant と紛らわしい用語ですが別の概念で、複数の character からなる文字定数を意味します。この character には、single-byte character, multi-byte character, wide character があり、それぞれに対応する multi-character character constant があるのです(規格書では character という用語は single-byte character の意味で使われているが、ここでは3種の文字を指す言葉として使う)。<br>
|
||
Multi-character character constant の使い道は特に何もなさそうです。これが K&R 1st. 以来認められているのは単に、character constant の型は int だから int の範囲に入るものであれば何でもかまわない、ということにすぎないと思われます。</p>
|
||
|
||
<h4>i.35.1. multi-character 文字定数の評価</h4>
|
||
<p>Single-byte character の multi-character character constant は、K&R 1st. からあったものです(A.16)。しかし、評価のバイトオーダーは K&R 1st. でも Standard C でも規定されていません。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>e.35.2. multi-character 文字定数の値が範囲外</h4>
|
||
<p>16進または8進 escape sequence による multi-character character constant の値は通常は int または unsigned int の範囲になければならないと考えられます。しかし、C90 では #if 式では int / unsigned int は long / unsigned long と同じ内部表現を持つかのように扱われるので、long または unsigned long の範囲にあるかどうかをチェックすればすむと考えられます。しかし、この点は規格は明確ではありません。値の評価の仕方そのものが implementation-defined であるので、範囲チェックも implementation-defined であるとも解釈できます。<br>
|
||
いずれにしても、このサンプルは long が64ビット以下の処理系ではどう評価しても unsigned long の範囲を超えるので、診断メッセージを出すのが適当でしょう。<br>
|
||
C99 では #if の型はその処理系の最大の整数型となりました。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>i.35.3. Multi-character ワイド文字定数の評価</h4>
|
||
<p>ワイド文字の multi-character constant というものも存在します。
|
||
評価の仕方はやはり全面的に implementation-defined ですが、対応する multi-byte character の multi-character constant と合っている必要があります。</p>
|
||
<p>配点 : なし。</p>
|
||
|
||
<h3><a name="3.4.37" href="#toc.3.4.37">3.4.37. Translation limits</a></h3>
|
||
<p>Standard C では、処理系が扱うことのできる各種 translation limits の最低限が規定されました。しかし、この規定はかなり緩やかなものです。すなわち、22種の限界値についてそれぞれ、それを満たす例を1つ以上含むプログラムを処理でき、実行できなければならない、というものです。この Validation Suite のサンプルを見ればわかるように、このプログラムは処理系にとって最も負担が少なくなるように、きわめて単純に非実際的に書くことができます。これらの translation limits が常に保証されるわけではないことに、注意してください。Translation limits の規定は目安にすぎないと言えます。このサンプルはプリプロセスの関係する8種の translation limits のテストだけをするものです。*1, *2, *3<br>
|
||
なお、これらのテストサンプルの一部は画面内におさまるように行を折り返しています。処理系によっては、行接続等の処理が正しく行えないためにこれらのテストに失敗することがあります(Borland C 等)。行接続等のテストがここでの目的ではないので、失敗した場合はエディタで行をつないで再テストしてください。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 5.2.4.1 Translation limits 翻訳限界
|
||
|
||
*2 C99 5.2.4.1 Translation limits
|
||
</pre>
|
||
<p>C99 では、translation limits は大幅に拡大されている。
|
||
C++ Standard ではさらに大きい(<a href=#4.6>4.6</a> 参照)。</p>
|
||
|
||
<h4>n.37.1. マクロ定義中の31個のパラメータ</h4>
|
||
<p>C90 では、マクロ定義中のパラメータは31個までが一応保証されています。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>n.37.2. マクロ呼び出し中の31個の引数</h4>
|
||
<p>同様に C90 では、マクロ呼び出し中の引数は31個までが一応保証されています。</p>
|
||
<p>配点
|
||
: 2。</p>
|
||
|
||
<h4>n.37.3. 31文字の識別子名</h4>
|
||
<p>C90 では、Translation unit の内部的な identifier(マクロ名もこれに含まれる)は先頭の 31 characters が有意であることが保証されています。プリプロセスはもちろん、31 バイトの名前を通す必要があります。*1</p>
|
||
<p>配点 : 4。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.1.2 Identifiers 識別子 -- Implementation limits 処理系限界
|
||
</pre>
|
||
|
||
<h4>n.37.4. 8重の条件取り込み</h4>
|
||
<p>C90 では、#if (#ifdef, #ifndef) section のネストは8重までが一応保証されています。</p>
|
||
<p>配点 : 2。
|
||
</p>
|
||
|
||
<h4>n.37.5. 8重の #include</h4>
|
||
<p>C90 では、#include のネストは8重までが一応保証されています。</p>
|
||
<p>配点 : 4。</p>
|
||
|
||
<h4>n.37.6. 32重のカッコ付き #if 式</h4>
|
||
<p>C90 では、式中の (, ) のネストは32重までが一応保証されています。
|
||
これは #if 式にも適用されると考えられます(#if 式では一般の式と違ってそこまで保証する必要はないとも思われる。しかし、#if 式は整数定数式で long / unsigned long だけで評価されることと、実行時環境への問い合わせを必要とせず、実行時や phase 7 と同じ評価の仕方でなくてもよいということだけが例外として規定されていて、他の側面では特別扱いされていないので、やや過剰な規定になっているところもある)。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>n.37.7. 509 バイトの文字列リテラル</h4>
|
||
<p>C90 では、文字列リテラルの長さは 509 バイトまでが一応保証されています。この長さはトークンの長さであり、char 配列の要素数ではありません。両端の " を含み、\n 等は2バイトと数えます。ワイド文字列リテラルでは L という prefix も含みます。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>n.37.8. 509 バイトの論理行</h4>
|
||
<p>C90 では、論理行の長さは 509 バイトまでが一応保証されています。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>n.37.9. 1024 個のマクロ定義</h4>
|
||
<p>C90 では、マクロ定義は 1024 個までが一応保証されています。しかし、translation limits の規定でもこれが最もあいまいなものです。このサンプルのように最も簡単なマクロばかりの場合と、長大なマクロを多く含む場合とで、処理系の必要とするメモリの量はまったく異なります。また、1024 個に事前定義マクロを含むかどうかでも、テストプログラムが違ってきます。実際のプログラムでは、ユーザプログラムでマクロを定義する前に、標準ヘッダで多くのマクロが定義されます。この規定はまさに大ざっぱな目安にすぎません。実際の限界はシステムの提供できるメモリ量によって決まるでしょう。</p>
|
||
<p>配点 : 4。</p>
|
||
|
||
<h4>n.tlimit. C99 の translation limits</h4>
|
||
<p>C99 では translation limits が大幅に拡大されました。</p>
|
||
<p>配点 : 以下のそれぞれについて 2。</p>
|
||
|
||
<h4>n.37.1.L. マクロ定義中の 127 個のパラメータ</h4>
|
||
|
||
<h4>n.37.2.L. マクロ呼び出し中の 127 個の引数</h4>
|
||
|
||
<h4>n.37.3.L. 63 文字の識別子名</h4>
|
||
|
||
<h4>n.37.4.L. 63 重の条件取り込み</h4>
|
||
|
||
<h4>n.37.5.L. 15 重の #include</h4>
|
||
|
||
<h4>n.37.6.L. 63 重のカッコ付き #if 式</h4>
|
||
|
||
<h4>n.37.7.L. 4095 バイトの文字列リテラル</h4>
|
||
|
||
<h4>n.37.8.L. 4095 バイトの論理行</h4>
|
||
|
||
<h4>n.37.9.L. 4095 個のマクロ定義</h4>
|
||
<br>
|
||
|
||
<h2><a name="3.5" href="#toc.3.5">3.5. 処理系定義部分のドキュメント</a></h2>
|
||
<p>Standard C には implementation-defined(処理系定義)と呼ばれる部分があります。
|
||
この部分の仕様は処理系によって異なります。しかし、処理系はその仕様をドキュメントに記載しなければならない、とされています。*1</p>
|
||
<p>Implementation-defined とされるものの中には、言語処理系そのものが決めるもののほかに、CPU やOSによって決まる部分も含まれています。クロスコンパイラの場合、CPU やOSは翻訳時と実行時とで異なる場合もあります。</p>
|
||
<p>以下の項目はプリプロセスに関する implementation-defined な部分について、処理系のドキュメントに記載があるかどうかをチェックするものです。プリプロセスですから、CPU やOSはもちろん翻訳時のものです。d.1.* はプリプロセス固有の仕様であり、d.2.* はコンパイラ本体の仕様とも関係のあるものです。しかし、#if での式の評価は、コンパイラ本体とは仕様が異なってもかまわないことになっています。</p>
|
||
<p>以下の項目のほかにも、#if 式の評価には implementation-defined な側面がいくつかあります。まず、文字セットです(基本文字セットが ASCII か EBCDIC か等)。また、整数の encoding(2の補数、1の補数、符号+絶対値)もそうです。さらに、通常の算術変換によって符号つき整数が符号なしに変換された結果もそうです。しかし、これらはいずれもマシンとOSで決まるものであるのでそちらのドキュメントがあれば足り、言語処理系のドキュメントには記載はなくてもかまわないと考えられます。したがって、ここではこれらは評点の対象としません。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 3 Definitions of Terms 用語の定義及び規約
|
||
C99 3 Terms, definitions, and symbols
|
||
</pre>
|
||
|
||
<h4>d.1.1. Header-name を構築する方法</h4>
|
||
<p>Header-name というのは特殊な pp-token です。
|
||
<, > または ", " で囲まれた sequence をどうやって header-name という1つの pp-token に結合するのかは implementation-defined となっています。実装上は ", " で囲まれたものは文字列リテラルという pp-token として扱えばすむので簡単なのですが、<, > で囲まれたものはきわめて特殊な問題を持っています。<stdio.h> は translation phase 3 で <, stdio, ., h, > という5つの pp-token にいったん分解された上で、phase 4 で1つの pp-token に合成されることになるからです。この部分がマクロで書かれている場合は、さらに微妙な問題が生じます。*1</p>
|
||
<p>配点 : 2。すなわち、この仕様が処理系のドキュメントに記載されていれば 2 点、記載されていなければ 0 点。<br>
|
||
なお、header-name 中の大文字・小文字の区別の有無やファイル名の規則も implementation-defined であるが、これはOSで決まることなので、言語処理系のドキュメントには必ずしも記載する必要はないと考えられる。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.2 Source file inclusion ソースファイル取り込み -- Semantics 意味規則
|
||
C99 6.10.2 Source file inclusion
|
||
</pre>
|
||
|
||
<h4>d.1.2. #include でヘッダをさがす方法</h4>
|
||
<p>#include 行から header-name が取り出された後、その header file をどうやってさがすのかも implementation-defined です。", " で囲まれた header-name の場合は、まず implementation-defined な方法でファイルがサーチされ、見つからなかった時は <, > で囲まれた header-name と同様にサーチされることになっています。ところがこの後者もまた implementation-defined なのです。一向に要領を得ない規定ですが、これは Standard C がOSについて前提を置くことができないため、こうした表現にならざるを得ないのです。<br>
|
||
ディレクトリ構造を持ったOSでは、前者はカレントディレクトリからの相対パスを、後者は処理系の既定のディレクトリをサーチすると解されます。ただし、前者では include 元のファイルからの相対パスをサーチする処理系もあります。Implementation-defined であるからには、これも間違いとは言えません。これについて Rationale は、カレントディレクトリからの相対パスでサーチする仕様が委員会の意図ではあるが、OSについて前提を置くことができないので明文化できなかった、と説明しています。*1<br>
|
||
また、前者のサーチはサポートされなくても良い(", " で囲まれた header-name も <, > とまったく同じに扱っても良い)とされています。後者は必ずしもファイルでなくてもよい、とされています。処理系に組み込みの header もありうるということです。*2</p>
|
||
<p>配点 : 4。すなわち、これらの header の捜し方がドキュメントに十分に記載されていれば 4 点、不十分な記載しかなければ 2 点、ほとんど記載されていなければ 0 点。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 ANSI C Rationale 3.8.2 Source file inclusion
|
||
|
||
*2 C90 6.8.2 Source file inclusion ソースファイル取り込み -- Semantics 意味規則
|
||
C99 6.10.2 Source file inclusion
|
||
</pre>
|
||
|
||
<h4>d.1.3. #include のネストの限度</h4>
|
||
<p>#include のネストがどれだけできるかは implementation-defined です。ただし、少なくとも C90 では8レベル、C99 では 15 レベルは保証しなければなりません。*1, *2</p>
|
||
<p>配点 : 2。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 5.2.4.1 Translation limits 翻訳限界
|
||
C99 5.2.4.1 Translation limits
|
||
|
||
*2 C90 6.8.2 Source file inclusion ソースファイル取り込み -- Semantics 意味規則
|
||
C99 6.10.2 Source file inclusion
|
||
</pre>
|
||
|
||
<h4>d.1.4. 実装されている #pragma sub-directive</h4>
|
||
<p>#pragma という preprocessing directive は処理系固有の拡張機能を指定するために用意されている directive です。プリプロセスにおいても、拡張機能はすべて #pragma sub-directive として実装しなければなりません。*1</p>
|
||
<p>配点 : 4。その処理系で有効な #pragma sub-directive について(プリプロセスのドキュメントでは少なくともプリプロセスのためのすべての #pragma sub-directive について)ドキュメントに十分な記載があれば 4 点、不十分な記載しかなければ 2 点、ほとんど記載がなければ 0 点。また、#pragma sub-directive 以外の処理系固有の directive がある場合は 2 点を減ずる(ただし、0 点が下限。Standard C に最も近い仕様を指定するオプションによって禁止される directive は含まない)。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.6 Pragma directive プラグマ指令
|
||
C99 6.10.6 Pragma directive
|
||
</pre>
|
||
|
||
<h4>d.pragma. #pragma でのマクロ展開</h4>
|
||
<p>C90 では、 #pragma 行の pp-token はマクロ展開の対象となりませんでした。しかし、C99 では #pragma に STDC という token が続くものはマクロ展開の対象となりませんが、それ以外の #pragma sub-directive をマクロ展開するかどうかは implementation-defined となりました。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4><a name="d.1.5">d.1.5. 事前定義マクロ</a></h4>
|
||
<p><tt>__FILE__</tt>, <tt>__LINE__</tt>, <tt>__DATE__</tt>, <tt>__TIME__</tt>, <tt>__STDC__</tt>, <tt>__STDC_VERSION__</tt> (C99 では <tt>__STDC_HOSTED__</tt> も)以外の事前定義マクロは implementation-defined です。それらは1つの _ に大文字が続く名前、または2つの _ で始まる名前でなければなりません。*1</p>
|
||
<p>配点 : 4。不十分な記載しかなければ 2 点、規定の制限に反する名前の事前定義マクロがある場合は 2 点を減ずる(ただし、0 点が下限。Standard C に最も近い仕様を指定するオプションによって禁止されるマクロは評価の対象としない)。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.8 Predefined macro names あらかじめ定義されたマクロ名
|
||
C99 6.10.8 Predefined macro names
|
||
</pre>
|
||
|
||
<h4>d.predef. C99 の事前定義マクロ</h4>
|
||
<p>C99 では、<tt>__STDC_IEC_559__</tt>, <tt>__STDC_IEC_559_COMPLEX__</tt>, <tt>__STDC_ISO_10646__</tt> というマクロが条件によって事前定義されるものとして追加されています。<br>
|
||
<tt>__STDC_IEC_559__</tt>, <tt>__STDC_IEC_559_COMPLEX__</tt> は IEC 60559 浮動少数点規格に合致する実装ではそれぞれ 1 に定義するとされています。この2つは浮動小数点演算のライブラリによって決まるもので、実際には <fenv.h> 等で定義するのが適当かもしれません。必ずしもプリプロセッサで事前定義する必要はないと考えられます。</p>
|
||
<p><tt>__STDC_ISO_10646__</tt> は wchar_t 型の文字の値がすべて ISO/IEC 10646(Unicode 系の Universal Character Set)の何らかのコード化された実装である場合は、準拠する ISO/IEC 10646 の amendment や corrigendum を含めた規格の年月を表す 199712L といった形の定数に定義するとされています。これも <wchar.h> 等で定義することが考えられ、プリプロセッサで事前定義する必要はなさそうです。<br>
|
||
しかし、どちらにしてもドキュメントで説明されていることは必要です。</p>
|
||
<p>配点 : 6。3つのそれぞれについて各 2 点。</p>
|
||
|
||
<h4>d.1.6. Phase 3 で white-spaces を圧縮するか</h4>
|
||
<p>Standard C では translation phase 3 で tokenization を行いますが、その時に <newline> 以外の white-space sequence を one space に圧縮するかしないかは implementation-defined とされています。*1<br>
|
||
しかし、これは通常はコンパイル結果には影響を与えない処理系の内部仕様なので、ユーザが関知する必要はないものです。行頭と行末の white-spaces にいたっては、削除してもかまいません。</p>
|
||
<p>ではこの規定は無用かと言うと、いつもそういうわけではなく、必要な場合が1つだけあります。それは preprocessing directive 行に [VT], [FF] がある場合です。これについては Standard C はわかりにくい規定の仕方をしています。一方でこれを violation of constraint とし、他方では上記の規定を設けているのです。すなわち、phase 3 で [VT], [FF] をその前後の space, tab と合わせて one space に圧縮することができ、その場合は phase 4 には [VT], [FF] は残らないが、圧縮しなかった場合はこれが残って violation of constraint となるのです。<br>
|
||
ドキュメントには、この [VT], [FF] の扱いが書かれていれば十分だと考えられれます。</p>
|
||
<p>配点 : 2。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 5.1.1.2 Translation phases 翻訳フェーズ 3
|
||
C90 6.8 Preprocessing directive 前処理指令 -- Constraints 制約
|
||
C99 6.10 Preprocessing directive
|
||
</pre>
|
||
|
||
<h4>d.ucn. 文字列化で UCN の \ を重ねるか</h4>
|
||
<p>UCN を含む pp-token を # 演算子によって文字列化する場合、UCN の \ を重ねるかどうかは implementation-defined です。*1</p>
|
||
<p>配点 : 2。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C99 6.10.3.2 The # operator
|
||
</pre>
|
||
|
||
<h4>d.2.1. #if: Single-character character constant の評価</h4>
|
||
<p>一般に文字定数の値の評価は implementation-defined です。
|
||
これにはいくつかの次元があります。</p>
|
||
<ol>
|
||
<li>基本文字セットは何か<br>
|
||
<li>多バイト文字・ワイド文字の encoding は何か<br>
|
||
<li>符号の扱いはどうか<br>
|
||
<li>複数バイトの文字定数の評価のバイトオーダーはどうか<br>
|
||
</ol>
|
||
<p>このうち 1 はハードウェアとOSで決まるものなので、ここでは評点の対象としません。問題は 2, 3, 4 です。<br>
|
||
1バイトの single-character の文字定数でも、符号の扱いは implementation-defined です。また、プリプロセスとコンパイルとで評価が違っていても良いことになっています。*1</p>
|
||
<p>配点 : 2。ドキュメントに記載がある場合、またはコンパイルフェーズでの評価についての記載があり #if 式でも同じ評価がされる場合は 2 点、正しい記載がない場合は 0 点。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.1.3.4 Character constants 文字定数
|
||
C90 6.8.1 Conditional inclusion 条件付き取り込み -- Semantics 意味規則
|
||
C99 6.4.4.4 Character constants
|
||
C99 6.10.1 Conditional inclusion -- Semantics
|
||
</pre>
|
||
|
||
<h4>d.2.2. #if: Multi-character character constant の評価</h4>
|
||
<p>'ab' といった multi-character 文字定数の評価には、バイトオーダーの問題があります。これも implementation-defined です。</p>
|
||
<p>配点 : 2。評点の仕方は d.2.1 と同様。</p>
|
||
|
||
<h4>d.2.3. #if: Multi-byte character constant の評価</h4>
|
||
<p>多バイト文字定数の評価は encoding の違いのほかに、符号の扱い、バイトオーダーの違いがあり、これらはいずれも implementation-defined です。</p>
|
||
<p>配点 : 2。評点の仕方は d.2.1 と同様。</p>
|
||
|
||
<h4>d.2.4. #if: ワイド文字 character constant の評価</h4>
|
||
<p>ワイド文字定数の評価にも encoding の違いのほかに、符号の扱い、バイトオーダーの違いがあり、これらはいずれも implementation-defined です。</p>
|
||
<p>配点 : 2。評点の仕方は d.2.1 と同様。</p>
|
||
|
||
<h4>d.2.5. #if: 負数の右シフト</h4>
|
||
<p>一般に負の整数の右ビットシフトで符号ビットがどう扱われるかは implementation-defined です。これは CPU の仕様にもよりますが、言語処理系の実装方法にもよると思われます。*1</p>
|
||
<p>配点 : 2。評点の仕方は d.2.1 と同様。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.3.7 Bitwise shift operators ビット単位のシフト演算子 -- Semantics 意味規則
|
||
C99 6.5.7 Bitwise shift operators -- Semantics
|
||
</pre>
|
||
|
||
<h4>d.2.6. #if: 負数の除算・剰余算</h4>
|
||
<p>一般に右辺または左辺の片方または双方が負の整数である場合の、除算と剰余算の結果は implementation-defined です。これは CPU の仕様にもよりますが、言語処理系の実装方法にもよると思われます。*1, *2</p>
|
||
<p>配点 : 2。評点の仕方は d.2.1 と同様。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.3.5 Multiplicative operators 乗除演算子 -- Semantics 意味規則
|
||
C99 6.5.5 Multiplicative operators -- Semantics
|
||
</pre>
|
||
<p>*2 C99 では、div(), ldiv() と同様に商は 0 の方向に切り捨てられることになった。</p>
|
||
|
||
<h4>d.2.7. 識別子の有効長</h4>
|
||
<p>マクロ名を含む identifier の先頭から何バイトまでが有意であるかは implementation-defined です。ただし、マクロ名と内部的識別子については、C90 では 31 バイト、C99 では 63 バイトは保証しなければなりません。*1</p>
|
||
<p>配点 : 2。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.1.2 Identifiers 識別子 -- Implementation limits 処理系限界
|
||
C99 6.1.2 Identifiers -- General -- Implementation limits
|
||
</pre>
|
||
|
||
<h4>d.mbident. 識別子中の multi-byte character</h4>
|
||
<p>C99 では、identifier 中に multi-byte character を使える実装も許されることになりました。これは implementation-defined です。*1</p>
|
||
<p>配点 : 2。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C99 6.1.2 Identifiers -- General
|
||
</pre>
|
||
<br>
|
||
|
||
<h1><a name="4" href="#toc.4">4. 規格で要求されていない諸側面の評価</a></h1>
|
||
<p>規格が処理系に対して要求していないことであっても、処理系の「品質」を評価するために重要なことがいろいろあります。この章では、これらの品質評価事項のテストを解説します。</p>
|
||
<br>
|
||
|
||
<h2><a name="4.1" href="#toc.4.1">4.1. Multi-byte character encoding</a></h2>
|
||
<p>Multi-byte character には各種の encoding があります。その仕様は implementation-defined ですが、ここでは処理系がどれだけ多様な encoding にどこまで対応しているかのテストを「品質」の問題としてとりあげます。<br>
|
||
この Validation Suite では、m_33, m_34, m_36 については数種の encoding に対応したサンプルを用意しています。処理系はそのシステムでの標準の encoding に対応していなければならないのはもちろんですが、多言語に対応したソースを処理するためには、多くの encoding に対応している必要があります。*1<br>
|
||
しかし、それをテストする方法はシステムや処理系によって異なり、簡単ではありません。</p>
|
||
<p>GCC は環境変数 LC_ALL, LC_CTYPE, LANG に対応して動作を変えることになっていますが、実装が中途半端であてにできません。また、GCC でこの機能が使えるかどうかは GCC 自身をコンパイルした時の configuration によって異なります。<br>
|
||
さらに GCC V.3.4 では multi-byte character の処理がガラリと変わりました。プリプロセスの最初に(translation phase 1 に相当する時期に)ソースファイルの encoding を UTF-8 に変換してしまいます。こうなると、#if 式中の文字定数は UTF-8 でないと評価できず、元の encoding とは関係なくなってしまいます。<br>
|
||
C++98 の規格にも同様の問題があり、translation phase 1 で multi-byte character を UCN に変換してしまうので、#if 式中の文字定数は UCN としてでないと評価できないことになります。<br>
|
||
規格と実装のいささか混乱した状況と、一般に #if 式中の文字定数というものの portability の無さと意味の無さを踏まえて、この Validation Suite では V.1.5 からは #if 式中の multi-byte/wide character の文字定数のテストは評点の対象からはずしました(m.33, m.34, m.36.1)。</p>
|
||
<p>Visual C++ には #pragma setlocale という便利なディレクティブがあるので、これが使えます。Windows では「地域と言語のオプション」によって使用言語を変更できることになっていますが、中途半端でありやっかいです。#pragma setlocale は Windows をいじらずに使えるので、プログラマにとっては便利なものです(Visual C++ 自身がそれをどこまで正しく実装しているかは別であるが)。<br>
|
||
私のテストした他の処理系では今のところ、その処理系でのデフォルトの encoding にしか対応していないようです。ライブラリに setlocale() 等の関数を持つ処理系は多くありますが、それはソースのプリプロセスやコンパイルには関係ありません。ここで必要なのは、処理系自身がソースの encoding を認識し処理する能力なのです。</p>
|
||
<p>注:</p>
|
||
<p>*1 C99 では、\u, \U で始まる Unicode の sequence が導入され、multi-byte / wide character との関係がきわめてわかりにくくなってしまっている。C++ Standard ではさらに複雑である。</p>
|
||
|
||
<h4>m.33. ワイド文字定数の評価</h4>
|
||
<p>ワイド文字定数については、<a href="#3.4.33">3.4.33</a> を参照してください。</p>
|
||
<p>配点 : なし。</p>
|
||
|
||
<h4><a name="m.34">m.34. #if 式の multi-byte 文字定数の評価</a></h4>
|
||
<p>#if 式では複数バイトの multi-byte character constant(多バイト文字定数)も使えます(規格書では multi-byte character という用語は single-byte character も含むものとして使われているが、ここでは混乱しないように、single-byte ではない multi-byte character を指すことにする)。しかし、この評価も single-byte character constant 以上に implementation-defined です。</p>
|
||
<p>配点 : なし。このテストは後述する <a href="#u.1.7">u.1.7</a> のテストと合わせて判断する必要がある。
|
||
単に文字の値を評価できただけでは、その encoding を認識していることにはならない。u.1.7 は multi-byte character がその encoding で認められる範囲に収まっているかどうかのテストである。m.34. で文字の値を正しく評価した上で、さらに u.1.7 で適切な診断メッセージを出して初めてその encoding を認識していると言える。</p>
|
||
|
||
<h4>m.36.1. Multi-byte character 中の 0x5c は escape 文字ではない</h4>
|
||
<p>(Multi-byte | wide) character の encoding が shift-JIS, Big-5, ISO-2022-* 等の場合は、文字の中に '\\' と同じ 0x5c の値を持つバイトが出てくることがあるため、わずらわしい問題が生じます。処理系はこれを escape 文字としての \ (backslash) と解釈してはいけません。1つの (multi-byte / wide) character は1つの文字であり、2つの single-byte character ではないからです。<br>
|
||
Multi-byte character に値が 0x5c のバイトがあっても、これを escape sequence の始まりと解釈してはいけません。</p>
|
||
<p>配点 : なし。</p>
|
||
|
||
<h4>m.36.2. # 演算子は multi-byte character に \ を挿入しない</h4>
|
||
<p># 演算子の operand に対応する引数中に 0x5c の値のバイトを持つ multi-byte character が含まれていても、そこに \ を挿入してはいけません。もっとも、コンパイラ本体が multi-byte character に対応していない場合にプリプロセッサで \ を挿入することで対応させるという方法がありますが、それはまた別の次元のことです。<br>
|
||
また、そもそもこの種の multi-byte character を含む文字定数や文字列リテラルの tokenization にも、その他のリテラルとは違った面倒な問題があります。<br>
|
||
ISO-2022-* で encode された multi-byte character には '\\' ばかりでなく '\'', '"' と一致する値のバイトも含まれるので、いい加減な処理をすると tokenization に失敗します。</p>
|
||
<p>配点 : 7。Shift-JIS, Big-5 の encoding について各 2 点。ISO-2022-JP については3つのサンプルについて各 1 点。<br>
|
||
なお、この項目は m_36_*.t だけでなく m_36_*.c もテストする必要がある。文字列化だけであれば正しく処理するプリプロセッサが、assert() マクロで 0x5c のバイトを持つ漢字を含む複雑な文字列リテラルを引数として同定することに失敗することがあるからである。m_36_*.c は文字列リテラルの tokenization のテストにもなっている。<br>
|
||
GCC 3.4-4.1 は encoding を UTF-8 に変換してしまうが、それを元の encoding に戻すことを指示すると失敗することが多い。勝手な変換は余計なお世話であるが、ここではそこまではテストしない。0x5c のバイトが正しく処理されれば合格とする(甘いテストである)。</p>
|
||
<br>
|
||
|
||
<h2><a name="4.2" href="#toc.4.2">4.2. Undefined behavior</a></h2>
|
||
<p>Standard C には undefined behavior という規定がたくさんあります。Undefined behavior を引き起こすのは、間違ったプログラムまたはデータ、あるいは少なくとも移植性のないプログラムですが、violation of syntax rule or constraint とは違って、処理系はこれに対して診断メッセージを出すことを義務付けられていません。これを処理する時は、処理系はどう処理してもかまわないのです。正しいプログラムとして何らかの reasonable な処理をしても良く、診断メッセージを出してエラーにしても良く、診断メッセージを出さずに処理を中止したり暴走したりしても規格違反ではありません。</p>
|
||
<p>しかし、処理系の「品質」を評価するには、undefined behavior が具体的にどういうものであるかが問題になります。処理系は何らかの診断メッセージを出すのが適当でしょう。エラーにするのであればもちろんのこと、正しいプログラムとして扱う場合でもウォーニングを出すことが、プログラムに移植性がないことを知らせるために有用です。暴走するのは論外です。</p>
|
||
<p>以下のテストでは、undefined behavior を引き起こすソースに対して処理系が適切な診断メッセージを出すかどうかをチェックします。診断メッセージはエラーでもウォーニングでもかまいません。もちろん、ウォーニングの場合は何らかの reasonable な処理をする必要があります。<br>
|
||
u.1.* はプリプロセス固有の問題で、u.2.* は定数式一般に共通する問題です。</p>
|
||
<p>配点 : 適切な診断メッセージが出されれば、以下の各項目のうち、特に断りのない項目については 1 点。見当外れな診断メッセージである場合や診断メッセージが出されない場合は 0 点。</p>
|
||
|
||
<h4>u.1.1. ソースファイルが <newline> で終わっていない</h4>
|
||
<p>空でないソースファイルの最後が <newline> でないのは、undefined behavior を引き起こします(もっとも、OSによってはファイル中に改行文字というデータは存在せず、ファイルを読み込む時に処理系によって自動的に付加されるのだそうである)。*1<br>
|
||
u.1.1, u.1.2, u.1.3, u.1.4 はいずれもソースファイルが完結していないものですが、translation unit がそのファイルで終わっている場合は、診断メッセージを出す処理系が多いと思われます。しかし、そうした処理系でも、このファイルが include されたものである場合、include 元のファイルと連続して処理されることで、正常なソースとして扱われる可能性があります。これも undefined behavior の一種であり、間違った処理ではありませんが、やはり診断メッセージを出すのが適当です。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 5.1.1.2 Translation phases 翻訳フェーズ
|
||
C99 5.1.1.2 Translation phases
|
||
</pre>
|
||
|
||
<h4>u.1.2. ソースファイルが <backslash><newline> で終わり</h4>
|
||
<p>ソースファイルが <backslash><newline> sequence で終わっているのは、undefined behavior を引き起こします。*1</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 5.1.1.2 Translation phases 翻訳フェーズ
|
||
C99 5.1.1.2 Translation phases
|
||
</pre>
|
||
|
||
<h4>u.1.3. ソースファイルがコメントの途中で終わっている</h4>
|
||
<p>ソースファイルがコメントの途中で終わっているのは、undefined behavior を引き起こします。これは実際にはコメントの閉じ忘れまたはコメントのネストです。*1</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 5.1.1.2 Translation phases 翻訳フェーズ
|
||
C99 5.1.1.2 Translation phases
|
||
</pre>
|
||
|
||
<h4>u.1.4. ソースファイルがマクロ呼び出しの途中で終わっている</h4>
|
||
<p>ソースファイルが完結しないマクロ呼び出しで終わっているのは、undefined だと考えられます。*1<br>
|
||
これはマクロの引数を閉じるカッコを忘れた場合などに発生するもので、診断メッセージは重要です。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.3.4 Rescanning and further replacement 再走査と再置換え
|
||
C99 6.10.3.4 Rescanning and further replacement
|
||
</pre>
|
||
|
||
<h4>u.1.5. 引用でないところに不正な character がある</h4>
|
||
<p>文字列リテラル、文字定数、header-name、コメント以外のところに書ける文字はごく限られています。大文字と小文字の alphabet、数字、29種の記号、6種の white space characters です。ソースですから当然のことです。*1<br>
|
||
ここでは、white space 以外のコントロールコードがあった場合のテストをします。コントロールコードはたとえ文字列リテラル等の中であっても不正だと考えられますが、これはコンパイラ本体でチェックされるはずであり、また一般に文字セットには locale-specific ないし implementation-defined な面が多く、範囲が必ずしも明確ではないので、ここではテストしません。また、漢字も上記以外の場所では undefined と考えられますが、同様の理由でテストしません。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 5.2.1 Character sets 文字集合
|
||
C99 5.2.1 Character sets
|
||
</pre>
|
||
C99 では UCN が追加された。
|
||
</p>
|
||
</blockquote>
|
||
|
||
<h4><a name="u.1.6">u.1.6. コントロール行中に [VT][FF] がある</a></h4>
|
||
<p>White space characters であっても、# で始まる preprocessing directive 行では [SPACE][TAB] 以外があると violation of constraint となります。しかし、これは translation phase 4 での話で、その前に phase 3 でそれらをその前後の <newline> 以外の sequence of white spaces とともに one space に圧縮することもでき、その場合は violation は発生しません。*1<br>
|
||
規定はそうですが、やはりこれには診断メッセージを出すのが適当でしょう。このテストは undefined behavior のテストではありませんが、他に分類しがたい特殊なものなので、便宜上ここに入れてあります。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 5.1.1.2 Translation phases 翻訳フェーズ
|
||
C99 5.1.1.2 Translation phases
|
||
C90 6.8 Preprocessing directives 前処理指令 -- Constraints 制約
|
||
C99 6.10 Preprocessing directives -- Constraints
|
||
</pre>
|
||
|
||
<h4><a name="u.1.7">u.1.7. 引用の中に不正な multi-byte character sequence</a></h4>
|
||
<p>文字列リテラル、文字定数、header-name、コメントの中であっても、multi-byte character として認められない sequence があると undefined です。Multi-byte character の第一バイトの次のバイトが第二バイトとして使えないものである場合です。*1</p>
|
||
<p>配点 : 9。7種の各 encoding のうち UTF-8 以外のものについて各 1 点、UTF-8 については 3 点。
|
||
UTF-8 については4つのテストがあるが、1つ目は正しい encoding であとの3つは illegal な encoding である。
|
||
3つの illegal encoding のうちの診断できた数を点数とする。
|
||
正しい encoding に見当外れな診断をするものは 0 点。
|
||
なお、<a href="#m.34">m.34</a> の説明を参照のこと。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 5.2.1.2 Multibyte characters 多バイト文字
|
||
C99 5.2.1.2 Multibyte characters
|
||
</pre>
|
||
|
||
<h4>u.1.8. 論理行が文字定数の途中で終わっている</h4>
|
||
<p>文字定数という pp-token はその論理行で完結していなければなりません。論理行中に対応する ' がない ' があると undefined です。*1<br>
|
||
#error 行には任意のメッセージを書くことができますが、それも形としては pp-token の並びになっていないといけないので、単独の apostrophe はいけません。このサンプルでは、コメントのつもりのところも文字定数の終わりの ' をサーチするために食われてしまうでしょう。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.1 Lexical elements 字句要素 -- Semantics 意味規則
|
||
C99 6.4 Lexical elements -- Semantics
|
||
</pre>
|
||
|
||
<h4>u.1.9. 論理行が文字列リテラルの途中で終わっている</h4>
|
||
<p>文字列リテラルもその論理行で完結していなければなりません。
|
||
単独の " は undefined です。*1<br>
|
||
かつては UNIX 系の多くの処理系では、行をまたぐ文字列リテラルというものが認められていたようです。いまだにそれをあてにしたソースが一部に見られます。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.1 Lexical elements 字句要素 -- Semantics 意味規則
|
||
C99 6.4 Lexical elements -- Semantics
|
||
</pre>
|
||
|
||
<h4>u.1.10. 論理行が header name の途中で終わっている</h4>
|
||
<p>#include の論理行で完結しない header-name も undefined です。*1</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.2 Source file inclusion ソースファイル取込み -- Semantics 意味規則
|
||
C99 6.10.2 Source file inclusion -- Semantics
|
||
</pre>
|
||
|
||
<h4>u.1.11. Header name 中に ', ", /*, \ がある</h4>
|
||
<p>Header-name という pp-token の中に ', /*, \ があると undefined です。<, > で囲まれる header-name の中に " がある場合も同様です(文字列リテラルの形の header-name では、初めから " は pp-token の終わりになってしまうので、エラー)。*1<br>
|
||
これらは \ 以外はいずれも、文字定数、文字列リテラル、コメントと紛らわしく、どちらにも解釈できるからです。</p>
|
||
<p>また、\ は escape sequence と紛らわしく、header-name には escape sequence は存在しないものの、これが header-name だとわかるのは translation phase 3 での tokenization が終わって phase 4 になってからであるので、やはり処理系は区別に困るのです。Escape sequence が処理されるのは phase 6 ですが、phase 3 でも \" を文字列リテラルの終わりではなく escape sequence と解釈するために escape sequence を認識することが必要だからです。</p>
|
||
<p>しかし、\ は Windows 等のOSでは正規の path-delimiter で、それらのOS上の処理系は当然、これを正しい文字として扱います(文字列リテラルの形の header-name でその最後の文字が \ である場合だけは別として)。Undefined behavior をもたらすのは多くの場合は間違ったプログラムですが、常にそうだというわけではありません。しかし、/ ですむところをわざわざ \ と書くのは、portability の上で感心しません。処理系はウォーニングを出すことが望まれます。他のOSではファイルをオープンしようとしてエラーになるでしょうから、必ずしもプリプロセスの tokenization で診断する必要はありません。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.1.7 Header names ヘッダ名 -- Semantics 意味規則
|
||
C99 6.4.7 Header names -- Semantics
|
||
</pre>
|
||
|
||
<h4>u.1.12. #include の引数が header name でない</h4>
|
||
<p>#include 行の引数が header-name でないのは undefined です。すなわち、文字列リテラルの形でもなく、<, > に囲まれたものでもなく、そのどちらかに展開されるマクロでもない場合です。*1</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.2 Source file inclusion ソースファイル取込み -- Semantics 意味規則
|
||
C99 6.10.2 Source file inclusion -- Semantics
|
||
</pre>
|
||
|
||
<h4>u.1.13. #include の引数に余計なトークンがある</h4>
|
||
<p>#include 行の引数は header-name 1つだけです。
|
||
それ以外の余計な pp-token があると undefined です。*1</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.2 Source file inclusion ソースファイル取込み -- Semantics 意味規則
|
||
C99 6.10.2 Source file inclusion -- Semantics
|
||
</pre>
|
||
|
||
<h4>u.1.14. #line の引数に行番号がない</h4>
|
||
<p>#line の引数に行番号がないのは undefined です(ファィル名は optional。行番号は第一引数でなければならない)。*1</p>
|
||
<p>注:</p>
|
||
<p>*1 以下、u.1.18 までの典拠はいずれも同じ。</p>
|
||
<pre>
|
||
C90 6.8.4 Line control 行制御 -- Semantics 意味規則
|
||
C99 6.10.4 Line control -- Semantics
|
||
</pre>
|
||
|
||
<h4>u.1.15. #line のファイル名引数が文字列リテラルでない</h4>
|
||
<p>#line の第二引数であるファイル名は文字列リテラルでなければなりません。<br>
|
||
これがワイド文字列リララルであった場合だけが violation of constraint で、他の #line の間違いはすべて undefined というのは、バランスを欠いた規定です。</p>
|
||
|
||
<h4>u.1.16. #line の引数に余計なトークンがある</h4>
|
||
<p>#line 行に3つ以上の引数があるのは undefined です。</p>
|
||
|
||
<h4>u.1.17. #line の行番号の引数が [1, 32767] の範囲にない</h4>
|
||
<p>C90 では、#line の行番号引数は [1, 32767] の範囲になければなりません。それ以外では undefined です。<br>
|
||
このサンプルでは、#line での指定はこの範囲にあるものの、その後でソースの行番号がこれを超えてしまったという場合のテストもしています。処理系によっては、ここで行番号が黙って wrap round したりしますが、やはりウォーニングを出すのが適当でしょう。</p>
|
||
<p>配点 : 2。3つのサンプルのうち1つまたは2つ診断できれば 1 点。</p>
|
||
|
||
<h4>u.line C99: #line の行番号の引数が範囲外</h4>
|
||
<p>C99 では、この範囲は [1, 2147483647] です。</p>
|
||
<p>配点 : 2。
|
||
3つのサンプルのうち1つまたは2つ診断できれば 1 点。</p>
|
||
|
||
<h4>u.1.18. #line の行番号の引数が10進数でない</h4>
|
||
<p>#line の行番号引数は10進数でなければなりません。16進数などは undefined です。</p>
|
||
|
||
<h4><a name="u.1.19">u.1.19. #if 行中に defined に展開されるマクロがある</a></h4>
|
||
<p>defined が演算子でありながら identifier と紛らわしい形をしていることは、さまざまな問題を引き起こします。Translation phase 3 ではいったん identifier として tokenize され、phase 4 でこれが #if 行中にあった場合に限って演算子として認識されるので、#define 行で defined に展開されるマクロを定義することも、ありえないことではありませんそして、#if 行に実際にこのマクロが現れた場合は、undefined です。それを展開した結果が演算子として扱われる保証はありません。*1</p>
|
||
<p>defined という名前のマクロを定義することはそれ自体が undefined ですが(<a href="#u.1.21">u.1.21</a> 参照)、実際にそういう例は見掛けません。しかし、置換リスト中に defined というトークンのあるマクロ定義は、時に見掛けます。処理系によっては、これについて特殊な処理をして合法的なものとして扱うものもありますが、論理的な仕様ではありません。</p>
|
||
<p>配点 : 2。2つのサンプルのうち1つだけ診断できた場合は 1 点。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.1 Conditional inclusion 条件付き取込み -- Semantics 意味規則
|
||
C99 6.10.1 Conditional inclusion -- Semantics
|
||
</pre>
|
||
|
||
<h4>u.1.20. #undef の引数が defined, __LINE__, etc. である</h4>
|
||
<p>#undef の引数が defined, <tt>__LINE__</tt>, <tt>__FILE__</tt>, <tt>__DATE__</tt>, <tt>__TIME__</tt>, <tt>__STDC__</tt>, <tt>__STDC_VERSION__</tt> であると undefined です。*1, *2, *3</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.8 Predefined macro names あらかじめ定義されたマクロ名
|
||
C99 6.10.8 Predefined macro names
|
||
|
||
*2 Amendment 1 / 3.3 Version macro
|
||
</pre>
|
||
<p>*3 C99 では、<tt>__STDC_HOSTED__</tt>, <tt>__STDC_ISO_10646__</tt>, <tt>__STDC_IEC_559__</tt>, <tt>__STDC_IEC_559_COMPLEX</tt> が追加された。</p>
|
||
|
||
<h4><a name="u.1.21">u.1.21. #define のマクロ名が defined, __LINE__, etc.</a></h4>
|
||
<p>#define で定義するマクロ名が defined, <tt>__LINE__</tt>, <tt>__FILE__</tt>, <tt>__DATE__</tt>, <tt>__TIME__</tt>, <tt>__STDC__</tt>, <tt>__STDC_VERSION__</tt> であると undefined です。*1, *2, *3</p>
|
||
<p>配点 : 2。3つのサンプルのうち2つ診断できた場合は 1 点。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.8 Predefined macro names あらかじめ定義されたマクロ名
|
||
|
||
*2 Amendment 1 / 3.3 Version macro
|
||
</pre>
|
||
<p>*3 C99 では、<tt>__STDC_HOSTED__</tt>, <tt>__STDC_ISO_10646__</tt>, <tt>__STDC_IEC_559__</tt>, <tt>__STDC_IEC_559_COMPLEX</tt> が追加された。</p>
|
||
|
||
<h4>u.1.22. ## 演算子によって不正な pp-token が生成された</h4>
|
||
<p>## 演算子によって pp-token が連結された結果は有効な(単一の)pp-token にならなければなりません。そうでない場合は undefined です。*1<br>
|
||
このサンプルでは pp-number という Standard C で新しく規定された pp-token を題材にしています。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.3.3 The ## operator ##演算子 -- Semantics 意味規則
|
||
C99 6.10.3.3 The ## operator -- Semantics
|
||
</pre>
|
||
|
||
<h4>u.concat. C99: ## 演算子によって不正な pp-token が生成された</h4>
|
||
<p>// は C99, C++ ではコメントマークですが、これは pp-token ではありません。## 演算子によってこの sequence を生成しようとしても、その結果は undefined です。<br>
|
||
そもそもコメントはマクロが定義されたり展開されたりする前に a space に変換されているので、マクロによってコメントを生成することはできません。</p>
|
||
|
||
<h4>u.1.23. # 演算子によって不正な pp-token が生成された</h4>
|
||
<p># 演算子による置換の結果は有効な(単一の)文字列リテラルにならなければなりません。そうならなかった場合は undefined です。*1<br>
|
||
これはめったに発生することのない問題です。このサンプルでわかるように、\ がリテラルの外にあるという奇妙な場合の、そのまたさらに特殊な場合に限られます。このサンプルも、プリプロセスで診断されなくてもコンパイルフェーズで診断されるでしょうし、それで十分です。しかし、処理系がダウンしたり、黙って通してしまったりするのはいただけません。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.3.2 The # operator #演算子 -- Semantics 意味規則
|
||
C99 6.10.3.2 The # operator -- Semantics
|
||
</pre>
|
||
|
||
<h4>u.1.24. マクロ呼び出しに空の引数がある</h4>
|
||
<p>関数様マクロの呼び出しに空の引数があるのは C90 では undefined です。*1<br>
|
||
空の引数を0個のトークンと解釈して reasonable なマクロ展開を行うことは、C99 で合法となったように十分可能で意味のあることです。*2<br>
|
||
これは undefined な規定に処理系が有意義な定義を与えることのできる1つの例です。しかし、そういう処理系であっても、少なくとも pre-C99 ではこれには移植性がないので、ウォーニングを出すのが適当でしょう。</p>
|
||
<p>配点 : 2。5つのサンプルのうち3つまたは4つ診断できた場合は 1 点。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.3 Macro replacement マクロ置換え -- Semantics 意味規則
|
||
|
||
*2 C99 6.10.3 Macro replacement -- Semantics
|
||
</pre>
|
||
|
||
<h4><a name="u.1.25">u.1.25. マクロ呼び出しにコントロール行類似の引数がある</a></h4>
|
||
<p>関数様マクロの呼び出しは複数の論理行にまたがることができます。
|
||
したがって、引数の中に preprocessing directive と紛らわしい行が含まれることがありえますが、その結果は undefined です。*1<br>
|
||
こうした「引数」は、スキップされる #if group の中にある場合は pre-processing directive 行と解釈されるでしょう。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.3 Macro replacement マクロ置換え -- Semantics 意味規則
|
||
C99 6.10.3 Macro replacement -- Semantics
|
||
</pre>
|
||
|
||
<h4><a name="u.1.26">u.1.26. Function-like マクロの名前で終わるマクロ展開</a></h4>
|
||
<p>C90 では、マクロ展開の結果が function-like マクロの名前で終わるのは undefined とされています。後で追加された解釈ですが、意味不明なものです。<a href="#2.7.6">2.7.6</a> を参照してください。*1<br>
|
||
この Validation Suite では V.1.2 まではこれも一応テストに含めていましたが、V.1.3 から削除しました。</p>
|
||
<p>注:</p>
|
||
<p>*1 ISO/IEC 9899:1990 / Corrigendum 1<br>
|
||
C99 ではこの規定は削除され、複雑な規定が追加されている。</p>
|
||
|
||
<h4><a name="u.1.27">u.1.27. 無効なディレクティブ</a></h4>
|
||
<p>行の最初の pp-token が # でその後にさらに pp-token がある場合は、通常は # の次は preprocessing directive でなければなりません。# だけの行はなぜか認められています。*1<br>
|
||
しかし、行頭の # に無効なディレクティブやその他の pp-token が続くことは、プリプロセスの violation of syntax or constraint ではありません。なぜなら、<a href="#u.1.25">u.1.25</a> でわかるように、# で始まる行がすべてプリプロセスディレクティブ行でなければならないわけではないからです。どれがプリプロセスディレクティブ行であるのかは、文脈によって決まるものなのです。</p>
|
||
<p>規格書はこれを undefined とも明記していませんが、「規定されていない」という意味でこれも undefined の一種です。処理系は何らかの診断メッセージを出すことが望まれます。ただし、これは必ずしもプリプロセスが診断する必要はありません。プリプロセスがこの行をそのまま出力すれば、コンパイルフェーズでエラーになるはずですから、それでもかまいません。プリプロセスが #ifdefined を #if defined と解釈してエラーにならずじまいなどということさえなければ、危険はありません。<br>
|
||
C99 では # non-directive という意味不明なものが追加されましたが、その内容は何も規定されておらず、事実上 undefined であると言えます。*2</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8 Preprocessing directives 前処理指令 -- Syntax 構文規則
|
||
|
||
*2 C99 6.10 Preprocessing directives -- Syntax
|
||
</pre>
|
||
|
||
<h4>u.1.28. directive 名にマクロは使えない</h4>
|
||
<p># で始まる行がプリプロセスディレクティブであるためには、次の pp-token としては directive 名しか許されません。Directive 名は決してマクロ展開されません。<br>
|
||
# の次に directive 名ではない identifier が来て、それがマクロ名であった場合は、存在しないディレクティブと診断するか、通常のテキストとみなしてマクロを展開して出力するかの、2つの処理がありえます。後者でも何らかの診断が望まれます。後者ではコンパイルフェーズでエラーになるはずですから、それでもかまいません。マクロ展開したものをさらに「正しい」プリプロセスディレクティブとして処理することだけは、あっては困ります。</p>
|
||
|
||
<h4>u.2.1. #if 式中に未定義の character escape sequence がある</h4>
|
||
<p>文字列リテラルまたは文字定数中の character escape sequence は \', \", \?, \\, \a, \b, \f, \n, \r, \t, \v だけが規定されています。それ以外の \ で始まる character sequence は undefined です。ことに \ に小文字が続く sequence は、将来 escape sequence を追加するために予約されています。*1<br>
|
||
これらの診断の多くはコンパイルフェーズにまかせればすみますが、#if 式の文字定数中にこれがあった場合だけは、プリプロセス以外に診断する者はいません。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.1.3.4 Character constants 文字定数 -- Description 補足規定
|
||
C99 6.4.4.4 Character constants -- Description
|
||
C90 6.9.2 Character escape sequences 文字拡張表記
|
||
C99 6.11.4 Character escape sequences
|
||
</pre>
|
||
<p>C99 では \uxxxx, \Uxxxxxxxx の形の UCN (universal-character-name) という escape sequence が追加された。</p>
|
||
|
||
<h4>u.2.2. #if 式中にシフトカウントが不正なビットシフト演算</h4>
|
||
<p>整数型のビットシフト演算では、右 operand(シフトカウント)が負値であったり左 operand の型のビット数以上であったりした場合は undefined です。*1<br>
|
||
#if 行にこれがあった場合はプリプロセスが診断すべきでしょう。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.3.7 Bitwise shift operators ビット単位のシフト演算子 -- Semantics 意味規則
|
||
C99 6.5.7 Bitwise shift operators -- Semantics
|
||
</pre>
|
||
<br>
|
||
|
||
<h2><a name="4.3" href="#toc.4.3">4.3. Unspecified behavior</a></h2>
|
||
<p>Standard C には unspecified という規定(「規定しない」という規定)もあります。これは、正しいプログラムであっても、その処理方法は規定しない、処理系は処理方法をドキュメントに書く必要もない、というものです。<br>
|
||
この例はあまり多くはありません。中でも、処理方法によって結果に違いが出るのは、きわめて特殊な場合だけです。しかし、特殊であっても、結果に違いの出る恐れのあるものに対してはウォーニングを出すのが望ましいと思われます。<br>
|
||
Unspecified な動作に依存するプログラムの結果は undefined です。</p>
|
||
<p>プリプロセスでは unspecified で、しかもその処理によって結果が違ってくるのは、次の2点です。この2つのテストではそれぞれ、不正な pp-token が生成されて診断メッセージが出されるか、それとも移植性がないというウォーニングが出されるか、どちらでも 2 点を与えることにします。また後者の場合は、マクロ定義時でも展開時でもどちらでも良いことにします。<br>
|
||
なお、そのほかに #if 式での演算の評価順序も unspecified ですが、#if 式はそれによって結果が変わることはないので、テストに含めていません。</p>
|
||
|
||
<h4>s.1.1. # 演算子と ## 演算子の評価順序は指定されていない</h4>
|
||
<p>1つのマクロ定義中に # 演算子と ## 演算子の双方があった場合、そのどちらが先に評価されるかは規定されていません。*1<br>
|
||
このサンプルは # と ## のどちらが先に評価されるかで結果が違ってくる例です。しかも、## が先に評価されると # は演算子ではなく単なる pp-token として扱われ、連結の結果は不正な pp-token が生成されてしまいます。こういうマクロには移植性がないので、処理系はウォーニングを出すのが適当です。</p>
|
||
<p>配点 : 2。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.3.2 The # operator #演算子 -- Semantics 意味規則
|
||
C99 6.10.3.2 The # operator -- Semantics
|
||
</pre>
|
||
|
||
<h4>s.1.2. 複数の ## 演算子の評価順序は指定されていない</h4>
|
||
<p>1つのマクロ定義中に複数の ## 演算子がある場合、その評価順序は規定されていません。*1<br>
|
||
このサンプルでは、評価する順序によっては途中で不正な pp-token が生成されます。こういうマクロには移植性がないので、処理系はウォーニングを出すのが適当です。</p>
|
||
<p>配点 : 2。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 6.8.3.3 The ## operator ##演算子 -- Semantics 意味規則
|
||
C99 6.10.3.3 The ## operator -- Semantics
|
||
</pre>
|
||
<br>
|
||
|
||
<h2><a name="4.4" href="#toc.4.4">4.4. ウォーニングの望ましいその他のケース</a></h2>
|
||
<p>Undefined, unspecified のほかにも、処理系がウォーニングを出すことが望ましいケースがいくつかあります。それらをここに集めました。</p>
|
||
<p>w.1.*, w.2.* は規格ではまったく正しいプログラムですが、実際には何らかの間違いである場合が多く、診断が重要なものです。w.1.* はプリプロセスに固有の問題で、w.2.* はコンパイルフェーズでの演算とも共通した問題の #if 式版です。</p>
|
||
<p>w.3.* は translation limits という implementation-defined な側面を持つ規定に関連したものです。規格で保証されている最小限の値を超える translation limits を実装することは、処理系の品質を上げることであるとも言えますが、しかし他方で、それに依存するプログラムは移植性が制限されてしまうという問題も発生します。したがって、最小限の値を超える translation limits を実装している処理系では、そのことを利用するプログラムに対してウォーニングを出すのが望ましいと考えられます。<br>
|
||
以下のテストでは、適切な診断メッセージを出せば合格とします。w.3.* は処理系の translation limits が規格の最小値と一致していて、そのためこれらのサンプルがエラーとなることも許容することにします。最小値に満たないためにエラーとなるのは不合格です(満たしているかどうかは n.37.* でわかる)。</p>
|
||
|
||
<h4>w.1.1. コメント中に /* がある</h4>
|
||
<p>コメントがネストされていたりコメントマークの片方がなかったりするソースのタイピングミスはよくあります。そのうち、/* /* */ */ というコメントのネスト、および */ だけがある場合は */ という sequence はC言語にはないので、コンパイルフェーズで必ずエラーになります。しかし、*/ が脱落している場合は次のコメントの終わりまでがコメントと解釈されるので、エラーにならないことがあります。これは危険な間違いであり、プリプロセスがウォーニングを出すことが大事です。コンパイルフェーズで何らかのエラーになった場合でも、その時には原因はわからなくなっています。</p>
|
||
<p>配点 : 4。</p>
|
||
|
||
<h4>w.1.2. マクロの再走査が後続のトークン列を取り込む</h4>
|
||
<p>マクロの再走査で置換リストの後ろのトークン列が取り込まれる場合があるのは、K&R 1st. では暗黙の仕様であり、Standard C では公認の仕様ですが、この事態を引き起こすのは尋常なマクロではありません。ことに置換リストが他の function-like マクロ呼び出しの前半部分を構成するのは、きわめて異常なマクロです。実際にはマクロ定義の間違いである可能性が大きく、ウォーニングを出すことが望まれます。Object-like マクロが function-like マクロの名前に展開されるものは実際にも時々見られますが、readability の悪い書き方です。</p>
|
||
<p>これについては規格のほうに問題があります。再走査が置換リスト内で完結しない場合はエラー(violation of constraint)とすべきであると思われます(<a href="#2.7.6">2.7.6</a> 参照)。</p>
|
||
<p>配点 : 4。2つのサンプルのうち1つだけ診断できた場合は 2 点。</p>
|
||
|
||
<h4>w.2.1. #if 式の通常の算術変換で負数が符号無し数に変換</h4>
|
||
<p>符号付き整数と符号無し整数との混合演算に際しては「通常の算術変換」が行われ、符号付きが符号無しに変換されます。元の符号付き整数が正数であった場合は値は変わりませんが、負数であった場合はこれが大きな正数に変換されます。これはエラーではありませんが、異常なものであり、何かの間違いである可能性があります。処理系がウォーニングを出すことが望まれます。プリプロセスではこの現象は #if 式に現れます。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>w.2.2. #if 式の符合なし演算が wrap round</h4>
|
||
<p>符号無し演算の結果が範囲外となった場合は wrap round することになっているので、エラーにはなりませんが、間違いの可能性があるのでウォーニングを出すのが望ましいでしょう。</p>
|
||
<p>配点 : 1。</p>
|
||
|
||
<h4>w.3.1. 31 個を超えるマクロパラメータ</h4>
|
||
<h4>w.3.2. 31 個を超えるマクロ引数</h4>
|
||
<p>w.3.? はいずれも C90 の translation limits に関するテストです。
|
||
内容は自明のことで、説明は要しないでしょう。n.37.* と比べてみてください。</p>
|
||
<p>配点 : 1。3.1., 3.2 は2つのどちらかを診断すればすむ。</p>
|
||
|
||
<h4>w.3.3. 31 バイトを超える長さの identifier</h4>
|
||
<p>配点 : 1。</p>
|
||
|
||
<h4>w.3.4. 8 レベルを超える #if (#ifdef, #ifndef) のネスト</h4>
|
||
<p>配点 : 1。</p>
|
||
|
||
<h4>w.3.5. 8 レベルを超える #include のネスト</h4>
|
||
<p>配点 : 1。</p>
|
||
|
||
<h4>w.3.6. #if 式中の 32 レベルを超える副式のネスト</h4>
|
||
<p>配点 : 1。</p>
|
||
|
||
<h4>w.3.7. 509 バイトを超える長さの文字列リテラル</h4>
|
||
<p>配点 : 1。これはプリプロセッサが診断しなくても、コンパイラ本体が診断すれば良しとします。</p>
|
||
|
||
<h4>w.3.8. 509 バイトを超える長さの論理行</h4>
|
||
<p>配点 : 1。</p>
|
||
|
||
<h4>w.3.9. 1024 個を超えるマクロ定義</h4>
|
||
<p>これだけは n.37.9 と同じものになっています。Translation limits の規定では、これが最もおおまかなものなのです。1024 個のマクロに組み込みマクロも数えるかどうか、標準ヘッダ中で定義されるマクロも数えるかどうかで、数が違ってきます。このサンプルでテストされるマクロはヘッダファイルの中では 1024 個目ですが、warns.t, warns.c の中にはそれ以前に定義されているマクロがいくつかあるので、このマクロはどちらにしても 1024 個目を超えることになります。処理系は適当なところでウォーニングを出すことが期待されます。</p>
|
||
<p>配点 : 1。</p>
|
||
|
||
<h4>w.tlimit. C99 の translation limits</h4>
|
||
<p>C99 では translation limits が大幅に拡大されました。
|
||
これを超える仕様を持つ処理系でも、portability のためには規定を超えるソースに対してはウォーニングを出すことが望まれます。</p>
|
||
<p>配点 : 8。各項目について、1 点ずつ。ただし、3.1, 3.2 はどちらかを診断できればすむ。3.7 はコンパイラ本体が診断するなら、それでも可。<br>
|
||
テスト用サンプルは test-l ディレクトリにある。なお、l_37_8.c はプリプロセスはできてもコンパイルはできない疑似ソースである。</p>
|
||
|
||
<h4>w.3.1.L. 127 個を超えるマクロパラメータ</h4>
|
||
|
||
<h4>w.3.2.L. 127 個を超えるマクロ引数</h4>
|
||
|
||
<h4>w.3.3.L. 63 バイトを超える長さの identifier</h4>
|
||
|
||
<h4>w.3.4.L. 63 レベルを超える #if (#ifdef, #ifndef) のネスト</h4>
|
||
|
||
<h4>w.3.5.L. 15 レベルを超える #include のネスト</h4>
|
||
|
||
<h4>w.3.6.L. #if 式中の 63 レベルを超える副式のネスト</h4>
|
||
|
||
<h4>w.3.7.L. 4095 バイトを超える長さの文字列リテラル</h4>
|
||
|
||
<h4>w.3.8.L. 4095 バイトを越える長さの論理行</h4>
|
||
|
||
<h4>w.3.9.L. 4095 個を超えるマクロ定義</h4>
|
||
<br>
|
||
|
||
<h2><a name="4.5" href="#toc.4.5">4.5. その他の各種品質</a></h2>
|
||
<p>以下には、処理系の使いやすさ等の品質に関する事項を集めています。q.1.1 以外はサンプルプログラムでテストすることのできないものです。</p>
|
||
<p>q.1.* は動作に関するものです。<br>
|
||
q.2.* はオプションや拡張機能に関するものです。<br>
|
||
q.3.* は稼働しうるシステムやシステム上での効率に関するものです。<br>
|
||
q.4.* はドキュメントに関するものです。</p>
|
||
<p>これらの中には、かなり主観的な評価にたよらざるをえないものもあります。
|
||
また客観的に評価できるものの、その尺度を明示できないものもあります。<a href="#6.2">6.2</a> の例を参考に、適当に採点してください。</p>
|
||
|
||
<h3><a name="4.5.1" href="#toc.4.5.1">4.5.1. 動作に関する品質</a></h3>
|
||
|
||
<h4>q.1.1. 規定を上回る translation limits</h4>
|
||
<p>Translation limits については、規格では最低限の仕様がゆるやかに規定されていますが、実際の処理系はこれを上回る仕様を持っていることが望まれます。ことに、#if のネストレベル、#include のネストレベルについては、C90 の要求は低すぎると考えられます。<br>
|
||
C99 では translation limits は大幅に引き上げられています。また、identifier の長さについては、255 バイト未満に制限しているのは obsolescent feature(廃止予定の規定)とされています。<br>
|
||
q.* の項目の中ではこれだけがテスト用サンプルが用意されています。test-l ディレクトリにある l_37_?.t, l_37_?.c で、それぞれ次のような translation limits をテストします。これは C99 のものをさらに上回っています(しかし、C++ Standard のガイドラインとしての translation limits の値は下回っている)。</p>
|
||
<blockquote>
|
||
<table>
|
||
<tr><th>37.1L</th><td>マクロ定義中のパラメータの数</td><td>255</td></tr>
|
||
<tr><th>37.2L</th><td>マクロ呼び出しの引数の数 </td><td>255</td></tr>
|
||
<tr><th>37.3L</th><td>identifier の長さ </td><td>255 bytes</td></tr>
|
||
<tr><th>37.4L</th><td>#if のネストレベル </td><td>255</td></tr>
|
||
<tr><th>37.5L</th><td>#include のネストレベル </td><td>127</td></tr>
|
||
<tr><th>37.6L</th><td>#if 式の副式のネストレベル </td><td>255</td></tr>
|
||
<tr><th>37.7L</th><td>文字列リテラルの長さ </td><td>8191 bytes</td></tr>
|
||
<tr><th>37.8L</th><td>ソースの論理行の長さ </td><td>16383 bytes</td></tr>
|
||
<tr><th>37.9L</th><td>マクロ定義の数 </td><td>8191</td></tr>
|
||
</table>
|
||
</blockquote>
|
||
<p>l_37_8.c はコンパイルしても実行プログラムにはなりません。プリプロセスするだけであれば結果を見ればわかりますが、コンパイルする場合は cc -c l_37_8.c 等としてオブジェクトファイルを作ってください。プリプロセスが成功すれば、コンパイラ本体がどれだけの長さの行を受け入れることができるかがわかります。</p>
|
||
<p>配点 : 9。9種のサンプルについて各 1 点。コンパイラ本体のテストは含まない。</p>
|
||
|
||
<h4>q.1.2. 診断メッセージの的確さ</h4>
|
||
<p>診断メッセージが出ることは出るものの、わかりにくかったり、おおまかすぎたり、問題の個所がわからなかったりするものもあります。ある種類の診断メッセージは詳しく出るが、別の種類の診断はピントがズレているという場合もあります。診断メッセージは単に "syntax error" などとするのではなく、なぜ間違いであるのかを示してもらいたいものです。個所は行を示して、さらに問題のトークンを指摘してもらいたいものです。<br>
|
||
また、#if section の対応関係のエラーでは、対応すべき行を教えてくれないと、どこにエラーの元があるのかがわかりません。<br>
|
||
同一のエラーに対していくつもの診断メッセージが重複して出るのは、好ましくありません。</p>
|
||
<p>配点 : 10。</p>
|
||
|
||
<h4>q.1.3. 行番号表示の正確さ</h4>
|
||
<p>プリプロセッサがコンパイラ本体に渡す行番号がズレてしまうのは困ります。
|
||
これは診断メッセージに現れるものですが、便宜上独立した項目にしておきます。この採点は、これまでのサンプルプログラムのテストで行番号が正確に表示されたかどうかで行います。</p>
|
||
<p>配点 : 4。</p>
|
||
|
||
<h4>q.1.4. 暴走・中断</h4>
|
||
<p>配点 : 20。この Validation Suite のどれかのサンプルで暴走したり、中断すべきでない処理を中断した処理系には、次のように減点する。「暴走」というのは、^C で中断せざるをえなくなったり、リセットせざるをえなくなったり、ファイルシステムに不整合を残したりするもので、「中断」というのは、これらの被害はないものの、処理の途中で終了してしまうものを指す。</p>
|
||
<ol>
|
||
<li>n_std.c(strictly conforming program)で暴走したら 0 点。<br>
|
||
<li>n_std.c で処理を中断したら 10 点(中断した個所の後の部分をさらにテストして暴走したら、「暴走」に分類)。<br>
|
||
<li>それ以外のどれかのサンプルで暴走したら 10 点。<br>
|
||
</ol>
|
||
|
||
<h3><a name="4.5.2" href="#toc.4.5.2">4.5.2. オプションと拡張機能</a></h3>
|
||
|
||
<h4>q.2.1. Include directory の指定</h4>
|
||
<p>標準ヘッダファイルの置かれるいわゆる include directory は最も単純な場合は1個所に固定されていますが、複数存在する場合もしばしばあり、ユーザが指定しなければならない場合もあります。また、ユーザレベルのヘッダファイルについては、カレントディレクトリにある場合は問題ありませんが、別のディレクトリにあって、それがさらに別のヘッダファイルを include する場合は、処理系によってディレクトリをサーチする規則が異なります。どちらにしても、include directory はオプションや環境変数によってユーザが明示的に指定することもできないと不便です(-I オプションを使う処理系が多い)。また、複数のディレクトリを順にサーチする場合は、システムで既定のディレクトリを外すオプションもないと不便です。サーチする規則そのものを変更するオプションにも、存在理由があります。</p>
|
||
<p>配点 : 4。</p>
|
||
|
||
<h4>q.2.2. マクロ定義オプション</h4>
|
||
<p>オブジェクト様マクロをソース中でなくコンパイル時に定義できるオプションは、有用なものです(-D オプションを使う処理系が多い)。これによって、同一のソースから違った仕様のオブジェクトを作ったり、違ったシステムでコンパイルしたりすることができます。置換リストを省略した場合は 1 に定義する処理系と0個のトークンに定義する処理系とがあり、使う時はドキュメントを確かめなければなりません。<br>
|
||
引数つきマクロの定義もオプションでできる処理系もあります。</p>
|
||
<p>配点 : 4。</p>
|
||
|
||
<h4>q.2.3. マクロ取り消しオプション</h4>
|
||
<p>処理系固有の組み込みマクロを取り消すオプションも、あったほうが良いでしょう。次のような種類があります。</p>
|
||
<ol>
|
||
<li>-U
|
||
といったオプションで1つのマクロを取り消すもの。<br>
|
||
<li>処理系固有の組み込みマクロを一括して無効にするオプション。
|
||
<br>
|
||
<li>Standard C で禁止されている組み込みマクロ(_ で始まらない unix 等)だけを一括して無効にするオプション。<br>
|
||
</ol>
|
||
<p>配点 : 2。この 1 または 2 のオプションがあれば 2 点。3 は <a href=#d.1.5>d.1.5</a> ですでに評価しているので、ここでは対象としない。</p>
|
||
|
||
<h4>q.2.4. Trigraphs オプション</h4>
|
||
<p>Trigraphs は必要な環境では常用するのでしょうが、多くの環境では必要がないのでほとんど使いません。これはコンパイル時のオプションで有効にしたり無効にしたりできるほうが、良さそうです。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>q.2.5. Digraphs オプション</h4>
|
||
<p>Digraphs も trigraphs と同様に、コンパイル時のオプションで有効にしたり無効にしたりできるほうが、良いでしょう。</p>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>q.2.6. ウォーニング指定オプション</h4>
|
||
<p>Violation of syntax rule or constraint ではないものに対するウォーニングは、なるべく多面的に出してくれたほうが役に立ちますが、場合によってはうるさいこともあります。ウォーニングのレベルを指定するオプション、または種類ごとにウォーニングを有効にしたり無効にしたりするオプションは、欲しいものです。</p>
|
||
<p>配点 : 4。</p>
|
||
|
||
<h4>q.2.7. その他のオプション</h4>
|
||
<p>プリプロセスにはそのほかにも有用なオプションがいくつかありえます。
|
||
やたらにオプションが多いのは煩雑ですが、ないと何かの時に不便なものもあります。比較的よくあるのは、行番号情報を出力しないオプション(-P が多い)で、これはC言語以外の目的に使うものだと思われます。コメントを削除せずに出力するものもあります。OSのコマンドプロセッサによっては、診断メッセージのリダイレクトを処理系のほうで実現する必要のある場合もあります。また、プリプロセスが独立したプログラムではない、いわゆる1パス・コンパイラでは、プリプロセス後の出力を指定するオプションがぜひ欲しいところです。<br>
|
||
C90 (C95), C99, C++ を区別するオプションは当然必要なものですが、さらに C99 と C++ の互換性を上げる(C++ のプリプロセスを C99 のものに近づける)オプションも有用でしょう。<br>
|
||
Makefile を作成するための、ソースファイルの依存関係記述行を出力するオプションを持っているプリプロセッサもあります。</p>
|
||
<p>配点 : 10。</p>
|
||
|
||
<h4>q.2.8. #pragma による拡張</h4>
|
||
<p>Standard C では、処理系固有の directive はすべて #pragma の sub-directive として実現することになっています。プリプロセスはたいていの #pragma をそのままコンパイラに渡しますが、一部の #pragma は自分自身が処理します。プリプロセスが処理する #pragma の例は多くはありません。</p>
|
||
<p>#pragma once と書いてあるヘッダファイルは何回 #include されても一度しか読まないというものがありますが、これは多重 include を避けるためだけでなく、処理速度を上げるために有効なものです。#pragma once を使わずに、ヘッダファイル全体が例えば</p>
|
||
<pre style="color:navy">
|
||
#ifndef __STDIO_H
|
||
#define __STDIO_H
|
||
...
|
||
#endif
|
||
</pre>
|
||
<p>といった皮でくるまれていると、自動的にこれを次回はアクセスしないようにする処理系もあります。</p>
|
||
<p><b>mcpp</b> にはヘッダファイルを "pre-preprocess" して、多くのヘッダファイルを1本にまとめてしまうという使い方のできる #pragma directive もあります。すなわち、#include で取り込まれるヘッダをいったんプリプロセスして出力し、そこに現れた #define directive をまとめて出力に追加するという機能です。元ソースのコンパイルではこれを include すれば足ります。こうして pre-preprocess したヘッダファイルは、コメントと #if がなくなるのでサイズが小さくなり、マクロ呼び出しもなくなります。アクセスするファイルも1つですみます。結果としてプリプロセスが速くなります。<br>
|
||
ヘッダの pre-compile という機能を持つ処理系もあります。これは主として C++ で巨大なヘッダファイルを処理するために考えられたもののようですが、pre-compiled header のサイズが元のヘッダファイルの合計よりも大きくなる傾向があり、少なくとも C では速度向上の効果はあまりないようです。Pre-compiled header の内容が compiler-proper の内部仕様に依存していて、ユーザには見えない black box になるところも、難点です。<br>
|
||
どちらにしても、以上はすべてプリプロセスを速くするためのもので、それ以外の意味はありません。したがって、これらの機能はここでは評価せず、<a href="#q.3.1">q.3.1</a> で評価することにします。</p>
|
||
<p>Multi-byte character の encoding を指定する #pragma を持つ処理系もあります。プリプロセッサやコンパイラにソースの文字の encoding を伝える方法としては、完全なものです。</p>
|
||
<p><b>mcpp</b> はプリプロセスをトレースしデバッグ情報を出力する #pragma を持っています。通常のデバッガではプリプロセスのデバッグはできないので、これはプリプロセッサでしかできない重要な機能です。この機能はオプションで指定するよりは #pragma を使うほうが、デバッグする個所を限定できるので使いやすくなります。</p>
|
||
<p>エラーやウォーニングのコントロールの指定のように、通常はコンパイル時オプションで指定することを #pragma で実装している処理系もあります。#pragma は Standard C 準拠の処理系であれば portability の問題がないことと、ソース上の場所を特定して指定できるという長所がありますが、それを変更する時はソースを書き替えることになるという短所もあります。実装するなら、コンパイル時オプションを実装した上でしてもらいたいものです。</p>
|
||
<p>それ以外の #pragma でプリプロセスで処理するものは、あまりありません。<br>
|
||
ところで、#pragma sub-directive は implementation-defined であるため同じ名前の sub-directive が処理系によって異なる意味を持つ恐れがあります。名前の衝突を避ける工夫が望まれます。GCC 3 では #pragma GCC poison というふうに GCC という名前で始まりますが、これは良い方法です。<b>mcpp</b> も V.2.5 からはこれをまねて #pragma MCPP debug というふうに MCPP という名前で始まるようにしました。</p>
|
||
<p>配点 : 10。</p>
|
||
|
||
<h4>q.2.9. 拡張機能</h4>
|
||
<p>拡張機能は #pragma で実装することになっていますが、それとは別にプリプロセスの新しい仕様の提案として #pragma ではないディレクティブが実装される場合もあります。</p>
|
||
<p>Linux のシステムヘッダでは GCC の #include_next が使われています。しかし、これはシステムヘッダが無用に複雑化していることによるもので、感心したことではありません。GCC / cpp にはそのほかにも #warning, #assert 等々の規格外のディレクティブがありますが、いずれもあまり必要性の感じられないものです。</p>
|
||
<p>GCC / cc1 では標準モードの動作のほかに traditional モードの仕様のオプションがあります。<b>mcpp</b> には多様な動作仕様のオプションがあります。こうした試みにはそれなりの意味があります。</p>
|
||
<p>配点 : 6。</p>
|
||
|
||
<h3><a name="4.5.3" href="#toc.4.5.3">4.5.3. 実行効率等</a></h3>
|
||
|
||
<h4><a name="q.3.1">q.3.1. 処理速度</a></h4>
|
||
<p>処理の正確さと診断の的確さとは最も重要なものですが、速度も速いにこしたことはありません。<br>
|
||
速度向上のための #pragma やオプションも動員して、結果としてどういう速度になるかを見ます。</p>
|
||
<p>配点 : 20。入力を出力にコピーするだけの、何もしないプログラムの速度を 20 点とし、それより相対的にどれだけ遅いかによって点を付ける。具体的な尺度は <a href="#6.2">6.2</a> の例を参考。絶対的な速度はもちろんハードウェアによって違うので、同程度のハードウェアを前提として比較する。また、同じプログラムを処理しても、読み込まれる処理系の標準ヘッダの量によって処理時間が違ってくる。<b>mcpp</b> を基準として比較するのが良い。<br>
|
||
時間を測るには、UNIX 系では time コマンドを使う(bash, tcsh では組み込みコマンドである)。Windows では、CygWIN が使えるなら、やはり bash に time コマンドがある。また、WindowsNT の「リソースキット」に TimeThis という同様のコマンドがある (*1)。これらが使えない環境では、tool/clock_of.c をコンパイルして使う(かなり不正確であるが)。<br>
|
||
最近のハードウェアではプリプロセスなど一瞬で終わってしまうことが多いので、なかなか時間を測ることができない。
|
||
Windows では Windows.h を include する処理が幸か不幸か重いので、これが使える。
|
||
UNIX 系では glibc のソースなどから重い処理を選んで使う。</p>
|
||
<p>注:</p>
|
||
<p>*1 WindowsNT のリソースキットのプログラムには WindowsXP では使えるものと使えないものとあるが、TimeThis は使えるようである。</p>
|
||
|
||
<h4>q.3.2. メモリ使用量</h4>
|
||
<p>メモリ使用量は少ないに越したことはありません。ことにシステムの与えるメモリ量に厳しい制限がある場合は、これは切実な問題となります。</p>
|
||
<p>プリプロセスはコンパイルの一部であるので、実際には処理系全体のメモリ使用量が問題となります。プリプロセスが独立したプリプロセッサによって行われる場合は、通常はコンパイラ本体のほうがメモリを食うので、プリプロセッサのメモリ使用量はあまり問題にならないでしょう。しかし、マクロ定義が多い場合など、プリプロセッサのほうがメモリを消費することもあります。メモリ使用量にはプログラムの大きさばかりではなく、データエリアの使用量も含まれます。</p>
|
||
<p>配点 : 20。
|
||
これを測るのははなかなか適当な方法がない。
|
||
UNIX 系では glibc や firefox のような大きなソースを make しながら top を立ち上げていると cc1 や mcpp のメモリ消費量はだいたいわかる。
|
||
Windows ではタスクマネージャを立ち上げて、Windows.h を include するとわかる。</p>
|
||
|
||
<h4><a name="q.3.3">q.3.3. 移植性</a></h4>
|
||
<p>プリプロセッサそのもののソースの移植性は、処理系の既存のプリプロセッサと取り替えようとする時や、自分自身をアプデートしたりカスタマイズしたりしようとする時に、問題となります。次のような点が評価の対象となるでしょう。</p>
|
||
<ol>
|
||
<li>ソースが公開されているか(公開されていなければ 0 点)。<br>
|
||
<li>多くの処理系・OSに対応しているか。<br>
|
||
<li>移植できる処理系・OSの範囲はどうか。どういう条件が前提されているか。<br>
|
||
<li>移植しやすいソースか。<br>
|
||
<li>移植のためのドキュメントが整備されているか。<br>
|
||
</ol>
|
||
<p>配点 : 20。しかし、私がソースを読んだのは1つ半くらいにすぎない。
|
||
後は眺めただけである。したがって、この採点はあてずっぽうである。</p>
|
||
|
||
<h3><a name="4.5.4" href="#toc.4.5.4">4.5.4. ドキュメントの品質</a></h3>
|
||
|
||
<h4>q.4.1. ドキュメントの品質</h4>
|
||
<p>d.* では Standard C の「処理系定義事項」についてのドキュメントがあるかないかだけを見ましたが、ここではドキュメントのその他の品質を評価します。<br>
|
||
ドキュメントとしては処理系定義事項のほかに、最低限、次のものが必要でしょう。</p>
|
||
<ol>
|
||
<li>Standard C との異同。<br>
|
||
<li>オプションの仕様。<br>
|
||
<li>診断メッセージの意味。<br>
|
||
</ol>
|
||
そのほか、Standard C の部分を含めたプリプロセス全体の仕様の解説もあれば、それに越したことはありません。<br>
|
||
これらについて、正確さ、読みやすさ、検索性、一覧性等が評価の対象となるでしょう。移植のためのドキュメントは <a href="#q.3.3">q.3.3</a> の評価に含めます。<br>
|
||
<p>配点 : 10。</p>
|
||
<br>
|
||
|
||
<h2><a name="4.6" href="#toc.4.6">4.6. C++ のプリプロセス</a></h2>
|
||
<p>C処理系が C++ 処理系といっしょに提供されることが多くなっていますが、その場合はプリプロセッサはCと C++ とで同じものが使われているようです。確かに両者のプリプロセスはほとんど同じなので、わざわざ別のプリプロセッサを用意する必要はないでしょう。しかし、両者はまったく同じではありません。</p>
|
||
<p>C++ Standard を C90 と比べると、C++ のプリプロセスは C90 のプリプロセスに次の仕様を付け加えたものになっています。</p>
|
||
<ol>
|
||
<li>Basic source character set に含まれない character は、translation phase 1 で \Uxxxxxxxx の形の Unicode の16進 sequence に変換する。そして、これは translation phase 5 で実行時文字セットに再変換する。*1<br>
|
||
<li>// から行末までをコメントとする。*2<br>
|
||
<li>::, .*, ->* をそれぞれ1つの pp-token として扱う。*3<br>
|
||
<li>#if 式では true, false を boolean literal として、それぞれ 1, 0 と評価する。*4<br>
|
||
<li>ISO C : Amendment 1 (1995) では標準ヘッダ <iso646.h> でマクロとして定義される 11 種の identifier 様 operator は、すべてマクロではなく token である(無意味な仕様)。(*3) 同様に、new, delete も operator である。*5<br>
|
||
<li>マクロ <tt>__cplusplus</tt> が 199711L に pre-define されている。*6<br>
|
||
<li><tt>__STDC__</tt> を定義するかどうか、定義するとすればどう定義するかは implementation-defined である(逆に C99 では <tt>__cplusplus</tt> を定義すると undefined である)。*6<br>
|
||
<li>Translation limits は次のように大幅に拡大されている。ただし、これはガイドラインであり、要求ではない。処理系は translation limits をドキュメントに明記しなければならない。*7<br>
|
||
<blockquote>
|
||
<table>
|
||
<tr><th>ソースの論理行の長さ </th><td>65536 バイト</td></tr>
|
||
<tr><th>文字列リテラル、文字定数、header name の長さ</th><td>65536 バイト</td></tr>
|
||
<tr><th>Identifier の長さ </th><td>1024 文字</td></tr>
|
||
<tr><th>#include のネスト </th><td>256 レベル</td></tr>
|
||
<tr><th>#if, #ifdef, #ifndef のネスト </th><td>256 レベル</td></tr>
|
||
<tr><th>#if 式のカッコのネスト </th><td>256 レベル</td></tr>
|
||
<tr><th>マクロのパラメータの数 </th><td>256 個</td></tr>
|
||
<tr><th>定義できるマクロの数 </th><td>65536 個</td></tr>
|
||
</table>
|
||
</blockquote>
|
||
<li>Header name は '.' の前の長さに(規格による)制限はなくなった。*8<br>
|
||
</ol>
|
||
<p>C99 ではこのうち2だけが同じ仕様になっていますが、他は異なります。また、C99 ではさらに、浮動小数点数の中の p+, P+ という sequence、identifier 中に multi-byte character を使える実装の公認、可変引数マクロ、カラ引数の合法化、 #pragma 行の引数のマクロ展開可、_Pragma() operator、#if 式の long long 以上での評価、隣接する wide-character-string-literal と character-string-literal の wide-character-string-literal としての連結等が加わって、新たな相違が発生しています。UCN は C99 では translation phase 5 だけの規定になりました。UCN に関する constraint も少し相違しています。Translation limits も C99 では C90 よりは大幅に拡大されたものの、C++ Standard ほど極端ではなく、ここでも違いが出てきています。</p>
|
||
<p>さほど大きな違いではないとも言えますが、それでもこれだけ違うと、CとC++ を同じプリプロセスですますわけにはいきません。Cについても、C90 (C95) と C99 とは同一のプリプロセスで間に合わせることはできません。</p>
|
||
<p>なお、C++ で <tt>__STDC__</tt> を事前定義しているのは間違いのもとであり、好ましいことではありません。<br>
|
||
<tt>__cplusplus</tt> については、これを -D オプションで定義する処理系もありますが、それではユーザ定義マクロの1つになってしまうので、不適切です。<br>
|
||
::, .*, ->* を1つの token として扱うかどうかは、プリプロセスではほとんど問題にならないことですが、正しく扱うに越したことはありません。</p>
|
||
<p>こういうことで、C90 (C95), C99, C++ の間でプリプロセッサを共通にするためには、実行時オプションで三者を区別し、それに応じて上記諸点の処理を変えるというのが、まともな実装だと思われます。</p>
|
||
<p>なお、<b>mcpp</b> では上記の仕様のうち次の2点は、効用に比べて実装の負担が大きすぎるため、対応していません。実用上はこれでほとんど問題ないと思われますが。</p>
|
||
<ol>
|
||
<li>Translation phase 1 での UCN への変換は実装していない。C++ Standard では、必ずしも UCN に変換しなくても、それと同じ結果になるなら良いとされている。しかし、実用上はともかく厳密には、変換せずに常に同じ結果になるはずはないのである。#if 式での UCN と multi-byte-character との文字定数の比較を考えればわかる。# 演算子による文字列化でも厳密に言えば、問題が生じる。*9<br>
|
||
<li>マクロの最大パラメータ数は 255 個までしか実装できない。<br>
|
||
</ol>
|
||
<p>以下に、C90 のプリプロセス規定に付加される C++ 独自のプリプロセス規定への適合性のテストを示します。<br>
|
||
Translation limits については test-l ディレクトリにあるもの以上のサンプルは用意していません。C++ では translation limits はガイドラインにすぎないということもあり、ここでは評点の対象としません。また、header name の長さは OS しだいなので、テストの対象としません。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C++ 2.1 Phases of translation
|
||
|
||
*2 C++ 2.7 Comments
|
||
|
||
*3 C++ 2.12 Operators and punctuators
|
||
|
||
*4 C++ 2.13.5 Boolean literals
|
||
</pre>
|
||
<p>なお、C99 では <stdbool.h> で bool, true, false, __bool_true_false_are_defined をマクロとしてそれぞれ _Bool, 1, 0, 1 と定義することになっている。</p>
|
||
<pre>
|
||
*5 C++ 2.5 Alternative tokens
|
||
|
||
*6 C++ 16.8 Predefined macro names
|
||
|
||
*7 C++ Annex B Implementation quantities
|
||
|
||
*8 C++ 16.2 Source file inclusion
|
||
</pre>
|
||
<p>C90 6.8.2 では、header name の '.' から左には6文字までしか保証されていなかった。C99 6.10.2 では8文字である。しかし、C++ ではこの制限が削除されている。</p>
|
||
<p>*9 C99 では、# 演算子によって UCN が文字列化された場合、\ を重ねるかどうかは implementation-defined である。C++ にはこの規定はない。<br>
|
||
重ねた場合は、その UCN はもはや multi-byte character に戻らないので、重ねないほうが良い実装である。しかし、C++ の規定では、重ねないのは「間違った」実装になってしまう。</p>
|
||
|
||
<h4>4.5.n.ucn1. UCN の認識</h4>
|
||
<p>配点 : 4。</p>
|
||
|
||
<h4>4.5.n.cnvucn. Multi-byte character の UCN への変換</h4>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>4.5.n.dslcom. // コメント</h4>
|
||
<p>配点 : 4。</p>
|
||
|
||
<h4>4.5.n.bool. true, false は boolean literal</h4>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>4.5.n.token1. ::, .*, ->* はトークン</h4>
|
||
<p>配点 : 2。テストが一見成功したようでも、Cで処理しても同じように、何のウォーニングも出ずにトークン連結が「成功」してしまうのでは、不可。</p>
|
||
|
||
<h4>4.5.n.token2. Operator の代替トークン</h4>
|
||
<p>配点 : 2。</p>
|
||
|
||
<h4>4.5.n.cplus. 事前定義マクロ __cplusplus</h4>
|
||
<p>配点 : 4。<tt>__cplusplus</tt> < 199711L の場合は 2 点。</p>
|
||
|
||
<h4>4.5.e.operat. identifier-like operator はマクロ名に使えない</h4>
|
||
<p>配点 : 2。2つともウォーニング等の診断メッセージが出れば 2 点。</p>
|
||
|
||
<h4>4.5.u.cplus. #define, #undef __cplusplus</h4>
|
||
<p>配点 : 1。ウォーニング等の診断メッセージが出れば 1 点。</p>
|
||
|
||
<h4>4.5.d.tlimit. Translation limits のドキュメント</h4>
|
||
<p>配点 : 2。</p>
|
||
<br>
|
||
|
||
<h1><a name="5" href="#toc.5">5. Cプリプロセスの周辺</a></h1>
|
||
<p>Cプリプロセッサの規格準拠度や品質の問題とは別に、実際にプリプロセッサを使う時に出会う周辺の問題を、以下に取り上げてみます。</p>
|
||
<br>
|
||
|
||
<h2><a name="5.1" href="#toc.5.1">5.1. 標準ヘッダファイル</a></h2>
|
||
<p>この Validation Suite のサンプルでは、いくつかの標準ヘッダを include しています。それらのヘッダが正しく書かれていないと、プリプロセッサそのもののテストが正確にできません。<br>
|
||
以下に、標準ヘッダの実装で問題の生じやすいところを見ていきます。</p>
|
||
|
||
<h3><a name="5.1.1" href="#toc.5.1.1">5.1.1. 一般的規約</a></h3>
|
||
<p>標準ヘッダは規定されている関数宣言や型定義・マクロ定義をすべて含んでいなければならないのはもちろんですが、さらに次のような条件を満たさなければならないことになっています。</p>
|
||
<ol>
|
||
<li>規定されておらずかつ予約されてもいない identifier を宣言したり定義したりしてはいけない。宣言できる範囲は標準ヘッダによって決まっている(複数の標準ヘッダで重複する、あるいは共通する範囲もある)。*1<br>
|
||
<li>したがって、1つの標準ヘッダが別の標準ヘッダを include するのは通常はいけない。<br>
|
||
<li>複数の標準ヘッダをどのような順序で include しても、同じ結果にならなければならない。*2<br>
|
||
<li>同じ標準ヘッダを複数回 include しても、<assert.h> 以外は同じ結果にならなければならない。*2<br>
|
||
<li>整数定数に展開される object-like マクロとして規定されているものはすべて #if 式でなければならない。*3<br>
|
||
</ol>
|
||
<p>予約される identifier の範囲は規定されており、それ以外の identifier はユーザに解放しておかなければなりません。'_' 1つまたは2つで始まる名前はすべて何らかの使用のために予約されているので、処理系が標準ヘッダ等で使うことができます(逆にユーザは '_' 1つまたは2つで始まる名前は定義してはいけない)。<br>
|
||
これは少々窮屈な規定です。規格外の伝統的な名前はすべて '_' で始まるものに変更しないと Standard C では使えないということになります。Standard C のライブラリや標準ヘッダの規定の出発点となった POSIX でも、Standard C の規格外の名前は</p>
|
||
<pre style="color:navy">
|
||
#ifdef _POSIX_SOURCE
|
||
...
|
||
#endif
|
||
</pre>
|
||
<p>で囲むことにしていますが、少なくともこの部分を使う時は処理系は Standard C ではなくなってしまいます。</p>
|
||
<p>しかも、例えば open(), creat(), close(), read(), write(), lseek() 等の関数名が標準ヘッダには現れていなくても、fopen(), fclose(), fread(), fgets(), fgetc(), fwrite(), fputs(), fputc(), fseek(), etc. の関数が open(), etc. を使って実装されていれば、間接的にユーザの名前空間を侵害することになります。したがって、表向きだけ open(), etc. を <tt>_POSIX_SOURCE</tt> で分けたり <unistd.h> 等の別ヘッダにしたりしても、意味がありません。<br>
|
||
こうした「システムコール関数」は '_' で始まる名前に変更されるか、それとも事実上必須のものは Standard C に取り入れられるか、どちらかを期待するしかありません。</p>
|
||
<p>2 は規約に明記されていることではありませんが、標準ヘッダが他の標準ヘッダを include すると、通常はその標準ヘッダが宣言できない名前が宣言される結果になるので、1 にひっかかります。<stddef.h> を各標準ヘッダが include したりするのは反則です。これを避けるためには <sys/_defs.h> といった別の名前の標準ではないヘッダを用意して、標準ヘッダが(<stddef.h> 自身も)これを include するようにすれば良いのです。そして、そこで使われる名前はすべて '_' で始まるものにすることです。*4, *5</p>
|
||
<p>3 は実際に問題となることはないでしょう。</p>
|
||
<p>4
|
||
は古い処理系では問題のあったところですが、現在の処理系ではほとんど対応できているようです。</p>
|
||
<pre style="color:navy">
|
||
#ifndef _STDIO_H
|
||
#define _STDIO_H
|
||
...
|
||
#endif
|
||
</pre>
|
||
<p>といった皮で標準ヘッダの全体をくるむ方法が一般的です。そのほか、#pragma once といった拡張 directive を使う方法もあります。</p>
|
||
<p>5 で問題となるのは、sizeof やキャストを使ったマクロが標準ヘッダに書かれている処理系があることです。Standard C では、#if 式に sizeof やキャストは使えません。実際に標準ヘッダのマクロで sizeof やキャストを使っている処理系では、#if でも sizeof やキャストが使えるようですから(Borland C 5.5)、これは拡張仕様のつもりなのでしょう。</p>
|
||
<p>ユーザが自分のプログラムで #if 行に sizeof やキャストを使わない限り、portability の問題が発生することはなく、他の問題も発生することは実際にはまずないでしょうが、しかしこのプリプロセスの実装は Standard C の「拡張」ではなく「逸脱」だと言わざるをえません。なぜなら、Standard C では #if を処理するのは translation phase 4 であり、この phase では keyword は存在しないのです。Keyword は phase 7 で初めて認識されます。Phase 4 では keyword と同じ名前はすべて単なる identifier として扱われ、#if 行ではマクロとして定義されていない identifier はすべて 0 と評価されるので、sizeof (int) は 0 (0) と、(int)0x8000 は (0)0x8000 となり、violation of syntax rule となります。これに対して処理系は診断メッセージを出さなければなりません。診断メッセージを出さないのは、Standard C の「拡張」ではなく「逸脱」です。また、そもそも phase 4 で一部の keyword だけ認識するというのは、プリプロセスの論理構成として無理があり、compile phase (translation phase 7) の "pre"process phase としての意味を混乱させるものだとも言えます。*6</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 7.1.3 Reserved identifiers 予約済み識別子
|
||
C99 7.1.3 Reserved identifiers
|
||
</pre>
|
||
<pre>
|
||
*2 C90 7.1.2 Standard headers 標準ヘッダ
|
||
C99 7.1.2 Standard headers
|
||
</pre>
|
||
<pre>
|
||
*3 C90 7.1.7 Use of library functions ライブラリ関数の使用法
|
||
C99 7.1.4 Use of library functions
|
||
</pre>
|
||
<p>*4 次の本ではこの方法が使われている。
|
||
この本はことに処理系の実装にとって、参考となる点が多い。</p>
|
||
<blockquote>
|
||
<p>P. J. Plauger "The Standard C Library", 1992, Prentice Hall</p>
|
||
</blockquote>
|
||
<p>*5 GNU の glibc のシステムでは、標準ヘッダ自身によって <stddef.h> 等の他の標準ヘッダが複数回読み込まれるようになっているが、その時に定義されるのは予約された範囲の名前だけのようである。これは規格違反とは言えない。しかし、標準ヘッダの readability を損ない、メンテナンスを困難にするので、良い方法ではない。<sys/_defs.h> といったファイルを使うほうが良い。</p>
|
||
<p>*6 <a href="#2.3">2.3</a>, <a href="#3.4.14">3.4.14</a> 参照。
|
||
</p>
|
||
|
||
<h3><a name="5.1.2" href="#toc.5.1.2">5.1.2. <assert.h></a></h3>
|
||
<p>次に個々の標準ヘッダを見ていきます。
|
||
いくつかの処理系に付属する標準ヘッダを見ると、最も問題の多いのは <assert.h> と <limits.h> のようです。この2つは最も簡単なヘッダなのですが、C90 で新しく規定されたものであるためか、実装を間違えることがあるようです。この2つについては、使い方にも少し触れます。</p>
|
||
<p>まず <assert.h> です。*1, *2<br>
|
||
他の標準ヘッダと違ってこれだけは、何回 include しても同じというわけではありません。NDEBUG というマクロをユーザが定義するかしないかによって、include するたびに結果が変わります。すなわち、このヘッダは必要に応じてあちこちで</p>
|
||
<pre style="color:navy">
|
||
#undef NDEBUG
|
||
#include <assert.h>
|
||
assert( ...);
|
||
</pre>
|
||
<p>という使い方をするのです。そして、デバッグのすんだところから</p>
|
||
<pre style="color:navy">
|
||
#define NDEBUG
|
||
#include <assert.h>
|
||
assert( ...);
|
||
</pre>
|
||
<p>としていきます。NDEBUG が定義されていれば、assert( ...); がソースに残っていても、マクロが展開されると消えてしまいます(... が副作用を持つ式であっても、式の評価がされないので、副作用は発生しないことに注意)。</p>
|
||
<p>こういう使い方ができるためには、<assert.h> は</p>
|
||
<pre style="color:navy">
|
||
#ifndef _ASSERT_H
|
||
#define _ASSERT_H
|
||
...
|
||
#endif
|
||
</pre>
|
||
<p>などという皮でくるんではいけません。
|
||
#pragma once なども、もちろん書いてはいけません。</p>
|
||
<p>また、このことからわかるように、assert() はマクロであり、NDEBUG によってその定義が変わります。<assert.h> はいちいち #undef assert とした上で、NDEBUG に応じて assert マクロを定義しなおす必要があります。<br>
|
||
assert( expression) という呼び出しでは、NDEBUG が定義されていなければ、expression が真であれば何もせず、偽であればその旨を標準エラー出力に報告します。この報告は、expression をそのまま字句通りに表示し(マクロがあっても展開せず)、そのソースのファイル名と行番号を表示します。これはプリプロセッサに # 演算子と <tt>__FILE__</tt>, <tt>__LINE__</tt> マクロが正しく実装されていれば、簡単に実現できます。</p>
|
||
<p>実際には古い処理系では、# 演算子が正しく実装されていなかったり、<assert.h> の理解を間違えているものが見られます。この Validation Suite のサンプルには <assert.h> を include しているものが多くありますが、<assert.h> が正しく書かれていないとプリプロセッサ自身のテストが正確にできません。正しい <assert.h> は簡単に書けるので、処理系付属のものがおかしければ書き直しておいたほうが良いでしょう。次のものは C89 Rationale 4.2.1.1 に例示されているものです。もちろんこれでも、# 演算子が正しく実装されていなければ正確な結果にはなりませんが、それはプリプロセッサの問題なので、やむをえません。</p>
|
||
<pre style="color:navy">
|
||
#undef assert
|
||
#ifndef NDEBUG
|
||
# define assert( ignore) ((void) 0)
|
||
#else
|
||
extern void __gripe( char *_Expr, char *_File, int _Line);
|
||
# define assert( expr) \
|
||
((expr) ? (void)0 : __gripe( #expr, __FILE__, __LINE__))
|
||
#endif
|
||
</pre>
|
||
<p>__gripe() という関数は次のように書けます(もちろん、__gripe という名前は '_' で始まるものなら何でもよい)。</p>
|
||
<pre style="color:navy">
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
|
||
void __gripe( char *_Expr, char *_File, int _Line)
|
||
{
|
||
fprintf( stderr, "Assertion failed: %s, file %s, line %d\n",
|
||
_Expr, _File, _Line);
|
||
abort();
|
||
}
|
||
</pre>
|
||
<p>__gripe() 等の関数を使わず fprintf() あるいは fputs() や abort() 等を直接 <assert.h> に書いている処理系も見られます。それもやってできないことではありませんが、そのためにはこれらの関数の宣言が必要です。FILE や stderr の宣言も必要です。しかし、<stdio.h> を include するわけにはいかないので、かなりやっかいです。別関数を実装するほうが間違いがありません。<br>
|
||
また、ささいなことですが、すべてをマクロで実現した場合は、呼び出しのたびに文字列リテラルが重複して生成されます。重複した文字列リテラルを1つにマージする最適化を処理系がしない場合は、コードサイズの上でも得策ではありません。</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 7.2 Diagnostics <assert.h> 診断機能<assert.h>
|
||
C99 7.2 Diagnostics <assert.h>
|
||
</pre>
|
||
<p>*2 C99 では、assert() マクロがどの関数から呼び出されたかも表示することになった。こうした用途のために、__func__ という内部識別子が規定されている。</p>
|
||
|
||
<h3><a name="5.1.3" href="#toc.5.1.3">5.1.3. <limits.h></a></h3>
|
||
<p>この標準ヘッダは整数型の範囲等を表すマクロを書いておくものです。これらのマクロの書き方は、その値が規定と合致していることはもちろんですが、さらに次の条件を満たしている必要があります。*1</p>
|
||
<ol>
|
||
<li>#if directive で使える整数定数式であること。<br>
|
||
<li>対応する型のオブジェクトが integral promotion を受けた結果の型と同じ型の式であること。<br>
|
||
</ol>
|
||
<p>キャストを使っている処理系がありますが、 #if directive での sizeof やキャストが Standard C の範囲にないことは、<a href="#5.1">5.1</a> で論じました。そもそも、<limits.h> が新しく規定された意味は、キャストや sizeof のような実行時環境に関する照会を、プリプロセッサがする必要がないようにすることにあります。<br>
|
||
例えば、</p>
|
||
<pre style="color:navy">
|
||
#if (int)0x8000 < 0
|
||
</pre>
|
||
<p>とか、
|
||
</p>
|
||
<pre style="color:navy">
|
||
#if sizeof (int) < 4
|
||
</pre>
|
||
<p>とかとする代わりに、
|
||
</p>
|
||
<pre style="color:navy">
|
||
#include <limits.h>
|
||
#define VALMAX ...
|
||
#if INT_MAX < VALMAX
|
||
</pre>
|
||
<p>とするのです。#if では <limits.h> のマクロを使えば、キャストや sizeof を使う必要はないはずです。</p>
|
||
<p><limits.h> のマクロが型を間違えている例は、時々見られます。これはそのプリプロセッサの仕様からくるものではなく、<limits.h> を書く人が上記の2. および integral promotion(汎整数拡張)と usual arithmetic conversion(通常の算術型変換)の規則、さらに整数定数トークンの評価の規則のどれかを失念することから起こるもののようです。</p>
|
||
<p>例えば、次のような定義をしているものがあります。</p>
|
||
<pre style="color:navy">
|
||
#define UCHAR_MAX 255U
|
||
</pre>
|
||
<p>unsigned char の値は(<tt>CHAR_BIT</tt> が 8 であれば)すべて int の範囲におさまるので、unsigned char 型のデータオブジェクトの値は integral promotion によって int になります。したがって <tt>UCHAR_MAX</tt> も int として評価されるものでなければなりません。ところが、255U では unsigned int になってしまいます。これは</p>
|
||
<pre style="color:navy">
|
||
#define UCHAR_MAX 255
|
||
</pre>
|
||
<p>としなければならないのです。</p>
|
||
<p>どちらでも実用上は問題ないのではないかと思うかもしれませんが、必ずしもそうではありません。符号無しの型を含む演算は「通常の算術変換」を引き起こし、同じサイズの符号付きの型の符号無しへの変換を強制します。そのため、大小比較の結果が違ってくるのです。</p>
|
||
<pre style="color:navy">
|
||
assert( -255 < UCHAR_MAX);
|
||
</pre>
|
||
<p>としてみると、わかるでしょう。</p>
|
||
<p>この間違いには、integral promotion と usual arithmetic conversion の規則が Standard C ではそれまでの多くの処理系で採用されていたものから変更された、という事情が関係しています。K&R 1st. には unsigned char, unsigned short, unsigned long という型はありませんでしたが、その後、多くの処理系に実装されるようになり、そしてそれらの処理系の多くでは、符号無しは常に符号無しに変換されることになっていました。<br>
|
||
しかし、Standard C の integral promotion では、unsigned char, unsigned short はそれぞれすべての値が int の範囲におさまる限りは int に promote され、そうでない場合だけ unsigned int に promote されます。また、unsigned int と long との間の usual arithmetic conversion では、unsigned int のすべての値が long の範囲におさまるのであれば long に、そうでない場合だけ unsigned long に変換されます。"unsigned preserving rules" から "value preserving rules" への変更と呼ばれるものです。このほうが直観的に意外性が少ないというのが、この規定の理由だとされています。<limits.h> では、この規則に注意が必要です。*2</p>
|
||
<p>以下の例ではすべて、short は16ビット、long は32ビットとします。<tt>USHRT_MAX</tt> の値は 65535 ですが、その書き方は int が16ビットの場合と32ビットの場合とで違ってきます。</p>
|
||
<pre style="color:navy">
|
||
#define USHRT_MAX 65535U /* int が16ビットの場合 */
|
||
#define USHRT_MAX 65535 /* int が32ビットの場合 */
|
||
</pre>
|
||
<p>unsigned short は int が16ビットの場合は int の範囲におさまらないので、unsigned int に promote されます。したがって、<tt>USHRT_MAX</tt> も unsigned int として評価されるものでなければなりません。65535 では long として評価されてしまいます。接尾子 'U' が必要なのです。他方で int が32ビットの場合は unsigned short の値はすべて int におさまるので、int に promote されます。したがって <tt>USHRT_MAX</tt> も int として評価されるものであることが必要です。'U' を付けてはいけません。ところが、これが逆になっている例が見られます。</p>
|
||
<pre style="color:navy">
|
||
#define USHRT_MAX 0xFFFF
|
||
</pre>
|
||
<p>これなら、int が16ビットでも32ビットでも正しく評価されます。Standard C では、U, u, L, l という接尾子がどれも付かない8進または16進の整数定数トークンは、int, unsigned int, long, unsigned long の順で、非負でその値を表現できる型に評価されます。すなわち、0xFFFF は int が16ビットなら unsigned int の 65535、int が32ビットなら int の 65535 と評価されるのです。これに対して、接尾子の付かない10進整数定数トークンは、int, long, unsigned long の順で評価されます。65535 は int が16ビットなら long、int が32ビットなら int で評価されることになります。*3</p>
|
||
<p>C99 では、 long long / unsigned long long が追加されました。0 か 1 の値しか持たない _Bool という型も追加されました。他の型の整数もオプションで実装できるようになりました。Integer promotion のルールも拡大され、unsigned long で表現できない整数定数トークンは long long / unsigned long long で評価されることになります。<br>
|
||
また、整数型が増え、処理系定義の整数型も認められたのに伴って、型の大小関係がわかりにくくなったため、integer conversion rank(整数変換ランク)という概念が導入されました。この概念はやや複雑ですが、実用上は気にする必要はありません。標準の整数型ではランクの大小関係は次の通りです。</p>
|
||
<p>long long > long > int > short > char > _Bool</p>
|
||
<p>ここで、たとえば long と int がともに 32 ビットというようなサイズの同じ実装であっても、ランクの大小は区別されるというところがポイントです。 *4, *5</p>
|
||
<p>注:</p>
|
||
<pre>
|
||
*1 C90 5.2.4.2.1 Sizes of integral types <limits.h> 各整数型の大きさ<limits.h>
|
||
C99 5.2.4.2.1 Sizes of integer types <limits.h>
|
||
|
||
*2 C90 6.2.1 Arithmetic operands 算術オペランド
|
||
C99 6.3.1 Arithmetic operands
|
||
|
||
*3 C90 6.1.3.2 Integer constants 整数定数
|
||
C99 6.4.4.1 Integer constants
|
||
|
||
*4 C99 6.4.4.1 Integer constants
|
||
</pre>
|
||
<p>*5 C99 では integer types の処理系による相違を吸収するための <stdint.h> および <inttypes.h> という標準ヘッダが追加されている。64 bits システムの登場によって integer types の種類が増えて、対応関係がわかりにくくなってきたため、long とか short とかの名前のほかにいくつかの型名を typedef しようというものである。しかし、この型名が 26 種もあり、それに対応する最大値・最小値を表すマクロが 42 種、対応する fprintf() の format specifier に変換されるマクロが 56 種、同様に fscanf() の format specifier に変換されるマクロが 56 種にも上る。処理系の負担はあまりないとは言え、煩雑すぎて、いささか末期症状の観を呈している。</p>
|
||
|
||
<h4>5.1.3.1. INT_MIN</h4>
|
||
<p><limits.h> のマクロのうちで最も混乱の見られるのが、2の補数の内部表現を持つシステムでの <tt>INT_MIN</tt> と <tt>LONG_MIN</tt> です。中でも int が16ビットで long が32ビットの処理系での <tt>INT_MIN</tt> が、上記の問題をすべて見せてくれます。そこで、特にこれを項を分けて取り上げてみます。<br>
|
||
この場合、int の範囲は [-32768,32767] であることは言うまでもありません。そして、<tt>INT_MAX</tt> についてはどの処理系も 32767 あるいは 0x7FFF としていて、問題ありません。ところが、<tt>INT_MIN</tt> が次のように定義されている例を見掛けます。</p>
|
||
<pre style="color:navy">
|
||
#define INT_MIN (-32767)
|
||
</pre>
|
||
<p>なぜこのような実際と違う定義をしているのでしょうか?<br>
|
||
他方で、さすがにこう定義している処理系は見当たりません。</p>
|
||
<pre style="color:navy">
|
||
#define INT_MIN (-32768)
|
||
</pre>
|
||
<p>-32768 は -, 32768 の2つのトークンから成っています。そして、32768 は int で表現できる範囲にありません。そこで、これは long で評価されます。したがって、-32768 は - (long) 32768 の意味になってしまうのです。<br>
|
||
中には、こう定義しているものもあります。</p>
|
||
<pre style="color:navy">
|
||
#define INT_MIN ((int)0x8000)
|
||
</pre>
|
||
<p>キャストを使った定義については、コメントは繰り返しません。0x8000 だけでは (unsigned) 32768 の意味になるので、やはり不可です。</p>
|
||
<p>では、どう定義すれば、キャストを使わずに (int) -32768 と評価されるのでしょうか?</p>
|
||
<pre style="color:navy">
|
||
#define INT_MIN (-32767-1)
|
||
</pre>
|
||
<p>これで良いのです。32767 は <tt>INT_MAX</tt> でも 0x7FFF でもかまいません。この定義には引き算という演算が含まれていますが、それは問題ありません(そもそも単項の - も演算子である)。*1, *2</p>
|
||
<pre style="color:navy">
|
||
#define INT_MIN (~INT_MAX)
|
||
#define INT_MIN (1<<15)
|
||
</pre>
|
||
<p>これらも正しい定義です。</p>
|
||
<pre style="color:navy">
|
||
#define INT_MIN (-32767)
|
||
</pre>
|
||
<p>これは演算をするという着想が浮かばなかったために、正しい値に定義することをあきらめたものと推測されます。</p>
|
||
<p>では、-32767 という定義は間違いでしょうか? それとも、これも正しい定義なのでしょうか?<br>
|
||
結論を言えば、これは間違いだと考えられます。<br>
|
||
<tt>INT_MIN</tt> は int の最小値を表すマクロと規定されています。もし <tt>INT_MIN</tt> が -32767 だとすると、これはいったい何を意味するものなのでしょうか? そして、<tt>INT_MIN</tt>-1 はいったい何なのでしょうか? あるいは ~<tt>INT_MAX</tt> や 1<<15 は何でしょうか?<br>
|
||
この場合の <tt>INT_MIN</tt>-1 については、浮動小数点演算の「非数」のような範囲外を表すビットパターンとする考え方があるようです。</p>
|
||
<p>しかし、整数型に関する Standard C の規定と照らし合わせると、この解釈は成り立つ余地がありません。まず、整数型についてのビット演算の結果は、op1 << op2 や op1 >> op2 の op2 の値が負数であるか op1 の型のビット数以上である場合が undefined ですが、それ以外には undefined な場合はなく、すべて整数型の一意の値を返します。~op は op が int であれば、その結果も int であり、op1 & op2, op1 | op2, op1 << op2, op1 >> op2 は op1, op2 がともに int であれば、その結果も int です。したがって、~<tt>INT_MAX</tt> も 1<<15 も結果は int なのです。1<<15 は overflow すると思うかもしれませんが、そうではありません。ビット演算ではビット操作をした結果のビットパターンに対応する値を返すので、overflow は発生しようがないのです。</p>
|
||
<p>Cでは一般に整数型の演算はよく定義されていて、undefined な部分はきわめて少ないものです。ことにビットパターンと値との関係は、1の補数や符号+絶対値の内部表現で 0 に2つのビットパターンがある以外は、完全に一対一に対応しています。これは K&R 1st. から Standard C まで一貫したものです。また、ビットパターンそのものを表記する方法はCにはなく、「非数」を表記しようにも (-32767-1) 等と書くしかありませんが、これは見ての通りの int の値そのものです。C89 Rationale はいくつかの根拠を挙げて、整数型には「無効な整数」とか「不正な整数」を表すビットパターンの存在する余地のないことを明らかにしています。*3, *4</p>
|
||
<p>こういうことで、2の補数の内部表現では ~<tt>INT_MAX</tt> が <tt>INT_MIN</tt> の値であり、それより大きい <tt>INT_MIN</tt> の定義は間違いだと言わざるをえません。</p>
|
||
<p>注:</p>
|
||
<p>*1 この定義を私が初めて見たのは、P. J. Plauger "The Standard C Library" である。最近の処理系にはこの流儀の limits.h が多くなっているようである。</p>
|
||
<p>ただ、この本の limits.h にも間違いがあるのである。int が16ビットで long が32ビットの処理系のための定義が次のようになっている。</p>
|
||
<pre style="color:navy">
|
||
#define UINT_MAX 65535
|
||
#define USHRT_MAX 65535
|
||
</pre>
|
||
<p>これでは long で評価されてしまう。正しくはこうである。</p>
|
||
<pre style="color:navy">
|
||
#define UINT_MAX 65535U
|
||
#define USHRT_MAX 65535U
|
||
</pre>
|
||
<p>*2 最近の処理系では *_MIN は (-*_MAX - 1) という形の定義が一般的になり、間違いは少なくなっている。しかし、まだ時に間違いが見られる。</p>
|
||
<p>Visual C++ 2003 の Vc7/include/limits.h, Vc7/crt/src/include/limits.h には次のようなものがある。</p>
|
||
<pre style="color:navy">
|
||
#define LLONG_MIN 0x8000000000000000
|
||
</pre>
|
||
<p>0x8000000000000000 は unsigned long long で評価される。この型は最高位ランクであるので、integer promotion の結果も同じ型である。マイナス値には決してならない。したがって、</p>
|
||
<pre style="color:navy">
|
||
#if LLONG_MAX > LLONG_MIN
|
||
</pre>
|
||
<p>は期待した結果にはならない。</p>
|
||
<p>LCC-Win32 2003-08, 2006-03 の include/limits.h の <tt>LLONG_MIN</tt> はこうなっている。</p>
|
||
<pre style="color:navy">
|
||
#define LLONG_MIN -9223372036854775808LL
|
||
</pre>
|
||
<p>9223372036854775808LL はこのトークンの値がすでに signed long long の範囲を overflow しており violation of constraint である。</p>
|
||
<pre style="color:navy">
|
||
#define LLONG_MIN -9223372036854775808LLU
|
||
</pre>
|
||
<p>とすれば 9223372036854775808LLU は unsigned long long になるが、符合無し型に unary - 演算を施しても、結果の型は変わらないので、unsigned long long で表現できない値となり、undefined である。</p>
|
||
<p>Visual C++ も LCC-Win32 も他の *_MIN の定義はすべて (-*_MAX - 1) となっているのであるが、<tt>LLONG_MIN</tt> だけ間違っているのはどうしたことであろうか。</p>
|
||
<pre style="color:navy">
|
||
#define LLONG_MIN (-LLONG_MAX - 1LL)
|
||
</pre>
|
||
<p>とすれば何も問題はない。</p>
|
||
<p>Visual C++ 2005 ではこれは修正された。</p>
|
||
<pre>
|
||
*3 C89 Rationale 3.1.2.5 Types
|
||
C99 Rationale 6.2.6.2 Integer types
|
||
</pre>
|
||
<p>*4 C99 では、特定のビットパターンを例外処理を引き起こす "trap representation" として扱うことも、処理系に許されることになっている。実際にどういう処理系が該当するのかは、私は知らない。</p>
|
||
|
||
<h3><a name="5.1.4" href="#toc.5.1.4">5.1.4. <iso646.h></a></h3>
|
||
<p>ISO C 9899:1990 / Amendment 1 では、iso646.h という標準ヘッダが追加されました。これは &, |, ~, ^, ! を含む演算子を ISO 646 の invariant character set だけで表す代替 spelling を提供するものです。|, ~, ^ は trigraphs でも代替 spelling が提供されていますが、trigraphs は readability に欠けるので、iso646.h はそれに代わって11種の演算子をトークン単位でマクロで定義しています。<br>
|
||
この実装はきわめて簡単で、次のようなもので十分です。プリプロセスでマクロ展開されるので、処理系にとっても何も面倒はありません。*1</p>
|
||
<pre style="color:navy">
|
||
/* iso646.h ISO 9899:1990 / Amendment 1 */
|
||
|
||
#ifndef _ISO646_H
|
||
#define _ISO646_H
|
||
|
||
#define and &&
|
||
#define and_eq &=
|
||
#define bitand &
|
||
#define bitor |
|
||
#define compl ~
|
||
#define not !
|
||
#define not_eq !=
|
||
#define or ||
|
||
#define or_eq |=
|
||
#define xor ^
|
||
#define xor_eq ^=
|
||
|
||
#endif
|
||
</pre>
|
||
<p>注:</p>
|
||
<p>*1 C++ Standard では、これらの identifier 様 operator はマクロではなく operater-token とされている。処理系にとってはやっかいな、無意味な仕様である。</p>
|
||
<br>
|
||
|
||
<h1><a name="6" href="#toc.6">6. 各種プリプロセッサのテスト結果</a></h1>
|
||
|
||
<h2><a name="6.1" href="#toc.6.1">6.1. テストしたプリプロセッサ</a></h2>
|
||
<p>テストした処理系と実行方法は次の通りです。処理系はリリースされた時期の順に並べてあります。<br>
|
||
実行時オプションは C95 (C90), C99, C++98 のそれぞれで少しずつ異なります。<br>
|
||
処理系付属の <assert.h>, <limits.h> に問題がある場合は、正しく書き直してからテストしています。</p>
|
||
<pre>
|
||
番号: OS / 処理系 / 実行プログラム(版数)
|
||
実行時オプション
|
||
コメント
|
||
|
||
1 : Linux / / DECUS cpp
|
||
C95: cpp
|
||
</pre>
|
||
<blockquote>
|
||
Martin Minow による DECUS cpp のオリジナル版 (1985/01)。当時の DEC の各種システム、UNIX, MS-DOS のいくつかの処理系に移植されているが、このテストで使ったのは、kmatsui がオリジナル版に手を加えて Linux / GCC でコンパイルしたもの。Translation limits がなるべく規定をクリアするようマクロを書き換えた。
|
||
</blockquote>
|
||
<pre>
|
||
2 : FreeBSD 2.2.7 / GCC V.2.7.2.1 / cpp (V.2.0)
|
||
GO32 / DJGPP V.1.12 / cpp (V.2.0)
|
||
WIN32 / BC 4.0 / cpp (V.2.0)
|
||
MS-DOS / BC 4.0, TC 2.0 / cpp (V.2.0)
|
||
MS-DOS / LSI C-86 V.3.3 / cpp (V.2.0)
|
||
OS-9/6x09 / Microware C/09 / cpp (V.2.0)
|
||
C95: cpp -23 (-S1 -S199409L) -W15
|
||
gcc -ansi -Wp,-2,-W15
|
||
C99: cpp -23 (-S1) -S199901L -W15
|
||
C++: cpp -23+ -S199711L -W15
|
||
</pre>
|
||
<blockquote>
|
||
kmatsui によるオープンソース・ソフトウェア (1998/08)。<b>mcpp</b> と呼ぶ。DECUS cpp をベースとして書き直したもの。
|
||
当時はこのバージョンは上記の処理系に対応していたが、今回は Linux 上で GCC でコンパイルしたものを使ってテストした。
|
||
</blockquote>
|
||
<pre>
|
||
3 : WIN32 / Borland C++ V.5.5J / cpp32 (2000/08)
|
||
C95: cpp32 -A -w
|
||
bcc32 -A -w
|
||
C99: cpp32 -A -w
|
||
C++: cpp32 -A -w
|
||
</pre>
|
||
<blockquote>
|
||
Trigraph は cpp も bcc も処理せず、その代わりに trigraph.exe という変換プログラムが用意されている。「規格準拠」と称するためのアリバイ作りである。Borland C では、これを使って予め変換したサンプルでテストした。ところが、この trigraph.exe は <backslash><newline> による行接続まで処理してしまう。そのため、行番号がズレる(q.1.2 のほうで減点)。
|
||
</blockquote>
|
||
<pre>
|
||
4 : Linux, CygWIN / GCC V.2.95.3 (2001/03) / cpp0
|
||
C95: cpp0 -D__STRICT_ANSI__ -std=iso9899:199409 -$ -pedantic -Wall
|
||
gcc -ansi -pedantic -Wall
|
||
C99: cpp0 -std=c9x -$ -Wall
|
||
C++: g++ -E -trigraphs -$ -Wall
|
||
</pre>
|
||
<blockquote>
|
||
GCC は portable なソースであるので、特定のシステムに移植したものの仕様は移植者が用意すべきであるが、そうした独自のドキュメントは提供されていない。cpp のドキュメントとしては GNU の cpp.info しか存在しない。
|
||
</blockquote>
|
||
<pre>
|
||
5 : Linux / GCC V.3.2 (2002/08) / cpp0
|
||
C95: cpp0 -D__STRICT_ANSI__ -std=iso9899:199409 -$ -pedantic -Wall
|
||
gcc -std=iso9899:199409 -pedantic -Wall
|
||
C99: cpp0 -std=c99 -$ -Wall
|
||
C++: g++ -E -trigraphs -$ -Wall
|
||
</pre>
|
||
<blockquote>
|
||
ソースから kmatsui がコンパイルしたもの。--enable-c-mbchar というオプションを付けて configure している。
|
||
</blockquote>
|
||
<pre>
|
||
6 : Linux / / ucpp (V.1.3)
|
||
C95: ucpp -wa -c90
|
||
C99: ucpp -wa
|
||
</pre>
|
||
<blockquote>
|
||
Thomas Pornin によるオープンソース・ソフトウェア (2003/01)。Portable な単体プリプロセッサ。Linux / GCC でコンパイルしてテストした。
|
||
</blockquote>
|
||
<pre>
|
||
7 : WIN32 / Visual C++ 2003 / cl
|
||
C95: cl -Za -E -Wall -Tc
|
||
C99: cl -E -Wall -Tc
|
||
C++: cl -E -Wall -Tp
|
||
</pre>
|
||
<blockquote>
|
||
-E オプションではコメントや <backslash><newline> が正しく処理されないので、コンパイルによるテストを併用 (2003/04)。
|
||
</blockquote>
|
||
<pre>
|
||
8 : WIN32 / LCC-Win32 2003-08 / lcc
|
||
C95: lcc -A -E
|
||
lcc -A
|
||
C99: lcc -A -E
|
||
C++: lcc -A -E
|
||
</pre>
|
||
<blockquote>
|
||
Jacob Navia が C. W. Fraser & Dave Hanson のオープンソース・ソフトウェアである lcc を元に書いた統合開発環境 (2003/08)。プリプロセス部分のソースは Dennis Ritchie が Plan9 のために書いたもの。
|
||
</blockquote>
|
||
<pre>
|
||
9 : WIN32, Linux, etc. / / wave (V.1.0)
|
||
C95: wave
|
||
C99: wave --c99
|
||
C++: wave
|
||
</pre>
|
||
<blockquote>
|
||
Hartmut Kaiser による portable なオープンソース・ソフトウェア。Compiler-independent なプリプロセッサ。
|
||
Boost preprocessor library なるものを使って実装されている。作者が用意した WIN32 用の実行プログラム (2004/01) でテストした。
|
||
</blockquote>
|
||
<pre>
|
||
10: FreeBSD, Linux, CygWIN / GCC 2.95, 3.2
|
||
WIN32, MS-DOS / Visual C 2003, BCC, etc. / mcpp_std (V.2.4)
|
||
C95: mcpp_std -23 (-S1 -V199409L) -W31
|
||
gcc -ansi -Wp,-2,-W31
|
||
C99: mcpp_std -23 (-S1) -V199901L -W31
|
||
C++: mcpp_std -23+ -V199711L -W31
|
||
</pre>
|
||
<blockquote>
|
||
<b>mcpp</b> V.2.4 (2004/02) の Standard モード。Linux / GCC でコンパイルしたものでテスト。
|
||
</blockquote>
|
||
<pre>
|
||
11: Linux / GCC V.3.4.3 (2004/11) / cc1, cc1plus
|
||
C95: gcc -E -std=iso9899:199409 -pedantic -Wall
|
||
C99: gcc -E -std=c99 -$ -Wall
|
||
C++: g++ -E -std=c++98 -$ -Wall
|
||
</pre>
|
||
<pre>
|
||
12: WIN32 / Visual C++ 2005 / cl
|
||
C95: cl -Za -E -Wall -Tc
|
||
C99: cl -E -Wall -Tc
|
||
C++: cl -E -Wall -Tp
|
||
</pre>
|
||
<blockquote>
|
||
-E オプションではコメントや <backslash><newline> が正しく処理されないので、コンパイルによるテストを併用 (2005/09)。
|
||
</blockquote>
|
||
<pre>
|
||
13: WIN32 / LCC-Win32 2006-03 / lcc
|
||
C95: lcc -A -E
|
||
lcc -A
|
||
C99: lcc -A -E
|
||
C++: lcc -A -E
|
||
</pre>
|
||
<blockquote>
|
||
LCC-Win32 2006-03 (2006/03)。
|
||
</blockquote>
|
||
<pre>
|
||
14: Linux / GCC V.4.1.1 (2006/05) / cc1, cc1plus
|
||
C95: gcc -E -std=iso9899:199409 -pedantic -Wall
|
||
C99: gcc -E -std=c99 -$ -Wall
|
||
C++: g++ -E -std=c++98 -$ -Wall
|
||
|
||
15: WIN32 / Visual C++ 2008 / cl
|
||
C95: cl -Za -E -Wall -Tc
|
||
C99: cl -E -Wall -Tc
|
||
C++: cl -E -Wall -Tp
|
||
</pre>
|
||
<blockquote>
|
||
-E オプションではコメントや <backslash><newline> が正しく処理されないので、コンパイルによるテストを併用 (2007/12)。
|
||
</blockquote>
|
||
<pre>
|
||
16: Linux, WIN32, etc. / / wave (V.2.0)
|
||
C95: wave (--c99)
|
||
C99: wave --c99
|
||
C++: wave
|
||
</pre>
|
||
<blockquote>
|
||
wave の V.2.0 (2008/08)。
|
||
Boost C++ Library V.1.36.0 に含まれているソースを筆者が Linux/GCC, Windows/Visual C++ でデフォルトの設定でコンパイルしたものを使用。
|
||
GCC, Visual C のヘッダファイルを使うための各設定ファイルを用意してテスト。
|
||
</blockquote>
|
||
<pre>
|
||
17: FreeBSD, Linux, Mac OS X, CygWIN, MinGW / GCC 2.95 - 4.1
|
||
WIN32 / Visual C 2003-2008, BCC, LCC-Win32 / mcpp (V.2.7.2)
|
||
C95: mcpp -23 (-S1 -V199409L) -W31
|
||
gcc -ansi -Wp,-2,-W31,-fno-dollars-in-identifiers
|
||
C99: mcpp -23 (-S1) -V199901L -W31
|
||
C++: mcpp -23+ -V199711L -W31
|
||
</pre>
|
||
<blockquote>
|
||
<b>mcpp</b> V.2.7.2 (2008/11)。
|
||
</blockquote>
|
||
<br>
|
||
|
||
<h2><a name="6.2" href="#toc.6.2">6.2. 採点表</a></h2>
|
||
<pre>
|
||
D M B G G u V L W M G V L G V W M
|
||
E C C C C c C C a C C C C C C A C
|
||
C P C C C p 2 C v P C 2 C C 2 V P
|
||
U P 5 2 3 p 0 0 e P 3 0 0 4 0 E P
|
||
S 2 5 9 2 1 0 3 1 2 4 0 6 1 0 2 2
|
||
C 0 C 5 3 3 0 0 4 3 5 0 1 8 0 7
|
||
P P 3 8 3 2
|
||
P P
|
||
|
||
max 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
||
|
||
[K&R: Processing of sources conforming to K&R and C90] (31 items)
|
||
n.2.1 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
n.2.2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.2.3 2 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.6.1 10 10 10 10 10 10 10 10 10 4 10 10 10 10 10 10 10 10
|
||
n.7.2 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
n.10.2 6 0 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
|
||
n.12.3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
n.12.4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
n.12.5 2 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.12.7 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
n.13.1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.13.2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.13.3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
n.13.4 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.13.7 6 6 6 4 6 6 6 4 6 0 6 6 4 4 6 4 6 6
|
||
n.13.8 2 0 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2
|
||
n.13.9 2 2 2 2 2 2 2 0 2 0 2 2 0 2 2 0 2 2
|
||
n.13.10 2 2 2 2 2 2 2 0 0 2 2 2 2 0 2 2 2 2
|
||
n.13.11 2 0 2 2 2 2 2 0 0 0 2 2 2 0 2 2 2 2
|
||
n.13.12 2 0 2 2 2 2 2 2 0 0 2 2 2 0 2 2 2 2
|
||
n.15.1 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
|
||
n.15.2 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
|
||
n.18.1 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30
|
||
n.18.2 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
|
||
n.18.3 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
|
||
n.27.1 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
|
||
n.27.2 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
n.29.1 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
|
||
n.32.1 2 2 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2
|
||
i.32.3 2 2 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2
|
||
i.35.1 2 2 2 2 2 2 2 0 0 0 2 2 0 0 2 0 1 2
|
||
stotal 166 150 166 164 166 166 166 156 158 140 166 166 160 156 166 160 165 166
|
||
|
||
[C90: Processing of strictly conforming sources] (76 items)
|
||
n.1.1 6 0 6 6 6 6 6 6 6 0 6 6 6 6 6 6 6 6
|
||
n.1.2 2 0 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2
|
||
n.1.3 2 0 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2
|
||
n.2.4 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.2.5 2 0 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2
|
||
n.3.1 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
|
||
n.3.3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
n.3.4 2 0 2 0 2 2 0 2 2 2 2 2 2 2 2 2 2 2
|
||
n.4.1 6 0 6 0 6 6 6 6 0 0 6 6 6 0 6 6 6 6
|
||
n.4.2 2 0 2 0 2 2 2 2 0 0 2 2 2 0 2 2 2 2
|
||
n.5.1 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
|
||
n.6.2 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
|
||
n.6.3 2 0 2 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.7.1 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
|
||
n.7.3 4 0 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
n.8.1 8 0 8 8 8 8 8 8 8 8 8 8 8 8 8 8 2 8
|
||
n.8.2 2 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.9.1 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
|
||
n.10.1 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
|
||
n.11.1 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8
|
||
n.11.2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.12.1 6 0 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
|
||
n.12.2 4 0 4 4 4 4 4 4 4 0 4 4 4 4 4 4 4 4
|
||
n.12.6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
|
||
n.13.5 2 2 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2
|
||
n.13.6 6 0 6 6 6 6 4 6 4 0 6 6 4 4 6 4 6 6
|
||
n.13.13 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
n.13.14 2 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.19.1 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
n.19.2 4 2 4 4 4 4 4 4 4 2 4 4 4 4 4 4 4 4
|
||
n.20.1 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
|
||
n.21.1 4 0 4 0 4 4 4 0 4 4 4 4 0 4 4 0 4 4
|
||
n.21.2 2 0 2 0 2 2 2 0 2 2 2 2 0 2 2 0 2 2
|
||
n.22.1 4 0 4 0 4 4 4 4 4 0 4 4 4 4 4 4 4 4
|
||
n.22.2 2 0 2 0 2 2 2 2 2 0 2 2 2 2 2 2 2 2
|
||
n.22.3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 0 2
|
||
n.23.1 6 2 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
|
||
n.23.2 2 2 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2
|
||
n.24.1 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
|
||
n.24.2 4 0 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
n.24.3 6 0 6 0 6 6 6 6 6 6 6 6 6 6 6 6 6 6
|
||
n.24.4 2 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.24.5 2 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.25.1 4 2 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
n.25.2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.25.3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.25.4 6 0 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
|
||
n.25.5 4 0 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
n.26.1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.26.2 2 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.26.3 2 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.26.4 2 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.26.5 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.27.3 2 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.27.4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 2 4
|
||
n.27.5 2 2 2 2 2 2 2 0 2 0 2 2 0 2 2 0 0 2
|
||
n.27.6 2 0 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.28.1 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
n.28.2 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
n.28.3 4 0 4 4 4 4 2 4 4 4 4 4 4 4 4 4 4 4
|
||
n.28.4 4 0 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
n.28.5 4 0 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
n.28.6 4 0 4 0 4 4 2 0 0 4 4 4 0 0 4 0 4 4
|
||
n.28.7 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
n.29.2 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
n.30.1 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
|
||
n.32.2 2 2 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2
|
||
n.37.1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.37.2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.37.3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
n.37.4 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.37.5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
n.37.6 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.37.7 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.37.8 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
n.37.9 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
stotal 286 160 284 252 286 286 278 274 272 240 286 286 272 272 286 272 274 286
|
||
|
||
[C90: Processing of implementation defined portions] (1 item)
|
||
i.32.4 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
stotal 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
|
||
[C90: Diagnosing of violation of syntax rule or constraint] (50 items)
|
||
e.4.3 2 2 2 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
e.7.4 2 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
e.12.8 2 0 2 2 2 2 2 2 0 2 2 2 2 0 2 2 2 2
|
||
e.14.1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
e.14.2 4 2 4 2 4 4 2 2 4 4 4 4 4 4 4 4 4 4
|
||
e.14.3 2 2 2 2 2 2 1 2 2 2 2 2 2 2 2 2 2 2
|
||
e.14.4 2 2 2 2 2 2 1 2 2 2 2 2 2 2 2 2 2 2
|
||
e.14.5 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
e.14.6 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
e.14.7 2 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
e.14.8 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
e.14.9 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
e.14.10 4 0 4 2 0 0 0 0 0 0 4 0 0 0 0 0 4 4
|
||
e.15.3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
e.15.4 2 2 2 1 2 2 2 1 2 2 2 2 2 2 2 2 2 2
|
||
e.15.5 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
e.16.1 2 2 2 1 2 2 2 1 2 2 2 2 2 2 2 2 2 2
|
||
e.16.2 2 2 2 1 2 2 2 1 2 2 2 2 2 2 2 2 2 2
|
||
e.17.1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
e.17.2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
e.17.3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
e.17.4 2 2 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2
|
||
e.17.5 2 0 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2
|
||
e.17.6 2 0 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2
|
||
e.17.7 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
e.18.4 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
e.18.5 2 2 2 2 0 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
e.18.6 2 0 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2
|
||
e.18.7 2 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
e.18.8 2 2 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2
|
||
e.18.9 2 0 2 0 2 2 2 2 0 0 2 0 2 0 2 2 0 2
|
||
e.19.3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
e.19.4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
e.19.5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
e.19.6 2 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
e.19.7 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
e.23.3 2 0 2 2 2 2 2 2 0 0 2 2 2 0 2 2 0 2
|
||
e.23.4 2 2 2 2 2 2 2 2 0 0 2 2 2 0 2 2 0 2
|
||
e.24.6 2 2 2 2 2 2 2 2 0 0 2 2 2 0 2 2 0 2
|
||
e.25.6 4 0 4 0 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
e.27.7 2 0 2 2 2 2 2 0 2 2 2 2 0 2 2 0 2 2
|
||
e.29.3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
e.29.4 2 2 2 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
e.29.5 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
e.31.1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
e.31.2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
e.31.3 2 2 2 0 2 2 2 1 2 2 2 2 1 2 2 1 2 2
|
||
e.32.5 2 0 2 2 2 2 0 2 0 0 2 2 2 0 2 2 2 2
|
||
e.33.2 2 0 2 0 0 2 0 2 0 0 2 2 2 0 2 2 2 2
|
||
e.35.2 2 0 2 1 2 2 0 2 0 2 2 2 2 0 2 2 2 2
|
||
stotal 112 74 112 92 104 108 98 100 92 86 112 106 105 92 108 105 104 112
|
||
|
||
[C90: Documents on implementation defined behaviors] (13 items)
|
||
d.1.1 2 0 2 0 0 2 0 0 0 0 2 2 0 0 2 0 0 2
|
||
d.1.2 4 2 4 4 4 4 0 4 0 0 4 4 4 0 4 4 2 4
|
||
d.1.3 2 0 2 0 0 2 0 0 2 2 2 2 0 0 2 0 2 2
|
||
d.1.4 4 0 4 4 4 4 0 4 4 2 4 4 4 4 4 4 2 4
|
||
d.1.5 4 2 4 4 2 4 4 4 4 4 4 2 4 4 2 4 4 4
|
||
d.1.6 2 0 2 0 0 1 0 0 0 0 2 1 0 0 1 0 0 2
|
||
d.2.1 2 0 2 2 2 2 2 0 0 0 2 2 2 0 2 2 0 2
|
||
d.2.2 2 0 2 2 0 2 0 0 0 0 2 2 2 0 2 2 0 2
|
||
d.2.3 2 0 2 0 0 0 0 0 0 0 2 0 0 0 0 0 0 2
|
||
d.2.4 2 0 2 0 0 0 0 0 0 0 2 0 0 0 0 0 0 2
|
||
d.2.5 2 0 2 0 0 0 0 2 0 0 2 0 2 0 0 2 0 2
|
||
d.2.6 2 0 2 2 0 0 0 2 0 0 2 0 2 0 0 2 0 2
|
||
d.2.7 2 0 2 2 0 2 0 2 0 0 2 2 2 0 2 2 0 2
|
||
stotal 32 4 32 20 12 23 6 18 10 8 32 21 22 8 21 22 10 32
|
||
|
||
[C90: Degree of Standard C conformance] (171 items)
|
||
mttl90 598 390 596 530 570 585 550 550 534 476 598 581 561 530 583 561 555 598
|
||
|
||
[C99: Conformance to new features] (20 items)
|
||
n.dslcom 4 0 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
n.ucn1 8 0 0 0 0 6 8 2 0 2 8 6 8 0 6 8 6 8
|
||
n.ucn2 2 0 0 0 0 0 2 0 0 0 2 2 2 0 2 2 0 2
|
||
n.ppnum 4 0 4 0 4 4 4 0 0 0 4 4 0 4 4 0 0 4
|
||
n.line 2 0 2 2 2 2 2 2 0 2 2 2 2 0 2 2 2 2
|
||
n.pragma 6 0 6 0 0 6 6 0 0 2 6 6 0 0 6 0 2 6
|
||
n.llong 10 0 0 0 10 10 8 10 0 0 10 10 10 0 10 10 10 10
|
||
n.vargs 10 0 10 0 10 10 10 0 0 10 10 10 10 2 10 10 10 10
|
||
n.stdmac 4 0 2 0 0 4 4 0 0 4 4 4 0 0 4 0 4 4
|
||
n.nularg 6 0 6 0 6 6 6 2 0 6 6 6 2 0 6 2 6 6
|
||
n.tlimit 18 0 18 14 18 18 17 18 14 18 18 18 18 12 18 18 16 18
|
||
e.ucn 4 0 0 0 0 0 2 0 0 2 4 0 2 0 0 2 2 4
|
||
e.intmax 2 0 0 0 2 2 2 0 0 0 2 1 0 0 1 0 2 2
|
||
e.pragma 2 0 2 0 0 2 2 0 0 2 2 2 0 0 2 0 2 2
|
||
e.vargs1 2 0 0 0 0 2 1 0 0 1 2 2 0 0 2 0 2 2
|
||
e.vargs2 2 0 2 0 0 0 0 0 0 0 2 0 0 0 0 0 0 2
|
||
d.pragma 2 0 2 0 0 2 2 2 0 0 2 2 2 0 2 2 2 2
|
||
d.predef 6 0 0 0 0 6 6 0 0 0 6 6 0 0 6 0 0 6
|
||
d.ucn 2 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 2
|
||
d.mbiden 2 0 0 0 0 2 2 1 0 0 2 2 1 0 2 1 0 2
|
||
mttl99 98 0 58 20 56 86 88 41 18 53 98 87 61 22 87 61 70 98
|
||
|
||
[C++: Conformance to new features not in C90] (9 items)
|
||
n.dslcom 4 0 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
n.ucn1 4 0 0 0 0 4 4 2 0 2 4 4 2 0 4 2 2 4
|
||
n.cnvucn 4 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0
|
||
n.bool 2 0 0 0 0 2 0 0 0 2 2 2 0 0 2 0 0 2
|
||
n.token1 2 0 2 0 0 2 0 0 2 2 2 2 0 2 2 0 2 2
|
||
n.token2 2 0 0 0 0 2 0 2 0 2 2 2 2 0 2 2 2 2
|
||
n.cplus 4 0 2 2 2 2 0 4 0 4 4 2 4 0 2 4 4 4
|
||
e.operat 2 0 0 0 0 2 0 0 0 2 2 2 0 0 2 0 2 2
|
||
d.tlimit 2 0 2 0 0 2 0 1 0 0 2 2 1 0 2 1 0 2
|
||
mttl++ 26 0 10 6 6 20 9 13 6 18 22 20 13 6 20 13 16 22
|
||
|
||
[C90: Qualities / 1 : handling of multibyte character] (1 item)
|
||
m.36.2 7 0 2 2 0 0 0 4 0 0 7 5 2 0 5 2 0 7
|
||
stotal 7 0 2 2 0 0 0 4 0 0 7 5 2 0 5 2 0 7
|
||
|
||
[C90: Qualities / 2 : diagnosis of undefined behaviors] (29 items)
|
||
u.1.1 1 0 1 0 1 1 0 0 0 1 1 1 0 0 1 0 1 1
|
||
u.1.2 1 0 1 0 1 1 0 1 0 1 1 1 0 0 1 0 1 1
|
||
u.1.3 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
|
||
u.1.4 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
|
||
u.1.5 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1
|
||
u.1.6 1 0 1 0 1 1 0 0 1 0 1 1 0 1 1 0 0 1
|
||
u.1.7 9 0 1 0 0 0 0 0 0 0 6 6 0 0 6 0 0 9
|
||
u.1.8 1 1 1 0 1 1 0 0 0 0 1 0 0 1 0 0 0 1
|
||
u.1.9 1 1 1 0 1 1 1 0 1 0 1 0 0 1 0 0 0 1
|
||
u.1.10 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
|
||
u.1.11 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1
|
||
u.1.12 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
|
||
u.1.13 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
|
||
u.1.14 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
|
||
u.1.15 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
|
||
u.1.16 1 0 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1
|
||
u.1.17 2 0 2 0 1 1 0 0 1 0 2 1 0 1 1 0 1 2
|
||
u.1.18 1 0 1 0 1 1 1 0 0 0 1 1 0 0 1 0 1 1
|
||
u.1.19 2 0 2 0 0 1 1 0 0 1 2 1 0 0 1 0 1 2
|
||
u.1.20 1 0 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1
|
||
u.1.21 2 0 2 1 0 1 2 2 2 2 2 1 2 2 1 2 2 2
|
||
u.1.22 1 0 1 0 0 1 1 0 1 1 1 1 0 1 1 0 1 1
|
||
u.1.23 1 1 1 0 1 0 0 1 1 0 1 0 1 1 0 1 0 1
|
||
u.1.24 2 0 2 0 0 0 0 0 0 0 2 0 0 0 0 0 2 2
|
||
u.1.25 1 0 1 0 0 1 0 0 0 0 1 1 0 0 1 0 1 1
|
||
u.1.27 1 1 1 1 0 1 1 1 1 0 1 1 1 1 1 1 1 1
|
||
u.1.28 1 1 1 1 0 1 1 1 1 0 1 1 1 1 1 1 1 1
|
||
u.2.1 1 1 1 1 1 1 0 1 0 1 1 1 1 0 1 1 1 1
|
||
u.2.2 1 0 1 1 0 1 1 0 0 0 1 1 0 0 1 0 0 1
|
||
stotal 41 10 33 16 19 26 20 18 19 16 38 30 17 21 30 17 25 41
|
||
|
||
[C90: Qualities / 3 : Diagnosis of unspecified behaviors] (2 items)
|
||
s.1.1 2 0 2 0 0 0 2 0 0 0 2 0 0 0 0 0 0 2
|
||
s.1.2 2 0 2 0 0 0 0 0 0 0 2 0 0 0 0 0 0 2
|
||
stotal 4 0 4 0 0 0 2 0 0 0 4 0 0 0 0 0 0 4
|
||
|
||
[C90: Qualities / 4 : Diagnosis of suspicious cases] (12 items)
|
||
w.1.1 4 4 4 0 4 4 0 0 0 0 4 4 0 0 4 0 0 4
|
||
w.1.2 4 0 4 0 0 0 0 0 0 2 4 0 0 0 0 0 2 4
|
||
w.2.1 2 0 2 1 0 0 0 0 0 0 2 2 0 0 2 0 0 2
|
||
w.2.2 1 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1
|
||
w.3.1 1 1 1 0 0 0 0 0 0 0 1 0 0 1 0 0 0 1
|
||
w.3.3 1 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1
|
||
w.3.4 1 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1
|
||
w.3.5 1 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1
|
||
w.3.6 1 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1
|
||
w.3.7 1 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1
|
||
w.3.8 1 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1
|
||
w.3.9 1 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1
|
||
stotal 19 5 19 1 4 4 0 0 0 2 19 6 0 1 6 0 2 19
|
||
|
||
[C90: Qualities / 5 : Other features] (17 items)
|
||
q.1.1 9 0 9 6 9 9 8 7 4 9 9 9 7 5 9 7 8 9
|
||
q.1.2 10 6 10 4 8 10 4 4 4 4 10 10 4 4 10 4 4 10
|
||
q.1.3 4 4 4 2 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
q.1.4 20 10 20 10 20 20 20 10 20 10 20 20 10 20 20 10 10 20
|
||
q.2.1 4 2 4 2 4 4 4 4 2 4 4 4 4 2 4 4 4 4
|
||
q.2.2 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
|
||
q.2.3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
|
||
q.2.4 2 2 2 0 2 2 0 0 0 0 2 2 0 0 2 0 0 2
|
||
q.2.5 2 0 2 0 0 0 0 0 0 0 2 0 0 0 0 0 0 2
|
||
q.2.6 4 0 4 2 4 4 2 4 2 0 4 4 4 2 4 4 0 4
|
||
q.2.7 10 4 6 4 8 8 4 4 4 2 8 8 4 4 8 4 4 8
|
||
q.2.8 10 0 6 2 2 2 0 6 4 4 8 2 6 4 2 6 4 8
|
||
q.2.9 6 2 2 0 2 2 0 0 0 2 2 2 0 0 2 0 2 4
|
||
q.3.1 20 10 8 8 14 12 8 10 10 6 8 12 10 10 12 10 6 8
|
||
q.3.2 20 20 20 18 16 16 18 16 18 14 18 14 14 18 12 14 12 16
|
||
q.3.3 20 10 14 0 10 12 8 0 0 8 14 12 0 0 12 0 10 16
|
||
q.4.1 10 2 6 6 4 6 2 4 6 4 4 6 4 6 6 4 4 8
|
||
stotal 157 78 123 70 113 117 88 79 84 77 123 115 77 85 113 77 78 129
|
||
|
||
[C90: Qualities] (61 items)
|
||
mttl90 228 93 181 89 136 147 110 101 103 95 191 156 96 107 154 96 105 200
|
||
|
||
[C99: Qualities of new features] (3 items)
|
||
u.line 2 0 2 0 1 1 0 0 0 0 2 1 0 0 1 0 2 2
|
||
u.concat 1 0 1 0 0 1 0 0 0 0 1 1 0 0 1 0 0 1
|
||
w.tlimit 8 0 8 0 0 0 3 2 0 0 8 0 2 0 0 2 0 8
|
||
mttl99 11 0 11 0 1 2 3 2 0 0 11 2 2 0 2 2 2 11
|
||
|
||
[C++: Qualities of features not in C90] (1 item)
|
||
u.cplus 1 0 1 1 0 0 0 1 0 1 1 0 1 0 0 1 1 1
|
||
mttl++ 1 0 1 1 0 0 0 1 0 1 1 0 1 0 0 1 1 1
|
||
|
||
[Overall] (265 items)
|
||
gtotal 962 483 857 646 769 840 760 708 661 643 921 846 734 665 846 734 749 930
|
||
|
||
D M B G G u V L W M G V L G V W M
|
||
E C C C C c C C a C C C C C C A C
|
||
C P C C C p 2 C v P C 2 C C 2 V P
|
||
U P 5 2 3 p 0 0 e P 3 0 0 4 0 E P
|
||
S 2 5 9 2 1 0 3 1 2 4 0 6 1 0 2 2
|
||
C 0 C 5 3 3 0 0 4 3 5 0 1 8 0 7
|
||
P P 3 8 3 2
|
||
P P
|
||
|
||
max 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
||
</pre>
|
||
<br>
|
||
|
||
<h2><a name="6.3" href="#toc.6.3">6.3. 各プリプロセッサの特徴</a></h2>
|
||
|
||
<p>1 : Linux / / DECUS cpp</p>
|
||
<blockquote>ANSI draft 初期のころのもので、今となっては規格準拠度は低くなっています。診断メッセージはまずまずですが、ドキュメントはほとんどありません。しかし、骨格のしっかりしたプログラムで、安定しています。<br>
|
||
ソースは portability の高いもので、いくつかの処理系に移植されていました。お手本のように読みやすいソースで、読むだけでも勉強になります。私が <b>mcpp</b> のベースにしたのはこのソースです。</blockquote>
|
||
|
||
<p>3 : WIN32 / Borland C++ V.5.5J / cpp32</p>
|
||
<blockquote>C90 の規格準拠度は比較的高く、やっかいな shift-JIS にもまともに対応しています。ドキュメントも整っています。しかし、e_* では診断メッセージはたいてい出ますが、間に合わせ的なものが多く、質は良くありません。<br>
|
||
「規格外の品質」は貧弱で、これという拡張機能もありません。Undefined な部分に対しては診断メッセージはあまり出ず、時々暴走します。規格に対応させるだけで精一杯となってしまったようです。<br>
|
||
速度は Turbo C と違って、速いほうではなくなりました。すでに1パス・コンパイラのメリットはなくなってデメリットだけが残っていると思われますが、いつまでこの流儀を続けてゆくのでしょうか。</blockquote>
|
||
|
||
<p>4 : Linux, CygWIN / GCC V.2.95.3 / cpp0</p>
|
||
<blockquote>C90, C95 の規格準拠度はかなり高く、診断メッセージも的確です。動作はほぼ安定しており、かつきわめて高速です。豊富すぎるくらい豊富なオプションも特徴です。<b>mcpp</b> でも、それらのオプションのいくつかをまねています。<br>
|
||
かつてはいくつかの痛いバグもありましたが、V.2.95 ではバグはほとんどなくなりました。<br>
|
||
残る問題は、C99, C++98 の新仕様の多くが未実装であること、診断メッセージがまだまだ足りないこと、ドキュメントがかなり不足していること、#pragma を使わない規格違反の拡張機能が多いこと、pre-Standard の obsolete な仕様が多く隠れていること、multi-byte character encoding への対応が中途半端で実用レベルに達していないこと、等です。<br>
|
||
cpp.info というドキュメントは GCC / cpp および Standard C プリプロセス全体の解説として優れたものです。ただ、処理系定義部分のドキュメントが CygWIN, FreeBSD, Linux のどれにも存在しないのは残念です。「移植」というのはプログラムだけではないはずですが。<br>
|
||
ソースはつぎはぎだらけで読みにくく、かつプログラムの組み立てが古いマクロプロセッサの構造を引きずっています。しかし、 GCC 処理系の全体が優れているため、多くのシステムに移植されています。</blockquote>
|
||
|
||
<p>5 : Linux / GCC V.3.2 / cpp0</p>
|
||
<blockquote>GCC V.3 ではプリプロセスは V.2 からソースが一新されました。同時にドキュメントも一新されました。Token-based な原則が徹底され、pre-Standard な仕様はまだ許容しながらもウォーニングが出るようになり、undocumented な仕様が減少しています。全体としては私の期待した方向へ大幅に改良されています。プログラム構造が一新されたことで、今後の改良もしやすくなったものと思われます。<br>
|
||
診断メッセージ・ドキュメント・C99 対応・multi-byte character への対応はまだ不十分です。速度はやや遅くなりましたが、それでもまだ速いほうです。<br>
|
||
ただ、ヘッダファイルが複雑化して include directory のサーチ順の設定も複雑になってきているのは、やっかいな問題です。また、古いオプションが使われなくなってきた一方で多くの新しいオプションが増えており、なかなか整理する方向へ進みません。V.3 ではプリプロセスがコンパイラ本体に一体化されましたが、プリプロセス部分とコンパイル部分との内部的な interface がなぜか複雑なものになっているのも、残念なことです。</blockquote>
|
||
|
||
<p>6 : Linux / / ucpp (V.1.3)</p>
|
||
<blockquote>C99 に対応していること、オープンソースで portable であることが特徴です。規格準拠度はかなり高くなっています。UCN と UTF-8 に対応していることになっていますが、これはきわめて不十分です。診断メッセージはやや漠然としています。ドキュメントもかなり不足しています。</blockquote>
|
||
|
||
<p>7 : WIN32 / Visual C++ 2003 / cl<br>
|
||
12 : WIN32 / Visual C++ 2005 / cl<br>
|
||
15 : WIN32 / Visual C++ 2008 / cl</p>
|
||
<blockquote>C99 の仕様は 2003 ではほとんど実装されていませんでしたが、2005 ではだいぶ実装が進みました。<br>
|
||
しかし、C90 以前からの仕様にいくつかのバグが残っています。Translation phase の混乱していることが最も根本的な問題です。非常に古いソースに部分的に手を入れながらバージョンアップを繰り返してきていることが想像されます。<br>
|
||
診断メッセージはややピントのずれたものがしばしば見られます。エラーが起こると処理を中断してしまうことが多いのも、使いにくい点です。ドキュメントは実装に比べて更新の大幅に遅れている部分があちこちにあります。<br>
|
||
Translation limits が大きいことと #pragma が比較的多いことがとりえと言えます。ことに #pragma setlocale は便利なものです。しかし、C90 でも #pragma 行がマクロ展開されること、にもかかわらず #pragma sub-directive がユーザの名前空間を使っているのは問題です。<br>
|
||
2008 のプリプロセッサは 2005 とほとんど変わりありません。
|
||
違いは C99 の _Pragma() に代わる __pragma() という名前も仕様も異なる演算子が実装されたことくらいです。
|
||
添付されるシステムヘッダには、2005 までは問題は少なかったのですが、2008 になってなぜか '$' を使ったマクロが突然増えてしまいました。</blockquote>
|
||
|
||
<p>8 : WIN32 / LCC-Win32 2003-08 / lcc<br>
|
||
13 : WIN32 / LCC-Win32 2006-03 / lcc</p>
|
||
<blockquote>プリプロセスの部分は Dennis Ritchie の Plan9 用のソースに Jacob Navia が手を加えたものですが、デバッグが不足しており、#if 式の評価などにかなりのバグがあります。C95 以降の仕様には対応していません。ドキュメントも不足しています。<br>
|
||
2003-08 と 2006-03 とではプリプロセスに関してはほとんど違いはありません。</blockquote>
|
||
|
||
<p>9 : WIN32, Linux, etc. / / wave (V.1.0)<br>
|
||
16: WIN32, Linux, etc. / / wave (V.2.0)</p>
|
||
<blockquote>C++ の STL のための "meta-programming" に使うことを第一の目的に作られたものです。実装の大半が C++ のライブラリでできていて、ソースの大半がヘッダファイルで成り立っているという特異なプリプロセッサです。"Meta-programming" の実例では再帰的マクロが多用されており、その展開では <b>mcpp</b> の -@compat オプションや GCC と同様の、「再置換禁止ルール」の適用範囲を狭く限定する方法をとっています(<a href=#3.4.26>3.4.26</a> 参照)。<br>
|
||
一般の C/C++ プリプロセッサとしても使えることを意図しており、C++98, C99 対応を標榜しています。V.1.0 は完成度のかなり低いものでしたが、V.2.0 は大幅に改善されました。V.1.0 のあとで、<b>mcpp</b> に付属する検証セットでテストして多くのバグを修正したとのことです。さらに診断メッセージの質の改善やドキュメントの充実が望まれます。また、診断メッセージや付属する testcases に現れる作者の規格の解釈に誤りが散見されるのも気になるところです。</blockquote>
|
||
|
||
<p>11 : Linux / GCC V.3.4.3 / cc1, cc1plus<br>
|
||
14 : Linux / GCC V.4.1.1 / cc1, cc1plus</p>
|
||
<blockquote>スコアは V.3.2 とほとんど変わりません。しかし、プリプロセスの組み立てはかなり変わりました。V.3.2 ではスッキリした方向性を示していた GCC ですが、V.3.3, 3.4 では方向が変わったようです。単体のプリプロセッサを廃止し、大量のマクロを事前定義し、V.3.2 で obsolete とされた仕様の一部を復活させ、巨大な1つのコンパイラにすべてを吸収する方向に進んでいるのは、はたして改善と言えるかどうか疑問のあるところです。Multi-byte character の encoding で UTF-8 に特権的な位置を与えたことも、multi-lingualization の多様な可能性を狭めることになるのではないでしょうか。<br>
|
||
V.4.1 はプリプロセスに関しては V.3.4 と大きな違いはありません。</blockquote>
|
||
|
||
<p>2 : FreeBSD, WIN32, MS-DOS / / <b>mcpp</b> (V.2.0)<br>
|
||
10 : FreeBSD, Linux, CygWIN, WIN32, MS-DOS / / <b>mcpp</b> (V.2.4)<br>
|
||
17 : FreeBSD, Linux, Mac OS X, CygWIN, MinGW, WIN32 / / <b>mcpp</b> (V.2.7.2)</p>
|
||
<blockquote>私が自分で作って自分でテストしているものなので、規格準拠度は当然、最高です。世界一正確なプリプロセッサのはずです。診断メッセージの豊富さと的確さ、ドキュメントの詳細さも随一です。有用なオプションと #pragma も一通り持っています。V.2.3 以降は C99 の仕様にもすべて対応しています。
|
||
ソースの移植性の良さも一番でしょう。<br>
|
||
しかし、実装したい機能がまだいくつか残っています。皆さん、よろしくお願いします。</blockquote>
|
||
<br>
|
||
|
||
<h2><a name="6.4" href="#toc.6.4">6.4. 総評</a></h2>
|
||
<p>こうして多くのプリプロセッサをテストしてみると、現在は C90 規格準拠度はかなり高いものが多くなってきていることがわかります。しかし、各処理系ともまだまだ多くの問題を持っています。なお、<b>mcpp</b> はほとんどの項目が満点なので、一々言及しません。</p>
|
||
<p>n_* のサンプルに関しては正しく処理するものが増えており、GCC 2.95 以降, BC (Borland C) 5.5, LCC-Win32 2003-08 以降, ucpp 1.3, Visual C++ 2003 以降, wave 2.0 は実用上はあまり問題のないレベルに達しています。しかし、どの処理系にも意外なバグが見られます。</p>
|
||
<p>最も驚いたのは、n_13_7.t (n_13_7.c) が division by 0 のエラーになってしまう処理系が Visual C を含めてしばしば見られることです。&&, || や3項演算子での「評価の打ち切り」というCの基本的な仕様を満たしていないのです。Borland C は n_13_7.c ではウォーニングにとどめていますが、e_14_9.c の本当の division by 0 でも同じウォーニングしか出ません。Turbo C では本当の 0 除算でも評価をスキップされる部分式でもどちらも同じエラーとなっていましたが、Borland C はその診断メッセージをウォーニングに格下げしただけなのです。この処理系の「間に合わせ診断メッセージ」の一例です。</p>
|
||
<p>C90 の仕様では、# 演算子による文字列化の実装の間違っているものが時にあります。<br>
|
||
Amendment 1, Corrigendum 1 で追加された仕様は、ある程度実装しているのは GCC 2.95 以降, ucpp, Visual C 2003 以降です。</p>
|
||
<p>C99 の仕様は、GCC 3.2 以降, ucpp だけがほぼ実装しています。// コメントは多くの処理系が以前から実装しています。そのほか GCC は long long を持ち、translation limits にかなりの余裕があり、マクロのカラ引数も適切に処理します。可変引数マクロは GCC 独自の仕様のものを持っていますが、2.95 では C99 の仕様のものも実装されました。3.2 では _Pragma() も実装されました。UCN は ucpp, Visual C 2005 以降だけが実装しています。GCC 3.2 以降は文字列リテラルおよび文字定数の中の UCN だけを実装しています。Wave 2.0 も C99 の仕様の過半を実装しています。<br>
|
||
C++98 の仕様は GCC 3.2 以降, wave, Visual C 2005 以降がほぼ実装しています。<br>
|
||
C++98 の extended-character を UCN に変換するという奇怪な仕様を実装しているものは、まだ見当たりません。</p>
|
||
<p>i_* の処理系定義部分の処理では、#if 式で wide character を扱えないものが多いようです。しかし、wide character に限らず #if 式で文字定数を使うことは、規格にはあるもののほとんど意味のないことなので、使えないからといって困ることはないでしょう。こういう無意味なものは規格の仕様から除外すべきです。<br>
|
||
Multi-byte character については、Visual C が多くの国の encoding に不十分ながら対応していますが、他の処理系は貧弱です。GCC 2.9, 3.2 は実装が中途半端で、実用レベルに達していません。GCC 3.4-4.1 ではすべての encoding を UTF-8 に変換してから処理するようになり、それによって多くの encoding に対応できるようになりましたが、実装は encoding によってはまだ実用レベルではありません。<br>
|
||
Shift-JIS や BIG-5 を使うシステムでは、リテラルの tokenization や # 演算子による文字列化に注意が必要です。Visual C はこれにうまく対応しています。Borland C 5.5J も shift-JIS には対応しています。</p>
|
||
<p>e_* に対する診断メッセージでは、GCC 2.95 以降が最も優れています。Visual C, ucpp では診断メッセージは比較的出るものの、漠然としていたりピントが外れていたりします。#if 式の overflow に対する診断メッセージの出るものは少なく、BC, ucpp, GCC がある程度診断するだけです。</p>
|
||
<p>処理系定義部分のドキュメントでは、GCC 3.2 以降がまずまずのレベルで、他はどれもお粗末です。</p>
|
||
<p>u_* に対する診断メッセージでは、GCC 3.2 以降がまずまずですが、他はお粗末です。Undefined だから処理系は何もしなくて良いというものではないと思いますが。</p>
|
||
<p>s_*, w_* についてはどの処理系もほとんど対処していません。コメントのネストに対するウォーニングさえ出すものが少ないのは、意外です。</p>
|
||
<p>「その他の各種品質」では、GCC がその豊富なオプション、診断メッセージの的確さ、高速性、移植性とで抜きん出ています。</p>
|
||
<p>総合すると、GCC V.3.2 以降が、規格準拠度の高さ、使い勝手の良さ、あまり大きな穴のない安定性等で、最も優れています。もちろん、<b>mcpp</b> は速度だけは劣るものの、他のほとんどの面でこれを上回っていることは言うまでもありません。</p>
|
||
<p>さて、こうして大量のテストをやってきましたが、改めて思うのは、テスト用サンプルというものがいかに重要かということです。<b>mcpp</b> はこのサンプルの作成と並行してデバッグが進められてきたものです。十分なサンプルがなければバグの存在にさえもなかなか気付かれませんから、デバッグどころではありません。<br>
|
||
規格にもこうした網羅的なテスト用サンプルが添付されるようになると、各処理系の品質は飛躍的に向上することでしょう。また逆に、網羅的なテスト用サンプルを作ることは、規格の問題点を浮き彫りにすることにもなります。テスト用サンプルは規格のイラストレーションなのです。</p>
|
||
<br>
|
||
|
||
<h2><a name="6.5" href="#toc.6.5">6.5. テスト報告とご意見を</a></h2>
|
||
<p>この Validation Suite についてのご意見や、これを使った各種処理系のプリプロセスのテスト報告をお待ちしています。</p>
|
||
<p><a href="http://mcpp.sourceforge.net/">http://mcpp.sourceforge.net/</a></p>
|
||
<p>の "Open Discusssion Forum" またはメールでお願いします。</p>
|
||
<p>プリプロセッサの詳細テストを行った場合は、<a href="#6.2">6.2</a> 採点表を切り取って、これに書き込んでお送りください。合計点の計算には tool/total.c をコンパイルして使います。採点表を cpp_test.old とすると、</p>
|
||
<p>total 18 cpp_test.old cpp_test.new</p>
|
||
<p>とすると、stotal (sub-total), mtotal (mid-total), gtotal (grand-total) の各欄が書き込まれて cpp_test.new に出力されます。"18" のところは処理系の数を指定します。</p>
|
||
<p>GCC の場合は、検証セットの testsuite 版による自動テストができます。各種バージョンの GCC のテスト結果をお送りください。ログファイル (gcc.sum, gcc.log) を送っていただくと、診断メッセージのバージョンによる違いを検証セットの testsuite 版に採り入れることができます。</p>
|
||
<p>また、Validation Suite や <b>mcpp</b> の開発は SourceForge の上記 mcpp project で進められています。開発に参加してみようという方は、ぜひメールをください。</p>
|
||
</body>
|
||
</html>
|