10章 Cプリプロセッサー



10.2:
見て見て下の気のきいたマクロ。
#define begin {
#define end }

どう思う。

A:
ゲーッ。17章も参照のこと。


10.3:
二つの値を交換する汎用のマクロは。

A:
この質問の確かな解答はない。もしも値が整数なら有名な排他論理和 を使った技を使うことができる。しかし、この技も浮動小数点表示の 数やポインターには使えない(整数の場合も同一の変数を二つの引数 として指定することはできない)。また整数型の"誰でもわかる"極端 に詰め込んだコードa^=b^=a^=bも、副作用が複数回起こることにより 厳密にいえば文法違反である)。もしマクロを任意の型の値に使いた いなら(普通はこれが目的である)、一時変数を使うことはできない。 なぜなら、どの型の一時変数が必要かわからないからである(もし一 時変数が使えたとしても、変数の名前の付けかたに苦労するだろう)。 標準Cはtypeof演算子を用意していない。

一番の万能の解決方法は、マクロを使うことを考えないことだ。ただ し型を渡すために第三の引数を渡すのが面倒でないなら話は別だ。


10.4:
cppのマクロ定義に複数の文(multi-statement)を書くにはどうすれば よいか。

A:
たいてい目標は、関数を呼び出し1つからなる1の文のように呼べるマ クロを書くことである。ということは使い手は最後のセミコロンを自 分で書くから、マクロ本体にはセミコロンを付けてはいけないことに なる。マクロ本体は、単にカッコ{}でくくった複数の文であってはな らない。なぜならマクロが(見た目は一つの文として、かつ余計なセ ミコロンを付けて呼ばれたときに)、else節を持つif/else文のifが成 立したときの分岐に使われたときに文法エラーとなる。

昔からの解決方法は以下に示すものである。

#define MACRO(arg1, arg2) do { \
/* declarations */ \
stmt1; \
stmt2; \
/* ... */ \
} while(0) /* (後ろの;は無し) */

使い手がセミコロンを付けたときに、上記のマクロはどこで使われよ うと一つの文となる(最適化を行うコンパイラは、必ず偽になる"死ん だ"テストや定数0相手の条件成立に対する分岐を取り去るだろう。た だしlintは文句を付けるかもしれない)。

マクロの中のすべての文が、宣言もループも持たない単純な式なら、 カッコ()でくくってコンマ演算子で区切った式を使うことができる (例としては、質問10.26の最初のDEBUG()マクロを参照のこと)。この 方法を使えば値を"返す"こともできる。

References:
H&S Sec. 3.3.2 p. 45; CT&P Sec. 6.3 pp. 82-3.


10.6:
初めて、プログラムを複数のソースファイルに分けている。何を.cファ イルに入れ、何を.hに入れたらいいか悩んでいる。(そもそも「.h」っ て何?)

A:
一般に、以下のものはヘッダー(.h)に入れる。

マクロの定義(プリプロセッサーの#define)
構造体、共用体、列挙型の宣言
typedefの宣言
外部関数の宣言(質問1.11も参照のこと)
グローバル変数の宣言

定義や宣言を複数のファイルで共有するときは、そういう定義や宣言 をヘッダーファイルに入れることは特に大事である。(絶対に外部関 数のプロトタイプを.cファイルに入れてはいけない。質問1.7も参照 のこと)

一方、宣言や定義をあるソースファイルの専用にしたいなら、そのソー スファイルの中に入れておいてもかまわない。

質問1.710.7も参照のこと。

References:
K&R2 Sec. 4.5 pp. 81-2; H&S Sec. 9.2.3 p. 267; CT&P Sec. 4.6 pp. 66-7.


10.7:
ヘッダーが、別のヘッダーを#includeすることは容認されているのか。

A:
これは書き方に関する質問であり、この質問に関する議論は盛り上が る。多くの人が、入れ子の#includeは止めたほうがいいと信じている。 権威あるIndian Hillスタイルガイド(質問17.9参照)は、以下の理由により入れ子はよくないとしている。

一方#includeを入れ子にするとヘッダーファイルをモジュールを組み上げるように使うことができる(ヘッダーファイルが必要なファイルを#includeする。使い手はいちいち#includeしてまわらなくてすむ。いちいち#includeしてまわるのは手に負えなくなって頭痛の種となる)。
grep(やタグファイルのような道具)は、定義がどこにあるのか見つけるのを容易にする。

有名なトリック

#ifndef HEADER_FILE_NAME
#define HEADER_FILE_NAME
…ヘッダーファイルの中身…
#endif
はヘッダーファイルの「べき等(A * A = A)」を可能にする(二度以上読み込まれても問題が発生しない)。こうすれば何回#includeしても問題ない。 Makefileを保守するための自動化ツール(どっちにしても、この手のツールはプロジェクトが大きくなれば絶対必要となる。質問18.1を参照のこと)は、入れ子になった#includeファイルの依存関係をうまく作り出す。質問17.10を参照のこと。

References:
Rationale Sec. 4.1.2.


10.8:
ヘッダー(#include)ファイルを探すのに、どこを探しにいくのか。

A:
動作の細かいところは実装依存である(ということは文章にしてなけ ればならないということを意味する。質問11.33参照)。通常は、<>の 構文を持つヘッダーファイルなら一つ以上ある標準の場所を探しに行 く。""の構文を持つヘッダーファイルなら"現在のディレクトリー(カ レントディレクトリー:current directory)"を最初に探して、(見つ からなかったら) <>のときと同じ標準の場所に探しに行く。

昔から(特にUnixのコンパイラでは)、現在のディレクトリーとは #includeが入っているファイルのあるディレクトリーのことである。 しかしながらその他のコンパイラでは現在のディレクトリー(そうい うものがあったとして)とはコンパイラーを呼び出したディレクトリー である。コンパイラに付いてきた資料を読むこと。

References:
K&R2 Sec. A12.4 p. 231; ANSI Sec. 3.8.2; ISO Sec. 6.8.2; H&S Sec. 3.4 p. 55.


10.9:
ファイルの最初の最初の宣言で奇妙な構文エラーが出た。よさそうに 見えるのに。

A:
#includeしている最後のファイルの最後の宣言の終わりにセミコロン を付けるのを忘れたのだろう。質問2.18, 11.19も参照のこと。


10.11:
私の使っているシステムには<sgtty.h>が見当たらない。だれか 送ってくれないだろうか。

A:
標準ヘッダーは、君が使っているコンパイラ、O/S、プロセッサーに 適切な定義を与えるような形で存在している。他の人のヘッダーファ イルを引っ張ってきて動くことを期待することはできない。もちろん 相手がまったく同じ環境を使っている場合は別である。コンパイラのベン ダーに何故そのファイルが供給されなかったか尋ねてみること(ある いは代わりを送ってくれるように依頼してみること)。


10.12:
文字列を比較するプリプロセッサーの#if式はどうすれば作れるか。

A:
直接には不可能である。プリプロセッサーの#ifの計算のところは整 数しか使えない。わかりやすいラベルを整数定数に#defineして、そ れらのラベルを使った条件を実装するしかない。

質問20.17も参照のこと。

References:
K&R2 Sec. 4.11.3 p. 91; ANSI Sec. 3.8.1; ISO Sec. 6.8.1; H&S Sec. 7.11.1 p. 225.


10.13:
#ifの中でsizeof()は使えるか。

A:
使えない。前処理はコンパイルの早い段階で、つまりデータ型の名前 を構文解析する前で実行される。利用可能ならANSIの<limits.h>内に あらかじめ定義された定数を使うか、"構成(configure)"スクリプト を使うこと(もっといいのはデータ型の大きさに依存しないようなコー ドを書くことである)。

References:
ANSI Sec. 2.1.1.2, Sec. 3.8.1 footnote 83; ISO Sec. 5.1.1.2, Sec. 6.8.1; H&S Sec. 7.11.1 p. 225.


10.14:
#ifdefを#defineの行に使って、あるものを二つのまったく異なった 風に定義することができるか。

A:
できない。「プリプロセッサーをプリプロセッサーにかけることはで きない」といったところか。

できるのは、#ifdefの設定によってまったく別個の二つの#defineの うちの一つを使うことである。

References:
ANSI Sec. 3.8.3, Sec. 3.8.3.4; ISO Sec. 6.8.3, Sec. 6.8.3.4; H&S Sec. 3.2 pp. 40-1.


10.15:
typedefのかわりになる#ifdefのようなものがないか。

A:
残念ながら存在しない。(質問10.13も参照のこと。)

References:
ANSI Sec. 2.1.1.2, Sec. 3.8.1 footnote 83; ISO Sec. 5.1.1.2, Sec. 6.8.1; H&S Sec. 7.11.1 p. 225.


10.16:
プリプロセッサーの#ifを使って、マシンがビッグエンディアンかリ トルエンディアンかを知ることができるか。

A:
たぶんできない(プリプロセッサーでの演算はすべてlongで行われ、 そこにはアドレスの概念はない)。本当にマシンのエンディアンを明 示的に知りたいのか。エンディアンを気にしないコードを書くほうが よい。


10.18:
こんど押し付けられたコードは#ifdefだらけで私の趣味にあわない。 このコードを条件コンパイルの一組だけ残して、cppを通すことなく、 かつ#includeや#defineは展開しないで前処理する方法があるか。

A:
世の中にはunifdef、rmifdef、scpp("(選択式Cプリプロセッサー) selective C preprocessor")というプログラムが出まわっていて、こ れがまさに上に書かれている機能を持つ。質問18.16を参照のこと。


10.19:
(訳注:__DATE__や__TIME__のような)あらかじめ定義されたマクロの 一覧を得るにはどうすればよいか。

A:
何度も必要になるにもかかわらず標準の方法というのは存在しない。 コンパイラ附属の資料が役に立たないなら、一番目的にかなった方法 は、たぶんUnixのstrings(1)かなんかを使って、コンパイラやプリプ ロセッサーの実行ファイルから文字列を拾いだすことであろう。従来 の、システムごとで予め#defineされた識別子(例:unix) は、規格違 反であるから(これらの識別子はユーザーの名前空間(name space)と 衝突する)、削除されたり名前を変更されたことに注意すること。


10.20:
識別子をマクロを使って作り出す、以下の古いコードを使っていた。

#define Paste(a, b) a/**/b

でももう動かなくなってしまった。

A:
これはCの歴史の初期のコンパイラ(有名なのはJohn Reiserが書いた もの)のいくつかが持っていた非公開の機能であった。つまりコメン トはまるっきり消えてなくなってしまうので、トークンの連結に使え た。ANSI規格は(K&R1と同様)、コメントは空白に置き換えられると明 言している。しかし、トークンを連結したいという要求が出され、そ の要求は現実問題に即したものであったから、トークンを連結する演 算子##をANSI規格は適切に定義して導入した。##は以下のように使う ことができる。

#define Paste(a, b) a##b

質問11.17も参照のこと。

References:
ANSI Sec. 3.8.3.3; ISO Sec. 6.8.3.3; Rationale Sec. 3.8.3.3; H&S Sec. 3.3.9 p. 52.


10.22:
なぜ以下のマクロ

#define TRACE(n) printf("TRACE: %d\n", n)

で、「文字列リテラルの内部のマクロ置換」という警告が出るのか。

TRACE(count);

printf("TRACE: %d\count", count);

と展開してるようだ。

A:
質問11.18を参照。


10.23:
マクロの展開で文字列リテラルの内側で引数を使いたい。どうすれば よいか。

A:
質問11.18を参照。


10.25:
コンパイル時に凝った処理をしたいが、どうやってcppにやらせたら よいかわからない。

A:
cppは汎用のプリプロセッサーとしては作られていない。不似合いな ことをcppにさせるよりは、特別な用途のための前処理を行うツール を自分で作ったほうがよい。make(1)のような道具を使えば、自動的 に仕事をさせることができる。

もしC言語以外のものを前処理することを考えているのであれば、汎 用のプリプロセッサーを使うことを考えること。(たいていのUnixシ ステムで使える古くから存在するツールはm4である)。


10.26:
可変個の引数を取るcppのマクロをどうやって書けばよいか。

A:
有名なトリックは、引数を一つ取るマクロを定義して使うときには引 数をカッコで二重にくくる。そうすればプリプロセッサーには引数が 一つに見える。

#define DEBUG(args) (printf("DEBUG: "), printf args)

if(n != 0) DEBUG(("n is %d\n", n));

明らかな欠点は、使い手が余計なカッコを付けることを覚えておかな ければならないことである。 gccは拡張機能として関数のようなマクロが可変数個の引数を取るこ とを許している。しかしこれは標準ではない。別の方法は、引数の数 に応じて別のマクロ(DEBUG1、DEBUG2など)を用意することや、以下の ようにコンマを使うことである。

#define DEBUG(args) (printf("DEBUG: "), printf(args))
#define _ ,

DEBUG("i = %d" _ i)

それよりは適切に定義された方法で可変個の引数を扱う特製の関数を 作るほうがよい。質問15.415.5を参照のこと。

目次へ戻る