BABYL OPTIONS: -*- rmail -*- Version: 5 Labels: Note: This is the header of an rmail file. Note: If you are seeing it in rmail, Note: it means the file has no messages in it.  0, unseen,, *** EOOH *** Newsgroups: fj.archives.answers,fj.lang.c Path: newsmaster.tuis.ac.jp!news.chiba-u.ac.jp!chiba-ns!hagi!ume!igakukei!news.cs.ritsumei.ac.jp!odins-suita!aist-nara!wnoc-tyo-news!sinfony-news01!news-jp-0.abone.net!np0.iij.ad.jp!nspixp!spinnews!spin-hsd0-tky!yhqfm!xeroc!leia!133.140.40.2!kitano From: kitano@crd.yokogawa.co.jp (Kinichi - Kinchan - Kitano) Subject: comp.lang.c Answers to Frequently Asked Questions (FAQ List) in Japanese[1/4] Sender: news@leia.pa.yokogawa.co.jp (Leia news server) Message-ID: Supersedes: Date: Mon, 14 Jul 1997 17:28:42 GMT Reply-To: kitano@crd.yokogawa.co.jp Organization: Yokogawa Electric Corporation, Tokyo, Japan. Followup-To: fj.lang.c Lines: 1475 Xref: newsmaster.tuis.ac.jp fj.archives.answers:1292 fj.lang.c:5588 Archive-name: c-faq-j/part1 Last-modified: 14 July. 1997 ========================= C FAQ 日本語訳[1/4] ========================= [1997年7月14日変更 北野 欽一] (訳注:1996年9月5日版に基づく (頭に[Last modified September 5, 1996 by scs.]とあるもの)) この記事の著作権は1990年から1997年に渡ってSteve Summitに帰属する。書籍 『C Programming FAQs: Frequently Asked Questions』の内容を著者と出版社 の許可の元、社会への貢献のために使用している。このFAQは書籍版を補足す ることを意図としている。内容は各国の著作権法によって守られる。(訳注:日 本語版の著作権は北野 欽一に帰属する。) * * * このニュースグループ(comp.lang.c)に何度も出てくる話題がある。それらは もっともな質問であり、答えもすぐに出てこないものもある。しかし話題にの ぼるたびに、ネットワークの資源と読者の時間が、返答とそういう返答につき ものの誤答の訂正という不毛なことに費やされる。 この記事は毎月投稿する。目的は、これらの繰り返される質問に的確にかつ簡 明に答えることにより、ネットワークでの議論がもっと建設的な話題に移行す るようにもっていくことである。 (訳注:日本語版は現在のところ、変更修正したときにだけ投稿している) どんなに優れた記事も、全範囲をカバーする教科書やC言語のマニュアルを注 意深く読むことの代用とはならない。C言語に関心があって、このグループを 読もうとする者であれば、これらのマニュアルを何冊か、できれば数回読むこ とにも関心を払うことであろう。残念ながらC言語の本やコンパイラのマニュ アルの中には適切でない物がある。ひどいものになると、この記事が論破しよ うとしている迷信をこの世に残すのに貢献している。C言語に関する本で注目 に値する本はこの記事の参考書一覧に挙げてある。興味を持った読者の勉強の 助けになるように、多くの質問や解答には、これらの本の参考すべきページを つけてある(ANSIとISO Cの違いに注意すること。また質問11.1を参照のこと)。 (訳注: 参考書のページ番号は参考書の原著のものであって各参考書の日本語 版のページ番号ではない。) Cに関する質問でこの本に答えの載っていないものがあれば、でたとこ勝負で ネットに質問するのではなく、教科書を何冊かあたるか博識な同僚に相談する こと。ネットには質問に喜んで答えてくれる人がたくさんいるけれど、ネット に参加する人が増えるにつれ、一つの質問にくり返し寄せられる解答の量や質 問の数がうんざりするレベルまで来つつある。(もしもこの記事に質問やコメ ントがあれば、この記事にフォローするのではなくメイルで返答すること。--- この記事はネットワークの負荷を減らすのが目的であって増やすことが目的な のではない---) この記事は、よく質問される質問を挙げるだけでなく、何度も寄せられる解答 もまとめてある。これらの解答をすべて知っていたとしても、このリストを時 折ながめることは、不注意にポストされた質問を見ても、答えることに時間を 無駄にしないことに役立つ。 この記事は1996年9月5日に最後に修正した。この記事がUsenetの我が家を離れ てからすごく遠くまで旅したかもしれない。今となっては時代遅れになってい るかもしれない。印字されたものや手に入れたり、新生代の頃の記事を保存し ているサイトから引っ張り出してきたり、CD-ROMから手に入れたとすれば、と りわけ時代遅れになっている可能性がある。最新版は常にftp.eskimo.comまた はrtfm.mit.eduやftp.uu.netからanonymous ftp可能である(質問18.16と20.40 を参照のこと)。あるいは「help」と本文に書いてmail-server@rtfm.mit.edu に電子メールを送ることでも最新版を手に入れることができる。ときどきこの FAQには修正が入るので、質問の番号は出まわっている古い版や将来の版と違 うかもしれない。FAQの内容を質問の番号で指すときは注意すること。 (訳注:日本語版はftp.kuamp.kyoto-u.ac.jpのpub/cfaqからanonymous ftp 可能である。) なお、この記事は無料で配布することを意図して作成した。手に入れるのに誰 かにお金を払う必要はない。 この記事は別の形態でも提供している。この記事に伴って簡略版と、(内容を 変更したときは)前回の版との差の一覧をポストする。ハイパーテキ スト版がいくつかworld-wide web(WWW)上で利用可能である。 を参照のこと。 最後に、製本されたハードコピー版(答えはもっと長いし、質問の数も多い)のほ うがいい人には、本としてなりたつだけの長さを持った版がAddison-Wesleyか ら1995年の秋に出版された(ISBN 0-201-84519-9)。 (訳注:日本語版は1997年3月にトッパンから『CプログラミングFAQ Cプログラミングの よく尋ねられる質問』として出版された。) この記事は絶えず改善されている。あなたからの情報を歓迎する。コメントは scs@eskimo.comまで。 (訳注:日本語によるコメント、日本語版に対するコメントは kitano@crd.yokogawa.co.jpまで) 質問は以下のように、いくつかの種類に分類される。 1. 宣言と初期化 2. 構造体、列挙体、共用体 3. 式 4. ポインタ 5. ヌルポインタ 6. 配列とポインタ 7. メモリの割り付け 8. 文字と文字列 9. ブール式と変数 10. Cプリプロセッサ 11. ANSI/ISO規格C 12. 標準入出力(stdio)ライブラリ 13. ライブラリ関数 14. 浮動小数点 15. 可変数引数リスト 16. 奇妙な問題 17. スタイル 18. 道具と資源 19. システム依存 20. その他 (質問の番号が連続に振られてないのは、先ほど述べた書籍版に合わせてある からである。書籍版にはもっと多くの質問が載っている。) よくある質問とその質問の答がここから始まる。 1章. 宣言と初期化 1.1: どの整数型を使えばよいか、どうやって決めればよいか。 A: 大きな値(32,767より大きな値か、-32,767より小さい値)が必要なら longを使う。もしメモリの効率を気にするなら(例:大きな配列がある とか構造体がたくさんあるなど)shortを使う。どうでもいいならint を使う。オーバーフローの時の性質の明確なことが大事で負の値が不 要なら、あるいはビットやバイトを操作する際の符合拡張の問題とか かわりたくないなら対応する符号なしの整数を使う(ただし符号付き と符号なしの整数の混在には注意すること)。 文字データ型を(特にunsigned charを)"小さな"整数として使うこと はできるが、そうすることによる予想不能な符号拡張の問題とコード サイズの増加を考えると、その価値よりも伴う面倒のほうが大きくな る場合がある。(unsigned charを使うことは助けになる。関連する問 題については質問12.1を参照のこと) 同じような空間と処理時間を天秤にかける話が、floatを使うべきか doubleを使うべきかの議論にもあてはまる。変数のアドレスが必要で、 そのアドレスの型が特定の型でなければならない場合は、上記の規則 は明らかにあてはまらない。 なんらかの理由で決まった大きさに何かを宣言する必要があるときも、 自分が選択した方法を適切なtypedefで隠すこと(こんなことをしなけ ればいけなくなるのは、たいてい外から押し付けられた保存領域の形に 合わせるためである。このような場合については質問20.5を参照のこ と)。どのデータ型を選んだとしても適切なtypedefで隠すこと。 References: K&R1 Sec. 2.2 p. 34; K&R2 Sec. 2.2 p. 36, Sec. A4.2 pp. 195-6, Sec. B11 p. 257; ANSI Sec. 2.2.4.2.1, Sec. 3.1.2.5; ISO Sec. 5.2.4.2.1, Sec. 6.1.2.5; H&S Secs. 5.1,5.2 pp. 110-114. 1.4: 最近登場した64ビットマシンの、64ビット整数のデータ型は何である べきか。 A: 64ビットマシン向けのC言語のベンダーの一部は、64ビット長の整数 型を提供している。既存のコードのあまりにも多くがintとlongの大 きさが同じであるとか、どちらかがきっちり32ビットであると思いこ んで書かれていることを懸念して、標準ではない新しい64ビット長の データ型long long(あるいは__longlong)を代わりに用意したところ もある。 移植性の高いコードを書くことに関心のあるプログラマは、64ビッ トの大きさのデータ型を適切なtypedefで隠さなければならない。新 しい、もっと大きな整数型を導入せざるをえないと考えているベンダー は、新しい整数型を「少なくとも64ビットある」(それは本当に新し い型である。従来のCにはない)と宣伝すべきで、「ぴったり64ビット」 とは宣伝すべきではない。 References: ANSI Sec. F.5.6; ISO Sec. G.5.6. 1.7: 外部変数を宣言、定義する一番よい方法は。 A: まず、1つのグローバル(厳密にいえば外部)変数や関数に、複数の"宣 言"が(たくさんの変換単位に)存在してもいいが、"定義"はただ1つし か存在してはならない。(定義とは実際に場所を割り付ける宣言で、 初期値があるときは初期値を与える。) 一番よい取り決め (arrangement)は、各定義を関連する.cファイルに置き、外部宣言を ヘッダファイル(".h")に置くことである。そしてヘッダファイル を宣言が必要になったら必ず#includeする。定義をふくんだ (contain).cファイルも同じヘッダファイルを#includeして、コン パイラが定義と宣言を照らし合わせることができるようにする。 この規則は移植可能性の度合を高める。ANSI C規格が要求するところ と一致し、ANSI C規格ができる前のコンパイラやリンカの多くの動 作とも一致する。(Unixのコンパイラやリンカは「共通モデル (common model)」を使う、これは2回以上初期化されないかぎり複数 の定義を許すものである。この動作は「共通の拡張(common extension)」としてANSI C規格に出てくる。両方とも「共通」がつく けれど共通点はない。いくつかの本当に奇妙なシステムは定義と外部 宣言を区別するのに明示的な初期化子(initializer)を必要とするか もしれない。) プリプロセッサを巧みに使って以下のような行を、 DEFINE(int, i); ただ1つのヘッダファイルに1度だけ書いてマクロの設定に応じて 定義にしたり宣言にしたりすることは可能である。しかし、こうまで する必要があるかどうかは疑問である。 ヘッダファイルにグローバル宣言を置くことは、宣言の矛盾をコン パイラに捕まえさせるつもりなら大切である。絶対に外部関数のプ ロトタイプを.cファイルに置いてはならない。一般に、.cに置かれ たプロトタイプは実際の宣言との一貫性をチェックされることはない し、実際の定義と矛盾したプロトタイプは有害無益である。 質問10.6と18.8も参照のこと。 References: K&R1 Sec. 4.5 pp. 76-7; K&R2 Sec. 4.4 pp. 80-1; ANSI Sec. 3.1.2.2, Sec. 3.7, Sec. 3.7.2, Sec. F.5.11; ISO Sec. 6.1.2.2, Sec. 6.7, Sec. 6.7.2, Sec. G.5.11; Rationale Sec. 3.1.2.2; H&S Sec. 4.8 pp. 101-104, Sec. 9.2.3 p. 267; CT&P Sec. 4.2 pp. 54-56. 1.11: 関数宣言についたexternは何を意味するのか。 A: こういう書き方をすることで、関数の定義がたぶん別のソースファイ ルにあるということを、ほのめかすことができる。しかし以下の2つ に違いはない。 extern int f(); int f(); References: ANSI Sec. 3.1.2.2, Sec. 3.5.1; ISO Sec. 6.1.2.2, Sec. 6.5.1; Rationale Sec. 3.1.2.2; H&S Secs. 4.3,4.3.1 pp. 75- 6. 1.12: autoというキーワードは何の役に立つのか。 A: 何もない。それは時代遅れだ。質問20.37も参照のこと。 References: K&R1 Sec. A8.1 p. 193; ANSI Sec. 3.1.2.4, Sec. 3.5.1; ISO Sec. 6.1.2.4, Sec. 6.5.1; H&S Sec. 4.3 p. 75, Sec. 4.3.1 p. 76. 1.14: リンク付きリストをうまく定義することができない。 typedef struct { char *item; NODEPTR next; } *NODEPTR; 上の宣言を試したがコンパイラはエラーを返す。Cの構造体は自身へ のポインタを含むことができないのか。 A: Cの構造体は、自身へのポインタを要素として持つことができる。 K&Rの6.5章の議論と例が、この点を明らかにしてくれる。上の宣言 問題はNODEPTRのtypedefが「next」フィールドを宣言した時点で は完成していないことである。修正するには最初に構造体にタグを付 け(「struct node」)、次にnextフィールドを「struct node *」とし て宣言するか、typedefの宣言と構造体の定義を分離して、typedefの 宣言を構造体の定義のまるっきり前か、まるっきり後ろに持ってくる。 上の修正を両方行なってもよい。修整版の1つは以下のようになる。 struct node { char *item; struct node *next; }; typedef struct node *NODEPTR; 少なくとも他に同じくらい正しい並べ方が3種類のある。 互いに参照しあう構造体の組をtypedefしようとすると、同じような 問題が持ち上がるので、同じような解決策を取ればいい。 質問2.1も参照のこと。 References: K&R1 Sec. 6.5 p. 101; K&R2 Sec. 6.5 p. 139; ANSI Sec. 3.5.2, Sec. 3.5.2.3, esp. examples; ISO Sec. 6.5.2, Sec. 6.5.2.3; H&S Sec. 5.6.1 pp. 132-3. 1.21: charへのポインタを返す関数へのポインタを返す関数へのポイン タN個からなる配列をどうやって宣言すればよいか。 A: この質問は少なくとも3つの方法で答えることができる。 1. char *(*(*a[N])())(); 2. 宣言をtypedefを使って段階的に作り出す。 typedef char *pc; /* charへのポインタ */ typedef pc fpc(); /* charへのポインタを返す関数 */ typedef fpc *pfpc; /* 上記へのポインタ */ typedef pfpc fpfpc(); /* …を返す関数 */ typedef fpfpc *pfpfpc; /* …を指すポインタ */ pfpfpc a[N]; /* …の配列 */ 3. プログラムcdeclを使う。cdeclは英語で書いた宣言をCの変数宣言 に変換し、またその逆変換を行う。 cdecl> declare a as array of pointer to function returning pointer to function returning pointer to char char *(*(*a[])())() cdeclは、複雑な宣言をキャストを使って説明してくれるし、引数が どの括弧の組に入るか示してくれる(上記のような複雑な関数の定義 では)。cdeclはcomp.sources.unixのボリュー ム14に保存されているし(質問18.16参照)K&R第2版にも別のcdeclが出ている。 Cに関するどんなよい教科書も、これらの複雑なCの宣言を「内から外 へ」読む方法について教えてくれる("宣言はその使われかたを真似る")。 上の例の関数へのポインタの宣言は引数の型の情報を含んでいない。 引数が複雑な型を含むときは、宣言は本当に混乱したものとなる(こ こでも新しい版のcdeclは役に立つ)。 References: K&R2 Sec. 5.12 p. 122; ANSI Sec. 3.5ff (esp. Sec. 3.5.4); ISO Sec. 6.5ff (esp. Sec. 6.5.4); H&S Sec. 4.5 pp. 85-92, Sec. 5.10.1 pp. 149-50. 1.22: 同じ型を持つ関数へのポインタを返すことのできる関数をどうやっ て宣言すればよいか。今、状態マシンを作っている。この状態マシン の状態ごとに関数を用意する。そして各関数が次の状態の関数へのポ インタを返す。けれど、こういう関数を宣言する方法が見つからな い。 A: 直接に定義することはできない。汎用の関数ポインタを返す関数を 用意して、ポインタを渡すたびに関数の型にあわせて慎重にキャス トを行う。または関数が構造体を返すようにする。その構造体は、そ の関数へのポインタだけをメンバーに持つようにする。 1.25: 私が使っているコンパイラは関数の無効な再宣言だと文句を付ける。 一度定義して、1回呼んでるだけなのに。 A: 関数を呼び出すところのスコープ内に関数の宣言がないときは(おそ らく最初の関数呼び出しが関数の定義より前にあるからだろう)その 関数はintを返す(かつ引数のデータ型の情報なし)と宣言されている と考える。そのため、後で関数がint以外を返すと宣言されると不一 致が生じる。intを返さない関数は、呼び出す前に宣言しなければな らない。 その外に考えられる問題の種は、その関数がヘッダファイルで宣言 されている別の関数と同じ名前を持っていることである。 質問11.3や15.1も参照のこと。 References: K&R1 Sec. 4.2 p. 70; K&R2 Sec. 4.2 p. 72; ANSI Sec. 3.3.2.2; ISO Sec. 6.3.2.2; H&S Sec. 4.7 p. 101. 1.30: 明示的には初期化されていない変数の初期値について、どこまで安心 して仮定することができるか。グローバル変数の初期値が"0"で初期 化されるのなら、ヌルポインタや浮動小数についても0であること が保証されるのか。 A: "静的な"寿命を持つ変数(すなわち、関数の外で宣言した変数や記 憶域クラスをstaticと宣言した変数)は、プログラマが「=0」と打 ち込んだかのように、0に(プログラムの立ち上がり時に一度だけ)初 期化されることが保証されている。すなわちポインタは(正しい型 の:5章参照)ヌルポインタに、浮動小数点数は0.0に初期化される。 "自動の(automatic)"寿命を持つ変数(すなわち、記憶域クラスを staticと宣言していないローカル変数)の中身は、明示的に初期化し ない限り、ゴミである(ゴミが役に立つかどうかは、予測できない)。 malloc()やrealloc()を使って動的に割り付けた領域も、中身はゴミで あると考えたほうがいい。だから呼び出した側のプログラムで適切に 初期化しなければならない。calloc()によって割り付けた領域はすべて のビットが0である。しかし、この初期化も、ポインタ変数や浮動小 数点数の変数に対しては必ずしも役にたたない(5章と質問7.31を参照)。 References: K&R1 Sec. 4.9 pp. 82-4; K&R2 Sec. 4.9 pp. 85-86; ANSI Sec. 3.5.7, Sec. 4.10.3.1, Sec. 4.10.5.3; ISO Sec. 6.5.7, Sec. 7.10.3.1, Sec. 7.10.5.3; H&S Sec. 4.2.8 pp. 72-3, Sec. 4.6 pp. 92-3, Sec. 4.6.2 pp. 94-5, Sec. 4.6.3 p. 96, Sec. 16.1 p. 386. 1.31: 下のコード、本からそのまま写したのに、コンパイルできない。 f() { char a[] = "Hello, world!"; } A: たぶん、君の使っているコンパイラはANSI規格ができる前のもので 「autoの集成体型(automatic aggregates)」(例:staticでないローカ ルな配列や構造体)の初期化を許してない。回避策としては、配列を static(もし、その後の呼び出しで真っ新なコピーが必要でないなら) かグローバルにする、またはポインタで代用する(もし行列に書き込 むことがないのなら)。(ローカルのchar *変数を文字列リテラルを指 すように初期化することはいつでも可能である。ただし以下の質問 1.32を参照のこと)。上のどの条件もあてはまらないなければ、f()を 呼ぶところでstrcpy()を使って配列を自分の手を煩わして初期化する 必要がある。質問11.29も参照のこと。 1.31a: 以下の初期化のどこがまずいのか。 char *p = malloc(10); コンパイラは「違法な初期化」とかなんとかと文句を付ける。 A: これは静的変数やローカルでない変数の宣言か。 関数呼びだしは、そのような変数の初期化子としては許されていない。 1.32: 以下の二つの初期化の違いは。 char a[] = "string literal"; char *p = "string literal"; p[i]に新しい値を代入しようとするとプログラムがクラッシュする。 A: 文字列リテラルには2種類の少し違った使い道がある。配列の初期 化指定子(char a[]の宣言で使うような)に使うときは、その配列の各 文字の初期値を指定する。その他の場所で使うときは、文字の名なし の静的な配列となる。このときは書き込み禁止のメモリに保存さ れるかもしれない。だから無事に値を変更できない可能性がある。式 が必要な場所では、いつもと同じく(6章を参照のこと)配列はその場 でポインタに変換される。だから2番目の宣言はpを名なしの配列の 最初の要素を指すように初期化する。 (古いコードをコンパイルするために、文字列を書き込み可能にする かどうかを制御するスイッチを持っているコンパイラもある。) 質問1.31, 6.1, 6.2, 6.8も参照のこと。 References: K&R2 Sec. 5.5 p. 104; ANSI Sec. 3.1.4, Sec. 3.5.7; ISO Sec. 6.1.4, Sec. 6.5.7; Rationale Sec. 3.1.4; H&S Sec. 2.7.4 pp. 31-2. 1.34: やっとのことで関数へのポインタを宣言する構文を理解した。さて どうやってこれを初期化すればよいか。 A: 以下のような方法を取る。 extern int func(); int (*fp)() = func; 関数名が、式に現われたが実行されないときは配列名と同じようにポ インタに成り下がる(つまり、そのアドレスが暗黙のうちに取られ る)。 一般に関数の明示的な宣言が必要である。なぜならこの場合、暗黙の 外部宣言は起こらないから(初期化子に現れる関数の名前は関数呼び 出しの一部ではないから)。 質問1.25と4.12も参照のこと。 2章 構造体、共用体、列挙体 2.1: 以下の二つの宣言の違いは。 struct x1 { ... }; typedef struct { ... } x2; A: 最初の形は「構造体タグ」を宣言している。2つ目の形は「typedef」 を宣言している。主な違いは1つ目のデータ型は「struct x1」として 参照できるのに対し2つ目のデータ型は「x2」として参照できる。つ まり主な違いは2番目の宣言のほうが少しだけ抽象データ型らしいこ とである。使う側は それが構造体であることを知っている必要がな い。また変数を宣言する際にstructというキーワードを使う必要がな い。 2.2: なぜ以下のコードがうまく動かないのか。 struct x { ... }; x thestruct; A: CはC++ではない。typedefした名前は構造体タグとして自動的に生成 されるわけではない。上の質問2.1も参照のこと。 2.3: 構造体は自身へのポインタをメンバーとして含んでいてもいいのか。 A: もちろん。質問1.14も参照のこと。 2.4: Cであいまいな(抽象)データ型を実装する一番よい手は。 A: よい方法の1つは使う側が構造体へのポインタを(たぶんtypedefで隠 すことまでして)使うことである。そのポインタが、定義してあるこ とを公開していない構造体データ型を指す。 2.6: 以下のように構造体を定義して、 struct name { int namelen; char namestr[1]; }; 巧妙にメモリ割り付けをして、配列namestrが複数の要素を持つよう に振る舞わせているコードを見た。このコードは許されるのか。移植 性は高いのか。 A: この技巧は人気がある。ただしDennis Ritchieは「Cの実装への根拠 のない馴れ馴れしさ」と呼んでいる。公式な解釈によると上の技巧は Cの規格に厳密には準拠していないと考えられる。(この技法が正しい かどうかを取り巻く議論を尽くすことは、このFAQの守備範囲を越え ている。) しかし世の中に知られている全てのコンパイラの実装で、 この方法は移植性が高いようである。(配列の境界を注意深くチェッ クするコンパイラは警告を出すかもしれない。) 別のやり方としては、可変長の要素の大きさを、小さく取るのではな く、非常に大きく取ることが考えられる。上の例は以下のように書き 換えることができる。 ... char namestr[MAXSIZE]; ... ここでMAXSIZEは配列namestrに保存されうるどんな名前よりも大きく 取る。しかしながらこの技法も、規格の厳密な解釈によると許されな いようだ。さらに上のどちらの"chummy"構造体も注意して使わなけれ ばならない。プログラマのほうがコンパイラよりも構造体の大きさに ついて知っているからである。(特に、こうした構造体は一般にポイ ンタを通してしか操作できない。) References: Rationale Sec. 3.5.4.2. 2.7: 構造体を、変数に代入することも、関数に引数として渡すことも、関 数の戻り値としても使うこともできると聞いた。けれどK&R初版には できないと書いてある。 A: K&R初版に書いてあるのは、構造体への演算の制限は将来のコンパイ ラでは取り除かれるだろうということである。実際K&Rの初版が発行 された時点のDennis Ritchieのコンパイラには、構造体の代入も構造 体の引数渡しも用意されていた。初期のCコンパイラの中には対応し ていないものも存在したが、現在のコンパイラはすべて対応している し、ANSI C規格の一部でもある。よって使うことをためらうことはな い。 (構造体に代入したり引数として渡したり関数の戻り値とするときに は、データは単純複写される。ポインタフィールドがあったとしても その指す先はコピーされない。) References: K&R1 Sec. 6.2 p. 121; K&R2 Sec. 6.2 p. 129; ANSI Sec. 3.1.2.5, Sec. 3.2.2.1, Sec. 3.3.16; ISO Sec. 6.1.2.5, Sec. 6.2.2.1, Sec. 6.3.16; H&S Sec. 5.6.2 p. 133. 2.8: なぜ構造体を比較することはできないのか。 A: 構造体の比較を、ハードウエアに近い部分の操作も可能というC言語 の特徴を殺さず、コンパイラにやらせる単純明解な方法はない。バイ ト単位の比較は、構造体の中の使われていない"穴"(このような埋め 草は、後ろのフィールドの配置が正しくなるように使われる)にどん なビットパターンが来るかもしれないことを考えると使えない。フィー ルド単位の比較は、大きな構造体が対象のときに、とんでもない量の くり返しのインラインのコードを必要とする。 二つの構造体を比較したいのなら、フィールド単位に比較する関数を 自分自身で書かなければならない。 References: K&R2 Sec. 6.2 p. 129; ANSI Sec. 4.11.4.1 footnote 136; Rationale Sec. 3.3.9; H&S Sec. 5.6.2 p. 133. 2.9: どんな仕組みで構造体を引数で渡したり、関数の戻り値に使うことが できるのか。 A: 構造体が関数の引数として渡されるとき、構造体全体が、必要なだけ のワードを使ってスタックに積まれる(プログラマは、このオーバー ヘッドを嫌って構造体へのポインタを代わりによく使う)。 構造体はコンパイラが用意する領域に置かれて関数から返ってくるの が一般的である。コンパイラは、この領域のアドレスを、特別な"隠 れた"引数として、呼ばれる側の関数に渡す。古いコンパイラの中に は、構造体を返すのに特別な静的な領域を使うものもあった。これは 関数を再入(リエントラント)不能にするので、ANSI Cは禁止している。 References: ANSI Sec. 2.2.3; ISO Sec. 5.2.3. 2.10: 構造体を引数として取る関数に定数値をどうやって渡せばよいか。 A: C言語には名前のない構造体の値を作り出す方法は存在しない。一時 的に構造体の変数を使うか、構造体を作り出すちょっとした関数を用 意しなければいけない。(gccは構造体の定数を拡張機能として用意し ている。こういう仕組みはC規格の将来の拡張でたぶん追加されるだ ろう。) 質問4.10も参照のこと。 2.11: 構造体をデータファイルから読む、あるいはデータファイルに書き込 むのはどうすればよいか。 A: fwrite()を使って構造体をデータファイルに書き込むのはそんなに難 しくない。 fwrite(&somestruct, sizeof somestruct, 1, fp); これに対応するfread()を使えば読み返すことができる。(ANSI Cが決 まる前のCでは、最初の引数に(char *)のキャストが必要である。大 事なのは、fwrite()はバイトへのポインタを受け取るのであって、 構造体へのポインタを受け取るのではないということである。) し かし、こうやって書き込んだデータファイルは、たいして移植性が高 いわけではない(質問2.12と20.5を参照のこと)。構造体がポインタ を含んでいたとしてもポインタの値が書かれるだけで、その値は読 み返したときに有効であるとは考えにくいということに注意すること。 最後に、高い移植性のためにはfopen()でファイルを開くときに「b」 フラグが必要なことにも注意すること。質問12.38を参照。 もっと移植性の高い解決策は、段取りに少し手間がかかるけれど、移 植性の高い方法で(たぶん人間にも読みやすい方法で)フィールド単位 で構造体を読む関数と書く関数を1組用意することである。 References: H&S Sec. 15.13 p. 381. 2.12: 私が使っているコンパイラは構造体の内部に穴を開けるので、領域は 無駄になるし、データを保存した外部のファイルに対して"バイナリ "で入出力することができない。この埋め草を止めたり、構造体の整 列を制御することはできないのか。 A: 君が使っているコンパイラは、詰め物をするかどうかの制御をおこな う拡張機能を(たぶん#pragmaで、質問11.20参照)用意しているかもし れない。ただし標準化された方法はない。 質問20.5も参照のこと。 References: K&R2 Sec. 6.4 p. 138; H&S Sec. 5.6.4 p. 135. 2.13: 構造体にsizeof演算子を使ったら、私が思っていたよりも大きな大き さを返してきた。まるで、おしりに詰め物がしてあるようだ。 A: 構造体は、構造体を連続的に配置したときに各構造体の先頭が整列す るように、このような詰め物を持つ場合がある(内部に詰め物をする こともある)。たとえ構造体が配列の一部でないとしても、sizeofが 一貫した値を返すように、おしりの詰め物は付いたままとなる。上の 質問2.12を参照のこと。 References: H&S Sec. 5.6.7 pp. 139-40. 2.14: 構造体内のフィールドのバイトオフセットを知る方法は。 A: ANSI Cは、offsetofマクロを用意しているので、用意されている場合 は使うこと。を参照。もし手に入れることができなければ、 実装の1つは以下のようになる。 #define offsetof(type, mem) ((size_t) \ ((char *)&((type *)0)->mem - (char *)(type *)0)) この実装も100%の移植性を持つわけではない。コンパイラの中には、 はねつけるものがあるかもしれないが、それはそれで文法的に正しい。 次の質問2.15への解答を、使い方の参考にすること。 References: ANSI Sec. 4.1.5; ISO Sec. 7.1.6; Rationale Sec. 3.5.4.2; H&S Sec. 11.1 pp. 292-3. 2.15: どうやれば構造体のフィールドを、実行時に名前でアクセスできるか。 A: まずoffsetof()マクロを使って名前とオフセットの対応表を用意する。 構造体aのフィールドbのオフセットは、 offsetb = offsetof(struct a, b) で与えられる。もし以下の式でstructpが、構造体の実体へのポイン タで、bが上で計算したオフセットを持つintのフィールドとすると、 bの値は間接的に *(int *)((char *)structp + offsetb) = value; として得られる。 2.18: 以下の関数は正しい結果を出力するけれど終了した時点でcoreを吐く。 なぜか。 struct list { char *item; struct list *next; } /* ここからmainプログラムが始まる */ main(argc, argv) { ... } A: セミコロンが抜けたことで、関数mainは構造体を返すとコンパイラに 思いこませてしまった。(間に入ったコメントが、構造体とmainが結び 付いていることをわかりにくくしている。) 構造体を戻り値として持 つ関数は、たいてい隠れた戻り値のポインタ(質問2.9を参照)を持 つように実装されるので、上のmain()に対して生成されたコードは3 つの引数を取ろうとする。このうち2つしか渡されない(この場合は、C言 語のスタートアップのコードによって)。質問10.9と16.4も参照のこ と。 References: CT&P Sec. 2.3 pp. 21-2. 2.20: 共用体を初期化することはできるか。 A: 共用体の最初のメンバーを初期化に使うことをANSI C規格は許してい る。他のメンバーを初期化する標準的な方法はない(そもそもANSI規 格成立より前のたいていのコンパイラでは、どのメンバーを使っても初 期化することはできなかった)。 References: K&R2 Sec. 6.8 pp. 148-9; ANSI Sec. 3.5.7; ISO Sec. 6.5.7; H&S Sec. 4.6.7 p. 100. 2.22: プリプロセッサで#defineを複数使うこととコンパイラで列挙体を 使うことの違いは。 A: 現状ではほとんど違いはない。多くの人が望んだ方向とは反対に、C 規格は、列挙体とそのほかの整数型を混合して使っても問題ないと述 べている。 (もしそのような混合が明示的なキャストなしには使えな いとしたら、列挙体をよく考えて使えば、ある種のプログラミ ングの誤りを捕まえることができるのであるが。) 列挙体の利点としては値が自動的に与えられることと、デバッガを 使って列挙体の値を調べる時にデバッガがシンボリックな値を表示 してくれるかもしれないということ、また列挙体のスコープがブロッ クであることが挙げられる(コンパイラは、列挙体と整数が見境なく 混合して使われたときに、致命的ではない警告を出すかもしれない。 そのように混ぜて使うことは、厳密にいえば文法違反ではないけれど、 よくない作法と考えられるからである)。欠点としてはプログラマ がデータの大きさを(さきほどの致命的でない警告についても)ほとんど 制御できないことが挙げられる。 References: K&R2 Sec. 2.3 p. 39, Sec. A4.2 p. 196; ANSI Sec. 3.1.2.5, Sec. 3.5.2, Sec. 3.5.2.2, Appendix E; ISO Sec. 6.1.2.5, Sec. 6.5.2, Sec. 6.5.2.2, Annex F; H&S Sec. 5.5 pp. 127-9, Sec. 5.11.2 p. 153. 2.24: 列挙体の値を(値ではなく)シンボルで表示する楽な方法はないのか。 A: ない。列挙体の定数を文字列に対応させるちょっとした関数を書けば いい。(デバッグのことしか気にしてないのなら、よくできたデバッ ガを使えば勝手に列挙体の定数をシンボルで表示してくれるので心 配ない。) 3章 式 3.1: なぜ以下のコードはうまく動かないか。 a[i] = i++; A: i++は副作用をもたらす。つまりiの値を変更する。同じ式の他のとこ ろでiが参照されているので、このことが未定義の動作を引き起こす (K&Rの言い回しではこの式の動作は未規定であると取れるが、C規格 は一段と厳しく未定義であるとしている。-- 質問11.33を参照のこと)。 References: K&R1 Sec. 2.12; K&R2 Sec. 2.12; ANSI Sec. 3.3; ISO Sec. 6.3. 3.2: 私が使っているコンパイラでは以下のコードで49を出力する。 int i = 7; printf("%d\n", i++ * i++); 評価の順にかかわりなく、56を出力するのではないのか。 A: 後置増分演算子++も後置減分演算子--も元の値を返した後で、その演 算を行う。この"後"の意味付けがしばしば誤解を招く。前回値を産み 出したすぐ後で、かつ式のその他の部分が評価される前に、++や--が 増分や減分を行うことは保証されていない。単に式が"終了"する前 (ANSI Cの用語でいえば次の"副作用完了点"(sequence point)の前。 質問3.8を参照)のどこかで更新が実行されることが保証されているだ けである。上の例では、元の値と元の値を掛け合わせてから両方の増 分を実行することをコンパイラは選んだ。 副作用があいまいな部分が複数あるコードの動作は、常に未定義であ る。(大まかに言うと、「副作用があいまいな部分が複数ある」とは、 ++, --, =, +=, -=, etc. の組み合わせのいずれかが1つの式の中に 現われ、同じオブジェクトが2回変更されたり、変更されてから値を 調べられることを意味している。これは荒っぽい定義である。正確な 定義は質問3.8を参照のこと。"未定義"の意味については質問11.33を 参照のこと。) 自分の使っているコンパイラがそのような副作用を持 つコードをどう実装しているか調べるようなこともしてはいけない (多くのCの教科書に載っている愚かな練習問題であるが、お薦めはし ない)。K&Rが指摘しているように「もし様々なマシンでどうやってやっ て実行してるのか知らなければ、知らないことが助けとなるかもしれ ない。」 References: K&R1 Sec. 2.12 p. 50; K&R2 Sec. 2.12 p. 54; ANSI Sec. 3.3; ISO Sec. 6.3; CT&P Sec. 3.7 p. 47; PCS Sec. 9.5 pp. 120-1. 3.3: 以下のコードをいくつかのコンパイラでコンパイルして実験してみた。 int i = 3; i = i++; iの値として、3を与えるもの4を与えるもの7を与えるものがあった。 上のコードの動作は未定義であるのは知っている。しかしどうし て7になるようなことがあるのか。 A: 未定義の動作というのは、どんなことが起っても不思議がないことを 意味している。質問3.9と11.33を参照のこと。(i++も++iもi+1とは違 うことに注意すること。iに1加えたかったら、i=i+1かi++か++iか i+=1のどれを使ってもいい。ただし組み合わせてはいけない。質問 3.12も参照のこと。) 3.4: 括弧をつけて評価の順をこっちの好きなようにすることができるか。 もしできないとして、優先順位が評価の順を決めるのではないのか。 A: 常に順番を指定できるわけではない。 演算子の優先順位や括弧を付けることは、式の評価に部分的に順序付 けをするだけである。以下の式を考えて見よう。 f() + g() * h() 掛け算は足し算の前に起こるということはわかっているけれど、3つ の関数のうちどの関数が最初に呼ばれるかはわからない。 式の一部の評価の順序を保証する必要があるなら、一時変数を用意し て複数の文に分ける必要があるかもしれない。 References: K&R1 Sec. 2.12 p. 49, Sec. A.7 p. 185; K&R2 Sec. 2.12 pp. 52-3, Sec. A.7 p. 200. 3.5: それでは&&や||や,(コンマ)はどうなるのか。 while((c = getchar()) != EOF && c != '\n')... のようなコードを見たことがあるが。 A: これらの演算子は(3項演算子?:も同様だが)特例であって、左から右 へ順に評価することが保証されている(間に入る評価順序点と同じで ある。質問3.8を参照のこと)。Cに関するどんな本でも、この点につ いて明白にしてくれるはずである。 References: K&R1 Sec. 2.6 p. 38, Secs. A7.11-12 pp. 190-1; K&R2 Sec. 2.6 p. 41, Secs. A7.14-15 pp. 207-8; ANSI Sec. 3.3.13, Sec. 3.3.14, Sec. 3.3.15; ISO Sec. 6.3.13, Sec. 6.3.14, Sec. 6.3.15; H&S Sec. 7.7 pp. 217-8, Sec. 7.8 pp. 218-20, Sec. 7.12.1 p. 229; CT&P Sec. 3.7 pp. 46-7. 3.8: 複雑な式をどうやって理解すればいいのか。「副作用完了点 (sequence point)」とはなにか。 A: 副作用完了点とは騒ぎがおさまってすべての副作用が完了したことが 保証されている(時)点である(つまり式の本当の終わり、 ||,&&,?:,","(カンマ演算子)、関数呼び出しの直前)。 ANSI/ISO C規格 は以下のように述べている、 直前の副作用完了点と次の副作用完了点までの間に、 式の評価によってオブジェクトに格納された値を 変更する回数は高々一度だけでなければならない。 更に、変更前の値は、格納される値を決定するため だけにアクセスしなければならない。 2番目の文は理解しにくい。オブジェクトに式の中で書き込むなら、 同じ式内でのそのオブジェクトに対する如何なる且つすべてのアクセ スは書き込むべき値の計算のためでなければならないと言っている。 この規則により合法な式はアクセスが変更の前に明らかに来るものに 実質的に限定される。 以下の質問3.9を参照のこと。 References: ANSI Sec. 2.1.2.3, Sec. 3.3, Appendix B; ISO Sec. 5.1.2.3, Sec. 6.3, Annex C; Rationale Sec. 2.1.2.3; H&S Sec. 7.12.1 pp. 228-9. 3.9: 以下のコードがあったとして、 a[i] = i++; a[]のどの要素に書きこまれるかはわからないが、iは確実に1つ大き くなるね。 A: 違う。式やプログラムが未定義になったら、すべての面で未定義とな る。質問3.2, 3.3, 11.33, 11.35を参照のこと。 3.12: 式の値を使わないとして、変数に1を加えるのにi++と++iのどちらを 使えばよいのか。 A: この二つは産み出す値が違うだけで、副作用だけを必要とするときは まったく同じである。(ただし、C++では前に付けるほうが望ましい。) 質問3.3も参照のこと。 References: K&R1 Sec. 2.8 p. 43; K&R2 Sec. 2.8 p. 47; ANSI Sec. 3.3.2.4, Sec. 3.3.3.1; ISO Sec. 6.3.2.4, Sec. 6.3.3.1; H&S Sec. 7.4.4 pp. 192-3, Sec. 7.5.8 pp. 199-200 3.14: なぜ以下のコードは私が思った通りに動かないのか。 int a = 1000, b = 1000; long int c = a * b; A: Cの整数の拡張規則により、掛け算がintで実行される。結果は左辺の long intの変数に拡張されて代入される前に桁あふれしたり捨てられ てしまうかもしれない、longで演算されるよう明示的にキャストを使 う。 long int c = (long int)a * b; (long int)(a * b)では望む結果が得られないことに注意すること。 整数変数同士割算をおこなって結果を浮動小数点型の変数に代入する ときにも同じような問題が起こる。 References: K&R1 Sec. 2.7 p. 41; K&R2 Sec. 2.7 p. 44; ANSI Sec. 3.2.1.5; ISO Sec. 6.2.1.5; H&S Sec. 6.3.4 p. 176; CT&P Sec. 3.9 pp. 49-50. 3.16: 条件によって、2つの変数のどちらかに代入する複雑な式がある。以 下のようなコードを使ってもよいか。 ((condition) ? a : b) = complicated_expression; A: 使えない。?:演算子は、たいていの演算子と同じように、値を産み出 す。値に代入することはできない。(言い換えると?:は左辺値を産み 出さない)。 本当にやりたいなら、以下のようなやり方を試せばいい。 *((condition) ? &a : &b) = complicated_expression; けれど、これはお世辞にも美しいとはいえない。 References: ANSI Sec. 3.3.15 esp. footnote 50; ISO Sec. 6.3.15; H&S Sec. 7.1 pp. 179-180. 4章 ポインタ 4.2: ポインタを宣言して、そのポインタ用の領域を割り付けようとして いる。でもうまくいかない。以下のコードのどこがおかしいのか。 char *p; *p = malloc(10); A: 君が宣言したポインタはpであって*pではない。ポインタにどこ かを指させるにはポインタの名前を使うだけでいい。 p = malloc(10); 指した先のメモリを操作するときに初めて*を間接演算子として使う。 *p = 'H'; 質問1.21, 7.1, 8.3も参照のこと。 References: CT&P Sec. 3.1 p. 28. 4.3: *p++はpを増分するか。それともpが指すものを増分するのか。 A: *, ++, --のような単項演算子は、右から左に結び付ける。だから *p++はpを増分する(そして増分する前にpが指していた値を返す)。p が指していた値を増分するには、(*p)++を使う(副作用が起こる順序 が問題でないなら++*pでもいいだろう)。 References: K&R1 Sec. 5.1 p. 91; K&R2 Sec. 5.1 p. 95; ANSI Sec. 3.3.2, Sec. 3.3.3; ISO Sec. 6.3.2, Sec. 6.3.3; H&S Sec. 7.4.4 pp. 192-3, Sec. 7.5 p. 193, Secs. 7.5.7,7.5.8 pp. 199- 200. 4.5: charのポインタがあって、そのポインタがたまたまintを指して いた。intの次へポインタを進めたい。なぜ以下の式ではうまくい かないのか。 ((int *)p)++; A: C言語でキャスト演算子は「ビットが別の型を持っているふりをして、 そういう風に扱ってやろう」ということを意味していない。キャスト は変換演算子であって、それは右辺値を生みだすと定義されている。 右辺値であるとするなら、代入することも++で足し算することもでき ないことになる(pccから派生したコンパイラやgccの拡張機能が上の ような式を受け付けることは例外である)。思っていることを式にあ らわそう。以下のようにする。 p = (char *)((int *)p + 1); あるいは(pはchar *なので)単に p += sizeof(int); 可能なときはいつでも、最初から適切なポインタ型を選ぶべきであ る。ある型を別の型とみなそうとしてはいけない。 References: K&R2 Sec. A7.5 p. 205; ANSI Sec. 3.3.4 (esp. footnote 14); ISO Sec. 6.3.4; Rationale Sec. 3.3.2.4; H&S Sec. 7.1 pp. 179-80. 4.8: ポインタを引数として取って、そのポインタを初期化することに なっている関数がある。 void f(ip) int *ip; { static int dummy = 5; ip = &dummy; } けれど以下のように呼んだら、 int *ip; f(ip); 呼んだ側のポインタの値は変わらなかった。 A: この関数が実際に初期化するものが、君が初期化してくれてると思っ たものと一致している自信があるか。C言語の引数は値渡しであるこ とをお忘れなく。呼ばれた側の関数は、ポインタの渡されたコピーを 変更するだけである。お望みの結果を得るには、ポインタのアドレス を渡すか(関数はポインタへのポインタを受けとることになる)、関数 がポインタを返すようにする。 質問4.9,4.11も参照のこと。 4.9: 参照呼び出しに使うため汎用のポインタを関数に渡すのに、 void **を使うことができるか。 A: 移植性まで考れば不可能である。C言語には汎用のポインタへのポ インタ型は存在しない。void *が汎用のポインタとして振る舞う のは、他の型のポインタをvoid *に設定したり参照するときに、自 動的に変換が行われるからである。void *以外の何かを指している void **を使って間接参照しようとすると、この変換は実行できない (隠れた正しいポインタの型がわからない)。 4.10: 以下の関数がある。 extern int f(int *); これは整数へのポインタを引数として受け取る。どうすれば定数を 参照渡しすることができるか。以下のような呼び出しは、 f(&5); うまくいかないようだ。 A: 一発では無理だ。一時変数を定義して、その変数のアドレスを関数に 渡さなければならない。 int five = 5; f(&five); 質問2.10, 4.8, 20.1も参照のこと。 4.11: Cにも"参照渡し"が用意されてるか。 A: まさか。厳密にいえば、Cはいつも値渡しである。手をわずらわして 参照渡しを真似ることはできる。ポインタを引数として取る関数を定 義し、その関数を呼ぶときに引数に&演算子を使う。あるいは関数に 配列を渡すと(代わりにポインタを渡すことで。質問6.4等を参照)コ ンパイラは実質的に参照渡しのふりをしてくれる。しかしCは正式の 参照渡しや、C++の参照パラメータに真に等価なものは持ち合わせて いない。(ただし、関数に似せたプリプロセッサのマクロは、一種の" 名前呼び出し"を提供する。) 質問4.8と20.1も参照のこと。 References: K&R1 Sec. 1.8 pp. 24-5, Sec. 5.2 pp. 91-3; K&R2 Sec. 1.8 pp. 27-8, Sec. 5.2 pp. 91-3; ANSI Sec. 3.3.2.2, esp. footnote 39; ISO Sec. 6.3.2.2; H&S Sec. 9.5 pp. 273-4. 4.12: 関数を呼ぶのに、ポインタを通す方法をみたことがある。どうなっ てるの。 A: もともとは関数へのポインタは*演算子を使って(かつ優先順位をはっ きりさせるためのおまけの括弧を使って)"本物の"関数へ変換しな ければならなかった。 int r, func(), (*fp)() = func; r = (*fp)(); また以下のように主張することも可能である。関数は常にポインタ 経由で起動され"本物の"関数の名前は常にポインタに成り下がる (式の中で、初期化のときと同じ様に。質問1.34参照)から何も問題は ない。こういう裏付けがpccを通して広がりANSI規格にも採用された。 すなわち、 r = fp(); はfpが関数の名前でも、関数へのポインタでも正しく問題なく動く (この書き方は始めからあいまいさの入る余地の無いものであった。 関数へのポインタに引数が続いていれば、そのポインタが指している 関数を呼ぶしか仕方がないもの)。*を書くことは害がないし、今まで 通り許される(許されるどころか、古いコンパイラとの互換性が重要 であれば使うことを勧める)。 質問1.34も参照のこと。 References: K&R1 Sec. 5.12 p. 116; K&R2 Sec. 5.11 p. 120; ANSI Sec. 3.3.2.2; ISO Sec. 6.3.2.2; Rationale Sec. 3.3.2.2; H&S Sec. 5.8 p. 147, Sec. 7.4.3 p. 190. 5章 ヌルポインタ 5.1: そもそもこの悪名高いヌルポインタとは何か。 A: C言語の定義によればどんなポインタの型にも特別な値、すなわち 「ヌルポインタ」が存在する。このヌルポインタは他のどんなポイン タの値とも区別可能で、「いかなるオブジェクトや関数へのポインタ と比較しても等しくなることがないことを保証されている」。すなわ ちアドレス演算子&を適用した結果がヌルポインタとなることはない。 またmallocの呼び出しに成功した場合の戻り値がヌルポインタの場合 もない。(mallocは領域割り付けに失敗した場合にヌルポインタを返 す。これがヌルポインタの典型的な使い方である。その値によりアド レス以外の意味をあらわす特別なポインタの値で、たとえば「領域割 り付けの失敗」とか、まだ「何も指していない」のような意味を持つ。) ヌルポインタは、初期化されていないポインタと異なる概念を持つ。 ヌルポインタは、何も指していないことが保証されている。 初期 化されていないポインタは、どこを指しているかわからない。質問 1.30、7.1、7.31を参照のこと。 上の定義のところで述べたように、各ポインタの型ごとにヌルポイ ンタが存在する。ヌルポインタの内部構造はポインタの型によっ て異なるかもしれない。プログラマは内部構造について知る必要は ない。コンパイラは必要なら区別が付けられるように、どの型のヌル ポインタが必要か知る必要がある(以下の質問5.2, 5.5, 5.6を参照)。 References: K&R1 Sec. 5.4 pp. 97-8; K&R2 Sec. 5.4 p. 102; ANSI Sec. 3.2.2.3; ISO Sec. 6.2.2.3; Rationale Sec. 3.2.2.3; H&S Sec. 5.3.2 pp. 121-3. 5.2: どうやればプログラムの中でヌルポインタを得ることができるのか。 A: C言語の定義によれば、ポインタを書くべきところに現れた定数0は、 コンパイル時にヌルポインタに変換される。すなわち初期化・代入・ 比較をするときに左辺/右辺のどちらかにポインタ型の変数か式が 現れたときは、コンパイラはもう一方の側の定数0がヌルポインタ を要求していることを理解し、適切なデータ型のヌルポインタの値 を産み出す。したがって以下のCプログラムの断片は正しい。 char *p = 0; if(p != 0) (質問5.3も参照。) しかし関数に渡される引数は必ずしもポインタを表しているかどう か識別可能ではない。そのときコンパイラはキャストのついていない 定数0がヌルポインタを意味していることを判別できないかもしれ ない。関数呼び出しの状況でヌルポインタを産み出すには、0がポ インタを表わしていることを認識させるために明示的なキャストが 必要となるかもしれない。例えばUnixシステムコールのexeclの引数 は、文字列のポインタの可変個のリストで、ヌルポインタで終わ る。正しい呼び出しは以下のようになる。 execl("/bin/sh", "sh", "-c", "date", (char *)0); もしキャスト(char *)を省略するとコンパイラはヌルポインタを渡す というふうには理解せず、整数0を代わりに渡す。(Unixのマニュアル で、この例に誤解を招くような説明をしているものが多い。) 関数プロトタイプがスコープ内なら、引数渡しの話は"代入の話"とな り、たいていキャストを省略しても問題ない。なぜならキャストの付 いていない0がポインタであることと、どの型のポインタが必要であ るということをプロトタイプがコンパイラに教えるので、コンパイラ は0を正しくポインタに変換することができる。しかし関数プロトタ イプは可変個の引数をもつ関数の引数リストに型の情報を与えること が出来ない。そこで可変個の引数を持つ関数の場合は明示的なキャス トが必要である(質問15.3参照)。可変個の引数を持つ関数や関数プロ トタイプを持たない関数については、すべてのヌルポインタの引数を 明示的にキャストすることが常に一番安全な方法である。こうすれば、 vargsの関数やプロトタイプなしの関数が来てもいいし、ANSI対応で ないコンパイラを一時的に使うこともできるし、プログラマがポイン タについて理解してプログラムを書いたということをコンパイラに伝 えることができる(ちなみに、こう覚えておくのが一番簡単である)。 要約すると: 0にキャストをつけないでもよい場合: 明示的なキャストが必要 が必要な場合: 初期化 プロトタイプがスコープに ないときの関数呼び出し 代入 可変個引数の関数引数 比較 関数プロトタイプがスコープに入って いるときの引数の数が固定の関数引数 References: K&R1 Sec. A7.7 p. 190, Sec. A7.14 p. 192; K&R2 Sec. A7.10 p. 207, Sec. A7.17 p. 209; ANSI Sec. 3.2.2.3; ISO Sec. 6.2.2.3; H&S Sec. 4.6.3 p. 95, Sec. 6.2.7 p. 171. 5.3: ポインタがヌルポインタでないかどうかのテストの省略形 「if(p)」は有効なのか? ヌルポインタの内部表現が0でない場合は どうなるのか。 A: C言語が式のブール値を必要とする場合(if、while、forやdo文におい て、また&&、||、!、?:演算子と共に使う場合)、0と比較して等しい 場合は偽の値が産み出され、その他の場合は真が産み出される。すな わち if(expr) と書いたらいつも、「expr」がどんな式かにかかわらずコンパイラは 必ず if((expr) != 0) と書かれたように基本的には動作する。ポインタ式「expr」をpに 置き換えると、 if(p) は if(p != 0)と同じ ということになる。比較をするので、コンパイラは(式では現れれな いが)0がヌルポインタを表していると判断して正しいヌルポインタ の値を使う。インチキはない。コンパイラはこのように動き、どちら の式についても同じコードを産み出す。ポインタの内部表現は関係 「ない」。 論理否定演算子!は以下のように記述することができる。 !expr は基本的に (expr)?0:1 と同じ あるいは、((expr) == 0) と同じ これから以下の結論が得られる。 if(!p) は if(p == 0) と同じ。 if(p)のような"省略形"は、文法的には正しいけれど、よくない書き 方であると考える人もいる(よい書き方であると考える人もいる。質 問17.10を参照)。 質問9.2も参照。 References: K&R2 Sec. A7.4.7 p. 204; ANSI Sec. 3.3.3.3, Sec. 3.3.9, Sec. 3.3.13, Sec. 3.3.14, Sec. 3.3.15, Sec. 3.6.4.1, Sec. 3.6.5; ISO Sec. 6.3.3.3, Sec. 6.3.9, Sec. 6.3.13, Sec. 6.3.14, Sec. 6.3.15, Sec. 6.6.4.1, Sec. 6.6.5; H&S Sec. 5.3.2 p. 122. 5.4: NULLとは何で、どう#defineされているのか? A: 書き方として、キャストのない0がプログラム内にあちこち散らばっ ているのを好まない人がたくさんいる。そこでマクロNULLが (に)0として定義されている。(void *)でキャ ストされているかもしれない(質問5.6参照)。整数0とヌルポインタ 定数の違いをはっきりさせたい場合は、NULLをヌルポインタが必要で あるところならどこに使用してもよい。 NULLを使うことは書き方の約束事でしかない。プリプロセッサはNULL を0に戻し、その0はコンパイラによってポインタとして解釈される。 それでも特に関数の引数ではNULLに(0にも必要であるのと同じで)明 示的なキャストが必要かもしれない。質問5.2の下の表は0だけでなく NULLにもあてはまる(キャストの付いていないNULLはキャストの付い ていない0と同等である)。 NULLはポインタとしてのみ使うべきである。質問5.9を参照のこと。 References: K&R1 Sec. 5.4 pp. 97-8; K&R2 Sec. 5.4 p. 102; ANSI Sec. 4.1.5, Sec. 3.2.2.3; ISO Sec. 7.1.6, Sec. 6.2.2.3; Rationale Sec. 4.1.5; H&S Sec. 5.3.2 p. 122, Sec. 11.1 p. 292. 5.5: ヌルポインタの内部表現に0でないビットパターンを使っているマ シンでは、NULLはどう定義するべきか。 A: ほかのどんなマシンとも同じである。0 (または((void *)0))と定義 されている。 プログラマがプログラム中に「0」や「NULL」と書いてヌルポイン タを要求したときに、そのマシンがどんなビットパターンをヌルポ インタを表現するのに使っていたとしても、ヌルポインタを作り 出すのはコンパイラの仕事である。だからヌルポインタの内部表現 が0でないマシンでNULLを0に#defineするのは他のマシン上とおなじ ように正当である。なぜならキャストのついていない0がポインタ を必要とする場所にあらわれた場合に、コンパイラは、そのマシンに 適切なヌルポインタを作り出すことができなければならないからで ある。質問5,2,5.10,5.17を参照のこと。 References: ANSI Sec. 4.1.5; ISO Sec. 7.1.6; Rationale Sec. 4.1.5. 5.6: もしNULLが以下のように定義されているとすると、 #define NULL ((char *)0) キャストされていないNULLを引数として渡す関数呼び出しが動かなく なるのでは? A: 動かなくなる場合もある。ここで問題はデータの型が異なるとポイン タの内部表現が異なるマシンがあることである。上の定義は、キャ ストなしのNULLをcharへのポインタを引数として取る関数に渡すと きはうまくいくが、その他の型のポインタを関数引数として取る場 合は問題がある。この場合は文法的に正しい、 FILE *fp = NULL; のような例でさえうまくいかない場合がある。 にもかかわらずANSI CはNULLの定義方法としてその他に #define NULL ((void *)0) を許している。上の定義は、ポインタの型の扱いに関して間違いのあ るプログラムを動くようにするし(ポインタの内部表現がどんなデー タ型でも同じマシンに限られる。そういう意味で役に立つとはちょっ といいにくいが)、この定義により、NULLをポインタの意味以外で使 う間違いを見つけることができるかもしれない(たとえばASCIIのヌル 文字(NUL)が本当は必要な場合など。質問5.9参照)。 References: Rationale Sec. 4.1.5. 5.9: もしヌルポインタを表わす数としてNULLと0が同じものを表すなら、 どちらを使えばよいのか。 A: ポインタを表すすべての場面で、その値をポインタとして使っている ことの注意書きとしてNULLを使うべきだと信じているプログラマがた くさんいる。一方、NULLと0を取り巻く混乱は、0を#defineの後ろに 隠してしまうことで輪をかけていると信じ、キャストのない0を代わり に使っている人もいる。この問題には唯一の正解というものは存在し ない。(質問9.2と17.10を参照。) Cプログラマは、ポインタが必要な状 況ではNULLと0は交換可能で、キャストされていない0を使うことはぜ んぜん問題ないことを理解しなければならない。NULLを使うことは(0 を使うのと違って)、ポインタが関係していることの親切な注意書き でしかない。プログラマはポインタの0と整数の0を区別する必要があ るときにはNULLに(自分で理解する代わり、あるいはコンパイラが解 釈する代わりに)頼ってはいけない。 NULLを、ポインタ以外の0が必要な場面に使ってはならない。プロ グラムは動くかもしれないが、コンパイラに間違ったメッセージを送っ ていることに違いはない(ANSIはNULLの定義に(void *)0を使うことを 許している。この場合ポインタ以外が必要な場合は全然うまくいか ない)。特にASCIIのヌル文字(NUL)が必要な場合は、絶対にNULLを使っ てはならない。必要なら自分で #define NUL '\0' を用意すること。 References: K&R1 Sec. 5.4 pp. 97-8; K&R2 Sec. 5.4 p. 102. 5.10: でも0よりはNULLを使うほうが、NULLの値が将来変わることを考える と、特にヌルポインタの内部表現が0でないマシンについては優れ ているのでは。 A: いや。(マクロNULLを使うことは好ましいかもしれないが、上の理由 からではない。) 数を直接に書く代わりに、シンボル定数を 使うことは、値が将来変わるかもしれないところでは、よくや るけど、これはNULLを0の代わりに使う理由じゃない。もう一度説明し よう。C言語は、ソースコード上の0が(ポインタを使う場面では)ヌル ポインタを作り出すことを保証している。NULLを使うのは、プログラ ミングの書き方の決まりでしかない。質問5.5と9.2を参照のこと。 5.12: 私は、データ型に応じたヌルポインタを作り出すのに以下のマクロ を使っている。 #define Nullptr(type) (type *)0 A: この技は、人気があるし見た目は魅力的だけれど、たいして役には立 たない。代入や比較の際には必要ない。質問5.2を参照のこと。ソー スの打ち込みの節約にも役立たない。こんなマクロを使っていると、 プログラムを書いた人のヌルポインタの知識が怪しげであることをプ ログラムを読む人に暗示し、このマクロの定義されているところ、使 われているところ、どんな形でもポインタの使われているところをす べて注意深くチェックしなければいけなくなる。質問9.1と10.2も参 照のこと。 5.13: 変だな。NULLは0となることが保証されている。けれどヌルポインタ は0となることは保証されていないね? A: "ヌル"とか"NULL"という単語が無造作に使われるときは以 下のどれかを意味する。 1. 概念としてのヌルポインタ。C言語内の抽象的な概念であっ て質問5.1で定義した。これは次のように実装されている。 2. ヌルポインタの内部(あるいは実行時の)表現、0ではない かもしれないし、ポインタの型によって表現方法が異なる かもしれない。実際の値はコンパイラの作成者しか関心を持 たないはずである。Cプログラムを書く人はそんなものを見 ない。なぜなら彼らが使うのは次のものである。 3. ヌルポインタ定数。これは整数の定数0である(質問5.2を 参照)。そしてこれはしばしば、以下のマクロの後ろに隠れて しまう。 4. NULLマクロ、これは「0」や「(void *)0」として#defineさ れている。最後に、次のものは混同しやすいが、別のもので ある。 5. ASCIIのヌル文字(NUL)、これはすべてのビットが0である。 ただし名前が似ていることを除いてヌルポインタと共通点 はない。 6. 「ヌルストリング」。これは空の文字列("")の別名である。 この呼びかたを使うと混乱を招きそうである。なぜなら空の 文字列はヌル('\0')文字と関係があるが、ヌルポインタに は関係ない。ヌルポインタの話しをするとと1.に戻る。 この資料ではヌルポインタという言葉を1の意味で、「0」という文 字を3の意味で、「NULL」という言葉を4の意味で使っている。 5.14: なぜヌルポインタに関する混乱が存在するのか。なぜこれらの問題 がこんなに何度も出て来るのか。 A: Cプログラマは、昔から自分の書いたプログラムが走るマシンの実装 に関して必要以上に知りたがる。ヌルポインタがたいていのマシンで、 ソースコード上も多くの内部表現上も0であることが、保証されない 仮定を招いている。マクロ「NULL」を使うことで、その値が将来変化 するかもしれないことや、妙なマシン上では0でないことを指してい るように思えるのかもしれない。「if(p==0)」と書くと、比較する前 に0をポインタ型に変換するというよりは、pを整数型に変換すること を必要とすると解釈されがちである。最後に(上の質問5.13で記述し た)「ヌル」という言葉は、文脈によって異なる意味を持つのに、違 いを大目に見がちである。 混乱を避けるよい方法は、C言語にはキーワードがあって(Pascalにな らえばnilになるだろう)、それを使ってヌルポインタ定数を要求する と考えることである。コンパイラはソースコードから型が決定できる ときは、"nil"を正しい型のヌルポインタに変換することができるし、 できないときは苦情を出す。実際にはヌルポインタのC言語のキーワー ドは"nil"ではなく「0」である。0は"nil"とほとんど同じ働きをする。 違いは、ポインタ以外が必要な状況ではキャストされていない0には エラーメッセージを出す代わりに整数の0を作り出すことである。キャ ストされていない0をヌルポインタのつもりで使うと、そのコードは うまく動かないかもしれない。 5.15: 混乱している。このヌルポインタに関するごたごたが理解できない。 A: 以下の二つの簡単な規則に従え。 1. ソースコード内でヌルポインタ定数が必要なら、「0」か 「NULL」を使う。 2. 「0」や「NULL」を関数の引数に使うときは、呼ばれる 関数が想定しているポインタ型にキャストする。 以下の議論は、ある種の誤解に答えるものか、ヌルポインタの内部 表現に関するものか(これは知らなくてよいものだ)、ANSI Cでの改良 点に関することである。質問5.1, 5.2, 5.4を理解して質問5.3, 5.9, 5.13, 5.14について考えれば、うまくやっていける。 5.16: ヌルポインタを取り巻くこれらの混乱を考えれば、単純にヌルポイ ンタは内部では0で表現されると決めてしまったほうが簡単なので は。 A: 他に取り立てて理由がなければ、そうすることは望ましくない。なぜ なら、そうすることはヌルポインタを特殊な0でない値で表現するほ うが自然なマシンで、ヌルポインタの実装に必要以上の拘束を与える ことになる。例えば、ヌルポインタを不当アクセスだとしてハードウェ アで捕まえる仕組みになっていると、ヌルポインタを0以外の特別な ビットパターンで表現したほうが、むしろ自然である、 そのうえ、0に決め打ちにして何をもたらすのか。ヌルポインタを 理解することは、その内部表現が0であるかどうかの理解を必要とし ない。ヌルポインタの内部表現が0であると仮定しても、コードは 少しも書きやすくならない(質問7.31 で述べる、あまりお薦めしたく ないcallocの使い方を除く)。ポインタの内部表現が0であることを 保証したとしても、関数の引数内でのキャストが不要になるわけでは ない。なぜならポインタの大きさは、intの大きさと違うかもしれ ない。(もしヌルポインタを使うときに0の代わりに質問5.14で述べた "nil"を代わりに使っていれば、内部表現について仮定しようという気 さえ起きなかっただろう。) 5.17: ヌルポインタに0以外の値を使用するマシンや、異なる型のポイン タに異なる内部形式を持つマシンは本当に存在するのか。 A: Prime50シリーズは少なくともPL/Iでは、セグメント07777・オフセッ ト0をヌルポインタの内部表現として使っていた。後のモデルはセグ メント0・オフセット0をCのヌルポインタに使った。このために TCNP(Test C NULL Pointer)のような新しい命令が、それまでに誤っ た思い込みをして書かれた、ヘマなCプログラムを救済するために必 要となった。もっと古い(バイトアドレスではなく)ワードアドレス方 式のPrimeのマシンもまた、ワードポインタ(int *)よりもバイトポイ ンタ(char *)のほうが大きいことでも悪名高かった。 Data GeneralのEclipse MVシリーズには、アーキテクチャが用意する 3つのポインタの形式(wordとbyteとbit)が存在した。このうち2つ はCコンパイラによって使われる。byteポインタはchar *とvoid * に、wordポインタは残りのすべてのポインタ型に使われている。 Honeywell-Bullのメインフレームの中には、ビットパターン06000を (内部の)ヌルポインタとして使っているものもある。 CDCのCyber 180シリーズはリング・セグメント・オフセットからなる 48ビットのポインタを持っていた。多くのユーザー(リング11で走る) はヌルポインタとして0xB00000000000を使う。古いCDCの1の補数表現 のマシンでは全ビット1のワードは、ありとあらゆる種類のデータの 特別なフラグとして使われた。その中には違法アクセスも含む。 古いHP 3000シリーズはバイトアドレスとワードアドレスで異なるア ドレス指定の方法を使っていた。だから同じアドレスを指していても、 voidとcharのポインタは、intの(構造体なども)ポインタと違っ た内部表現を持っていた。 Symbolics社のLISPマシンは、タグ付きアーキテクチャなので、そも そもポインタを数値で表すという通常の概念さえ持たない。 (基本的には、存在しないのハンドル)をCの ポインタとして使っている。 80*86プロセッサは(PC互換機では)メモリモデルによっては、デー タに16ビットポインタを使い、関数には32ビットポインタを使う。 また逆のこともある。 64-bitのCrayマシンの中にはint *の表現にワードの下48ビットを使 い、char *はワード内のバイトアドレスであることを示すために上16 ビットを使っているものもある。 References: K&R1 Sec. A14.4 p. 211. 5.20: 実行時に出る「null pointer assignment(ヌルポインタによる代入)」 というエラーメッセージは何を意味するのか。どうやって問題をたぐっ ていけばよいか。 A: このメッセージは普通はMS-DOSのコンパイラでしか発生しない(よっ て19章参照)。ヌルポインタを使って(おそらく初期化しなかったか ら) 0番地に書き込んだことを意味している(質問16.8も参考)。 デバッガは、0番地にブレークポイントを張ることや見張りを立て ることなどを許しているかもしれない。もしくは0番地を先頭に20 バイトかそこら別のバッファにコピーして、値が変化していないこ とを、定期的に確かめるコードをチョイと書けばよい。質問16.8も 参照のこと。  0, unseen,, *** EOOH *** Newsgroups: fj.archives.answers,fj.lang.c Path: newsmaster.tuis.ac.jp!news.chiba-u.ac.jp!chiba-ns!hagi!ume!igakukei!news.cs.ritsumei.ac.jp!odins-suita!aist-nara!wnoc-tyo-news!newsgate1.web.ad.jp!newsbase.nisiq.net!news1.nisiq.net!newsfeed.btnis.ad.jp!nspixp!spinnews!spin-hsd0-tky!yhqfm!xeroc!leia!133.140.40.2!kitano From: kitano@crd.yokogawa.co.jp (Kinichi - Kinchan - Kitano) Subject: comp.lang.c Answers to Frequently Asked Questions (FAQ List) in Japanese[2/4] Sender: news@leia.pa.yokogawa.co.jp (Leia news server) Message-ID: Supersedes: Date: Mon, 14 Jul 1997 17:29:59 GMT Reply-To: kitano@crd.yokogawa.co.jp Organization: Yokogawa Electric Corporation, Tokyo, Japan. Followup-To: fj.lang.c Lines: 1283 Xref: newsmaster.tuis.ac.jp fj.archives.answers:1293 fj.lang.c:5589 Archive-name: c-faq-j/part2 Last-modified: 14 July. 1997 ========================= C FAQ 日本語訳[2/4] ========================= 6章 配列とポインタ 6.1: あるソースファイルでchar a[6]と定義して、別のファイルでextern char *aと宣言した、なぜこれはうまくいかないのか。 A: extern char *aという宣言が、実際の定義と食い違うからである。 「タイプTへのポインタ」は「タイプTの配列」とは異なる。extern char a[]を使え。 References: ANSI Sec. 3.5.4.2; ISO Sec. 6.5.4.2; CT&P Sec. 3.3 pp. 33-4, Sec. 4.5 pp. 64-5. 6.2: でもchar a[]はchar *aと同じと聞いたことがあるが。 A: 全然別のものである。(君が聞いたのは、関数の仮引数の話だ。質問 2.4参照)。配列はポインタと違う。配列の宣言「char a[6]」は6文字 分の領域を確保して、それを「a」という名前で識別することを要求 する。すなわち「a」という名前の場所があって、そこには6文字を収 めることができる。一方、ポインタの宣言「char *p」はポインタを 収める場所を要求する。ポインタはpという名前で識別され、ほとん どどんなものでも指すことができる。どんなchar、あるいは連続した charの列を指すこともできるし、どこも指さなくても構わない(質問 5.1と1.30参照)。 例によって、百聞は一見に如かずである。文 char a[] = "hello"; char *p = "world"; は以下のように表現できるデータ構造を初期化する。 +---+---+---+---+---+---+ a: | h | e | l | l | o |\0 | +---+---+---+---+---+---+ +-----+ +---+---+---+---+---+---+ p: | *======> | w | o | r | l | d |\0 | +-----+ +---+---+---+---+---+---+ x[3]を参照したときに産み出されるコードが、xがポインタか配列か で違うのだと理解することは大事なことである。上記の宣言を与えら れたとして、コンパイラはa[3]という式を見たところで、「a」のと ころから始めて、そこから3つ進んで、そこにある文字を取り出す、 というコードをはきだす。p[3]という式を見ると、「p」に進み、そ こに存在するポインタの値を取り出し、ポインタの値に3を加え、最 後にポインタが指す場所から文字を取り出す、というコードをはきだ す。言い換えればa[j]はaという名前のオブジェクト(の先頭)から3つ 目の場所で、p[3]はpが指すオブジェクトから3つ目の場所である。上 の例ではa[3]、p[3]はたまたま同じ文字'l'を指すがコンパイラはそ こにたどり着くのに別の道をたどるのである。(本質的な違いは、aの ような配列の値とpのようなポインタの値は式のどこであらわれよう と別の方法で計算されるということである。次の質問でさらに説明す るように、添字付けされてようとなくても。) References: K&R2 Sec. 5.5 p. 104; CT&P Sec. 4.5 pp. 64-5. 6.3: Cで"ポインタと配列は同等"というのは何を意味しているのか。 A: Cにおけるポインタに関する混乱の多くは、上の文の誤解から来て る可能性がある。配列とポインタが"同等"といっても、この2つが まったく同じとか交換可能であるということは意味していない。 "同等"というのは、この問題を解く手掛かりになる以下の定義を指し ている。 式中に現われる型「Tの配列」という左辺値(3つの 例外を除いて)、配列の最初の要素を指すポインタ に意味が格下げになる。結果としてできるポインタ の型は「Tへのポインタ」となる。 (3つの例外とは、配列がsizeofの引数となるとき、アドレス演算 子&の引数となるとき、char型の配列を文字列リテラルで初期値す るとき、である)。 この定義により、演算子[]を配列に使っても、ポインタに使っても たいして違いはない。 a[i]と書いたとき、上の規則により配列の参 照「a」はポインタへと成り下がる。これでポインタ変数に対し てp[i]と書くのと同じことになってしまった(ただし質問6.2で説明し たように、メモリのアクセスのしかたは異なるかもしれない)。配列 のアドレスをポインタに代入することになったら、 p = a; p[3]とa[3]は同じ要素を指す。 質問6.8も参照のこと。 References: K&R1 Sec. 5.3 pp. 93-6; K&R2 Sec. 5.3 p. 99; ANSI Sec. 3.2.2.1, Sec. 3.3.2.1, Sec. 3.3.6; ISO Sec. 6.2.2.1, Sec. 6.3.2.1, Sec. 6.3.6; H&S Sec. 5.4.1 p. 124. 6.4: それではなぜ関数の仮引数では配列とポインタの宣言が交換できる のか。 A: そのほうが便利だからだと考えられているからである。 配列はすぐにポインタに成り下がるので、配列が実際に関数に渡る ことはない。ポインタ引数を配列のように見せて宣言することを許 すのは、配列が渡されたように見せるためである。プログラマは仮 引数が昔から配列のように扱われるとか、配列が(厳密に言えば配列 のアドレスだが)昔から渡されるということを強調したいのかもしれ ない。コンパイラに都合がいいように、配列のように"見える"仮引数 宣言は、以下に例を挙げるように f(a) char a[]; { ... } すべてコンパイラによってポインタが渡されたかのように扱われる。 なぜなら配列が渡されたときに、関数が実際に受け取るのはポインタ であるからである。 f(a) char *a; { ... } この変換は、関数の仮引数の仮引数宣言でのみ有効である。その他の 場所では起こらない。この変換が気に食わないなら使わなければよい。 関数の呼び出しや関数内での使い方に宣言を似せることによる小さ な利点よりも、それが引き起こす混乱のほうが大きいと考える人は多 い。 質問6.21も参照のこと。 References: K&R1 Sec. 5.3 p. 95, Sec. A10.1 p. 205; K&R2 Sec. 5.3 p. 100, Sec. A8.6.3 p. 218, Sec. A10.1 p. 226; ANSI Sec. 3.5.4.3, Sec. 3.7.1, Sec. 3.9.6; ISO Sec. 6.5.4.3, Sec. 6.7.1, Sec. 6.9.6; H&S Sec. 9.3 p. 271; CT&P Sec. 3.3 pp. 33-4. 6.7: どうして配列は代入できないのに左辺値(lvalue)なのか。 A: ANSI C規格は「変更可能な左辺値(modifiable lvalue)」を定義して いる。その中に配列は入っていない。 References: ANSI Sec. 3.2.2.1 p. 37. 6.8: 実際のところ、配列とポインタの違いは。 A: 配列は自動的に領域を割り付ける。ただし別の場所に移したり大きさ を変えることはできない。ポインタは、どこかに割り付けられた 領域(たぶんmalloc()を使って割り付けた領域)を指すアドレスを明示 的に代入しなければならない。そのかわり好きなように値を変えるこ とができる(すなわち別のものを指すことができる)。またメモリブロッ クを指す以外にも色々と使い道がある。 俗にいう配列とポインタが同等(質問6.3参照)により、配列とポイン タが同じに見える場合が多い。特にmalloc()により割り付けた領域を 指すポインタが、しばしば本物の配列のように(実際[]を使って参照 することができる)扱われる。質問6.14と6.16を参照のこと(ただし sizeofには要注意)。 質問1.32と20.14を参照。 6.9: 配列とは定数ポインタにすぎないと説明してくれた人がいた。 A: それはちょっとものごとを単純に考えすぎである。配列の名前は、名 前には代入できないという点で"定数"であるが、質問6.2の議論と図 を見れば配列がポインタでないことははっきりするだろう。質問6.3 と6.8を参照。 6.11: 5["abcdef"]という式を含んだジョークのコードを見たことがある。 どうしてこの式がC言語で文法上正しいことになるのか。 A: 配列の添字演算子[]の二つのオペランドは交換可能で、Y[X]と書いて もX[Y]と書いても同じ意味になるというのをご存知ない? この奇妙 な事実は、配列の添字付けのポインタの定義からきている。すなわち、 一方がポインタを表す式で、残りが整数である限り、どんなaとeをもっ てきても、a[e]は*((a)+(e))と同じものであるという定義である。こ のとんでもない交換可能性は、よくC言語について扱う文章の中で、 誇らしく思うかのように記述されているが国際難解Cプログラムコン テスト以外では役に立たない(質問20.36参照)。 References: Rationale Sec. 3.3.2.1; H&S Sec. 5.4.1 p. 124, Sec. 7.4.1 pp. 186-7. 6.12: 配列を参照することはポインタに成り下がることを考えれば、arr という配列があったとしてarrと&arrの違いは。 A: データ型。 ANSI/ISO Cの元では&は「Tの配列へのポインタ」を産み出す。これ は配列全体を指す。(ANCI Cが誕生する前は、arrの前に&を付けるこ とは、たいてい警告を招き、たいてい無視された。) すべてのCコン パイラで配列への(キャストのない)参照はポインタを産み出す。こ のポインタはTへのポインタで配列の最初の要素を指す((質問 6.3, 6.13, 6.18も参照のこと) References: ANSI Sec. 3.2.2.1, Sec. 3.3.3.2; ISO Sec. 6.2.2.1, Sec. 6.3.3.2; Rationale Sec. 3.3.3.2; H&S Sec. 7.5.6 p. 198. 6.13: 配列へのポインタをどうやって宣言するのか。 A: たいていは、そんなポインタを宣言したいのではない。なにげなく 配列へのポインタというときは、たいてい配列の最初の要素へのポ インタのことをいっているのである。 配列へのポインタではなく、配列の要素へのポインタを使うことを考 えること。型Tの配列は型Tへのポインタに成り下がる(質問6.3を参照)。 これは都合がよい。なぜなら結果としてできるポインタを使って添字 つきで参照したり、整数を加えることで配列の各要素にアクセスした りできる。これに対して、本当の配列へのポインタは、添字つきで参 照したり整数を加えると、配列全体を飛び越してしまう。これではせ いぜい配列の配列を扱うときにしか役立たない(上の質問6.18を参照)。 本当に配列そのものへのポインタが必要な場合は「int (*ap)[N];」 のような表現を使う。ここでNは配列のサイズを表す(質問1.21も参照)。 配列の大きさがわからない場合、Nを省略することができる。しかし 結果として得られる「大きさが未知の配列へのポインタ」は役に立た ない。 上の質問6.12も参照のこと。 References: ANSI Sec. 3.2.2.1; ISO Sec. 6.2.2.1. 6.14: どうすれば配列の大きさをコンパイル時に設定することができるか。 固定の大きさの配列を使わなくて済ますにはどうすればいいか。 A: 配列とポインタが同等(質問6.3を参照のこと)であることによって mallocしたメモリを指すポインタを使って配列をかなり効果的に 真似ることができる。 #include int *dynarray: dynarray = malloc(10 * sizeof(int)); を実行した後では(かつmalloc()の呼び出しが成功したら)、dynarray で、普通の静的に割り付けた配列(int a[10])のように扱って dynarray[i] (0から9までのiに対して)を参照することができる。質 問1.31a, 6.16, 7.7も参照のこと。 6.15: 引数として渡された配列の大きさに合ったローカルな配列をどうやっ て宣言することができるか。 A: Cでは不可能だ。配列の大きさはコンパイル時に定数でなければなら ない。(gccはパラメータ付き配列を拡張機能として提供している。) malloc()を使って、関数から返る前にfree()を必ず呼ばなければなら ない。質問6.14, 6.16, 6.19, 7.22を参照のこと。 質問7.32を参照 する必要があるかもしれない。 References: ANSI Sec. 3.4, Sec. 3.5.4.2; ISO Sec. 6.4, Sec. 6.5.4.2. 6.16: 多次元の配列を動的に割り付けるのはどうしたらよいか A: たいていポインタの配列を割り付けて、それぞれのポインタを動 的に割り付けた"列"に初期化するのが一番の解決策である。以下に2 次元配列の例を挙げる。 #include int **array1 = (int **)malloc(nrows * sizeof(int *)); for(i = 0; i < nrows; i++) array1[i] = (int *)malloc(ncolumns * sizeof(int)); (もちろん"本物の"コードではmallocの戻り値のチェックが必要であ る。) 配列の中身をメモリ上連続にすることもできる。ただしこうすると、 後で一つ一つの列を再割り付けするのが面倒になる。以下のような 明示的な、ちょっとしたポインタ計算が必要となる。 int **array2 = (int **)malloc(nrows * sizeof(int *)); array2[0] = (int *)malloc(nrows * ncolumns * sizeof(int)); for(i = 1; i < nrows; i++) array2[i] = array2[0] + i * ncolumns; どちらを選択しても、動的に割り付けた配列の各要素は普通の配列の ように添え字でアクセスできる。 (0 <= i <= NROWS かつ 0 <= j <= NCOLUMNSで) arrayx[i][j] というふうに。 もし上のような二重間接アクセス方法がなんらかの理由で受け入れら れないなら、一つの動的配置の1次元配列で2次元配列を真似ることが できる。 int *array3 = (int *)malloc(nrows * ncolumns * sizeof(int)); しかしこうすると添字の計算を自分で行わなければならない、すなわ ちi,j番目の要素へのアクセスは array3[i * ncolumns + j]としなけ ればならない(マクロを使えば明示的な計算を隠すことができる。し かしマクロを使えば括弧やコンマを使う必要があるので多次元配列 へのアクセスのようには見えなくなる。同じくマクロは少なくとも一 つの次元の大きさを知る必要がある。質問6.19も参照のこと)。 最後に配列へのポインタを使う方法を紹介する。 int (*array4)[NCOLUMNS] = (int (*)[NCOLUMNS])malloc(nrows * sizeof(*array4)); けれど、この構文は読む人に恐怖感を与えるし、コンパイル時に一つ の次元を除くすべての次元が確定していなければならない。 どの方法をとるにしても、必要がなくなったら配列を解放することを 憶えておかなければならない(それは、何段かの手続きを踏まなけれ ばならないかもしれない)。また配列を動的に割り付けて他の関数に 渡す際に、渡す先の関数が、静的に割り付けた普通の配列を引数とし て受けとる場合は特に注意が必要である(質問6.20参照のこと。また 質問6.18も参照のこと)。 上のどの技法も3次元以上の配列に拡張することが可能である。 6.17: ほらこのトリック。下のように書けば int realarray[10]; int *array = &realarray[-1]; arrayは1始まりの配列のように使える。 A: このテクニックは魅力的だが(NUMERICAL RECIPES IN Cの古い版でも 使われている)、厳密にいえばC言語の規格に従っていない。ポインタ 演算は、一度に割り振られた領域と、仮想的な"終端"を越えた1つめ の要素にだけ定義されていて、それ以外では未定義である。このこと は、たとえポインタを参照に使っていないとしてもあてはまる。上の コードはオフセットを引いた時に、とんでもないアドレスを作り出し て(たぶんアドレスが"ぐるっと回って"メモリセグメントの先頭を越 えて他のセグメントと重なるからだろう)うまくいかなくなる可能性 がある。 References: K&R2 Sec. 5.3 p. 100, Sec. 5.4 pp. 102-3, Sec. A7.7 pp. 205-6; ANSI Sec. 3.3.6; ISO Sec. 6.3.6; Rationale Sec. 3.2.2.3. 6.18: 私が使っているコンパイラは、ポインタへのポインタを使うべき ところで2次元配列を使うと不満をいう。 A: 配列がポインタに成り下がるという規則は(質問6.3参照)、再帰的に は成り立たない。配列の配列(例えばC言語における2次元配列)は、配 列へのポインタに成り下がるのであって、ポインタへのポインタに成 り下がるわけではない。配列へのポインタは、混乱を招くから、注意 して扱わなければならない。質問6.13も参照のこと。(混乱は行儀の 悪いコンパイラ、が多次元配列を多段のポインタとして受け付けるこ とで増長されている。そういうコンパイラとしてはpccの古い幾つか のバージョンと、そういうpccから派生したlintが含まれる)。 もし2次元配列を以下の関数に渡すと int array[NROWS][NCOLUMNS]; f(array); 関数の宣言は以下のどちらかでないといけない。 f(int a[][NCOLUMNS]) { ... } あるいは f(int (*ap)[NCOLUMNS]) /* apは配列へのポインタ */ { ... } 最初の宣言では、コンパイラが仮引数を「配列の配列」から「配列へ のポインタ」へ通常通り暗黙の書き換えを行う(質問6.3と6.4を参照)。 2つ目の定義では、ポインタの定義であるとはっきり書いてある。呼 ばれる側の関数は配列分の領域を取るわけではないので、配列全体の 大きさを知る必要はない。列の数「NROWS」は省略することができる。 配列の"形"は大事であるから行の数「NCOLUMS」を(3次元以上の配列 の場合は最初の次元をのぞくすべての次元の大きさを含めて)指定し なければならない。 もしポインタへのポインタを受け付けると関数が宣言していると きには、直接2次元配列を渡すことはやっても意味がないだろう。 質問6.12と6.15も参照のこと。 References: K&R1 Sec. 5.10 p. 110; K&R2 Sec. 5.9 p. 113; H&S Sec. 5.4.3 p. 126. 6.19: コンパイル時に"幅"が未定の2次元配列を引数とする関数はどうやっ て書けばよいか。 A: これは結構難しい。一つは[0][0]要素へのポインタを、2つの次元の それぞれの大きさと一緒に渡して配列の添字付けを"手で"真似る方法 がある。 f2(aryp, nrows, ncolumns) int *aryp; int nrows, ncolumns; { ... ary[i][j] は実際にはaryp[i * ncolumns + j] ... } この関数は質問6.18の配列を使って以下のように呼ぶこともできる。 f2(&array[0][0], NROWS, NCOLUMNS); しかしながら このような方法によって"手で"多次元の配列の添字付 けをするプログラムはANSI C規格に厳密には従っていないことに注意 すること。x >= NCOLUMNSの場合に(&array[0][0])[x]にアクセスした ときの動作は定義されていない。 gccを使えば、関数の引数を使ってローカルの配列がある大きさを持 つと宣言することができる。しかしこれは標準ではない拡張機能であ る。 様々な大きさの多次元配列を引数に取る関数を使いたいなら、質問 6.16のようにすべての配列を動的に真似るやりかたがある。 質問6.18, 6.20, 6.15も参照のこと。 6.20 関数の引数として配列を渡すときに、静的に割り付けた配列も動的に 割り付けた配列も受け付けるようにするにはどうしたらよいか。 A: 唯一の完全な解というのは存在しない。以下の宣言があって、 int array[NROWS][NCOLUMNS]; int **array1; /* ragged */ int **array2; /* contiguous */ int *array3; /* "flattened" */ int (*array4)[NCOLUMNS]; 質問6.16と同じようにポインタを初期化するとする。また以下のよう に宣言した関数が存在するとする。 f1(int a[][NCOLUMNS], int nrows, int ncolumns); f2(int *aryp, int nrows, int ncolumns); f3(int **pp, int nrows, int ncolumns); 関数f1()は通常の2次元の配列を引数とし、f2()は"平らにした"2次元 の配列を引数とし、f3()はポインタを指すポインタを引数として配列 を真似るとする(質問6.18と6.19も参照のこと)。以下の呼び出しはこ ちらの期待通りの動きをする。 f1(array, NROWS, NCOLUMNS); f1(array4, nrows, NCOLUMNS); f2(&array[0][0], NROWS, NCOLUMNS); f2(*array, NROWS, NCOLUMNS); f2(*array2, nrows, ncolumns); f2(array3, nrows, ncolumns); f2(*array4, nrows, NCOLUMNS); f3(array1, nrows, ncolumns); f3(array2, nrows, ncolumns); 以下の2つの例もたいていのマシンで多分うまくいく。ただし怪しい キャストが含まれているし、動的に割り付けたncolumnsが、静的に割 り当てたNCOLUMNSと一致するときしかうまく動かない。 f1((int (*)[NCOLUMNS])(*array2), nrows, ncolumns); f1((int (*)[NCOLUMNS])array3, nrows, ncolumns); ここでも&array[0][0]をf2に渡すのは(*arrayを渡すのも)厳密には規 格に準拠していないことに注意すること。質問6.19を参照。 もしなぜ上記の関数呼び出しがすべてうまくいくかということと、な ぜ上記のように記述されたかがわかっていて、洩れている組み合わせ がなぜうまくいかないかもわかっているのなら、C言語の配列とポイ ンタについての知識は結構いい線いっていると思っていいだろう。 さまざまな大きさの多次元の配列の、上に書いたようなことを気にす る代わりの扱いかたとしては、すべての配列を質問6.16にあるように 動的にする方法がある。静的な多次元の配列がなければ、つまりすべ ての配列が質問6.16のarray1やarray2のように割り付けられているな ら、すべての関数はf3()のように書くことができる。 6.21: なぜサブルーチンの引数として渡された配列の大きさをsizeof()でき ちんと計算できないのか。 A: コンパイラは配列仮引数がポインタと宣言されたように振る舞い (質問6.4参照)、sizeof()はポインタの大きさを返す。 References: H&S Sec. 7.5.2 p. 195. 7章 メモリの割り付け 7.1: 以下のプログラムはなぜ動かないのか。 char *answer; printf("なにか入力してください:\n"); gets(answer); printf("あなたは \"%s\" と入力しました\n", answer); A: ポインタ変数answerは応答を貯える場所として関数gets()に渡される が、その時点ではanswerは有効な領域を指していない。すなわちポイ ンタanswerは、どこを指しているのかわからない(ローカルな変数 は初期化されることはないし、たいていゴミが入っている。「answer」 がヌルポインタとして始まることさえ保証されていない。質問1.30 と5.1を参照のこと)。 上の質問のプログラムを修正する一番やさしい方法は、ポインタの代 わりにローカルの配列を使って、コンパイラに領域の割当てをまかせ ることである。 #include #include char answer[100], *p; printf("なにか入力してください。:\n"); fgets(answer, sizeof answer, stdin); if((p = strchr(answer, '\n')) != NULL) *p = '\0'; printf("あなたは \"%s\"と入力しました\n", answer); この例はgets()の代わりにfgets()を使って、配列に続く部分が上書 きされないような工夫もしている。(質問12.23参照。残念ながらこの 例ではfgets()はgets()とは違って、後ろの改行を自動的には削除し ない)。malloc()を使って返答用のバッファを割り付けることもでき る。 7.2: strcat()がうまく動かない。以下のプログラムを走らせたら、 char *s1 = "Hello, "; char *s2 = "world!"; char *s3 = strcat(s1, s2); 変な答えが返ってきた。 A: 上の質問7.1と同じように、ここでも一番の問題は連結した結果を貯 える領域がうまく割り付けられていないことである。C言語には、自 動的に管理される文字列型はない。ソースコードで明示的に表された オブジェクトに領域を割り付けるだけである(ここでいう"文字列"に ついてはcharの配列と"でくくられた文字列を含む)。プログラマは、 文字列の連結のような実行時の操作の結果に対して十分な領域を、配 列を定義したり、malloc()を起動することで明示的に割り付けなけれ ばならない。 strcat()は、領域の割り付けを行わない。2番目の文字列は最初の文 字列に、その場で連結される。一つの解決方法は、最初の文字列を十 分な大きさを持つ配列として宣言することである。 char s1[20] = "Hello, "; strcatは第一引数を返すので(この場合はs1)、s3は余計である。 質問のコードの元のstrcat()の呼び出しは、実際には二つの難点があ る。s1によって指される文字列リテラルが、連結されてできるどんな テキストも保存できるほど大きくないかもしれないだけではなく、そ もそも書き込み不可かもしれない。質問1.32参照。 References: CT&P Sec. 3.2 p. 32. 7.3: けれどstrcat()のmanページによると、strcat()は引数として2つの charへのポインタを取ることなっている。領域の割り付けが必要で あるとどうやって知ることができるのか。 A: 一般にポインタを使うときは、いつも領域の割り付けのことを考えて おかなければならない。少なくともコンパイラが代わりにやってくれ ることを確認しておかなければならない。ライブラリのドキュメント に記憶領域の割り付けについてはっきり記述していなければ、割り当 ては普通は使う人の責任である。 Unixのmanの先頭やANSI C規格の要約の章は、誤解を招くかもしれな い。そこに載っているコードの一部は、使い方というよりは、関数の 実装で使われる関数定義に近い。とくに(構造体や文字列への)ポイン タを扱う関数の多くはなんかしらのオブジェクト(構造体か配列への … 質問6.3や6.4を参照)へのアドレスを引数に起動される。ほかによく ある例はtime() (質問13.12参照)やstat()である。 7.5: 文字列を返すはずの関数がある。けれど呼んだ側の関数に返ってくる と、返ってきた文字列にはゴミが入っている。 A: 関数が返す文字列を格納する領域が正しく割り付けられていることを 確かめること。返されたポインタは静的に割り付けられたバッファか、 呼んだ側の関数から渡されたバッファか、malloc()により得られたメ モリを指すべきで、呼ばれた関数のローカルな(自動変数の)配列を指 していてはいけない。つまり以下のようなことは絶対にやってはいけ ない。 char *itoa(int n) { char retbuf[20]; /* 間違い */ sprintf(retbuf, "%d", n); return retbuf; /* 間違い */ } 修正する方法の1つは、バッファを以下のように宣言することである (これも、関数が再帰的に起動される場合や、戻り値が同時に複数の 箇所で必要な場合にうまく行かないことを考えると不十分である)。 static char retbuf[20]; 質問12.21と20.1も参照のこと。 References: ANSI Sec. 3.1.2.4; ISO Sec. 6.1.2.4. 7.6: どうしてmalloc()を呼ぶと「警告: 整数をポインタに代入の際には キャストが必要」というのが出るのか。 A: を#includeしたか、してないとするとmalloc()が正しく宣 言されるようにしたか。質問1.25も参照のこと。 References: H&S Sec. 4.7 p. 101. 7.7: mallocが返した値を割り付けたデータ型のポインタに注意深くキャス トしているコードをたまに見るのはなぜか。 A: ANSI/ISO規格のC言語がvoid *という汎用のポインタ型を導入する までは、互換性のないポインタ型の間で代入をするときに警告を黙 らせるの(もしかしたら変換もさせるのに) にこうしたキャストが必要となることがよくあった。 ANSI/ISO規格のCでは、こうしたキャストはもう必要ない。それどこ ろか今では思いとどまった方がよいと主張することも可能である。こ うしたキャストはmalloc()がたまたま正しく宣言されたかったときに、 大事な警告を隠してしまう可能性があるからである。上の質問7.6も 参照のこと。 References: H&S Sec. 16.1 pp. 386-7. 7.8: 以下のようなコードを見た。 char *p = malloc(strlen(s) + 1); strcpy(p, s); malloc((strlen(s) + 1) * sizeof(char))では? A: sizeof(char)を掛ける必要がある場合は絶対にない。なぜなら sizeof(char)は定義によりぴったり1であるから。 (一方、 sizeof(char)をかけても何も害はない。こう書くことで式にsize_tが 現れて理解しやすくなるかもしれない。)質問8.9も参照のこと。 References: ANSI Sec. 3.3.3.4; ISO Sec. 6.3.3.4; H&S Sec. 7.5.2 p. 195. 7.14: オペレーティングシステムによってはmallocしたメモリを実際に確 保するのをプログラムがそのメモリを使おうとするまで先延ばしす ると聞いたことがある。これは文法上許されるのか。 A: これは難しい。規格は、システムはこういう風に振る舞ってもよいと 書いていないけれど、こういう風に振る舞ってはいけないとはっきり 書いてあるわけでもない。 References: ANSI Sec. 4.10.3; ISO Sec. 7.10.3. 7.16: 数値演算をするのに、大きな配列を割り付けようと考えている。下の コードを書いたところ、 double *array = malloc(300 * 300 * sizeof(double)); malloc()はヌルを返すわけではないけれど、プログラムの動きが変だ。 メモリを上書きしたり、こっちが望んだだけmalloc()が割り付けてな かったりとかするようだ。 A: 300 x 300は90,000で、これはsizeof(double)を掛ける前から、16ビッ トの整数には納まらないことに注意(質問1.1参照)。こんなに大きな メモリを確保する必要があるときは、注意する必要がある。使ってい るマシンのsize_t(malloc()が認めたデータ型)が32ビットであれば、 300 * (300 * sizeof(double))と書くことでやっていけるかもしれな い(質問3.14参照)。これで駄目ならデータの構造をもっと小さな単位 に分解するか、32ビットのマシンを使うか、標準ではないメモリ割り 付けルーチンを使うかしなければならない。質問19.23も参照のこと。 7.17: 私のPCには8MBもメモリが載っている。どうして640Kかそこらし かmalloc()できなさそうなのか。 A: PC互換機のセグメント付きのアーキテクチャーでは、640Kより多くの メモリを使うことは非常に難しい。質問19.23も参照のこと。 7.19: プログラムがコケる。malloc()の内部のどこかのようだ。でもどこが 悪いか分からない。 A: mallocした領域の内部のデータ構造は残念ながら非常に簡単に壊れて しまう。しかも引き起こされる障害は追跡しにくいものとなることが ある。いちばんよくある障害の元は、mallocした領域に割り付けた大 きさよりも多く書き込んでしまうことである。特によくある例は、大 きさstrlen(s)+1ではなく、大きさstrlen(s)だけmalloc()で割り付け ることである。他にはfreeした領域を指すポインタを使うことや、2 回freeしたり、malloc()で割り付けた先を指していないポインタを free()を使って開放しようとしたり、ヌルポインタを使って realloc()を呼び出すことも障害を引き起こす(質問7.30を参照)。 質問7.26, 16.8, 18.2も参照のこと。 7.20: 動的に割り付けた記憶領域は解放した後には使えないね。 A: 使えない。malloc()の昔の解説には、解放された領域は「壊されずに 残っている」と記述してあるものもあった。このうかつな保証は一般 的になることはなく、C規格では、このようなことを保証することは 要求されていない。 解放した領域の中身を意識して使うプログラマは少ない。けれど偶然 使ってしまうことはよくある。一方向リンク付きリストを解放する以 下の(正しい)コードを考えてみよう。 struct list *listp, *nextp; for(listp = base; listp != NULL; listp = nextp) { nextp = listp->next; free((void *)listp); } 一時変数のnextpを使うことなくlistp = listp->nextを使ったとすれ ばどうなったか考えてみること。 References: K&R2 Sec. 7.8.5 p. 167; ANSI Sec. 4.10.3; ISO Sec. 7.10.3; Rationale Sec. 4.10.3.2; H&S Sec. 16.2 p. 387; CT&P Sec. 7.10 p. 95. 7.21: どうしてポインタがfree()を呼んだ後でヌルポインタにならない のか。freeした後のポインタの値を使う(代入、比較する)ことは、 どれくらい危険なのか。 A: free()を呼ぶと、free()に渡したポインタの指す先のメモリは解 放されるが、呼び出した側のポインタの値は変わらない。それはC の値渡しとは、呼ばれた側の関数が自分の引数を決して変えることは ないということだからである。(質問4.8も参照) 解放されたポインタの値は厳密にいえば無効で、それをどう使って も、たとえ間接参照以外のことに使っても理屈の上ではトラブルの元 である。ただ、これは実装の質の話だけれど、無効なポインタの害 のない使い方にわざわざ例外を発生させる実装はたぶん存在しない。 References: ANSI Sec. 4.10.3; ISO Sec. 7.10.3; Rationale Sec. 3.2.2.3. 7.22: ローカルなポインタ用にメモリをmalloc()で割り付ける。わざわざ free()を呼ばなければならないか。 A: もちろん。ポインタとポインタが指す先は別物であることを忘れては いけない。ローカル変数は関数から戻るときに解放される。ただしポ インタ変数に関しては、ポインタが解放されるのであって、ポインタ が指す先が解放されるわけではない。malloc()によって割り付けられ たメモリは明示的に解放するまで必ず残る。一般に、すべての malloc()の呼び出しに、対応するfree()がなければならない。 7.23: 動的に割り付けたオブジェクトへのポインタを含む構造体を割り付け ている。構造体を解放する前に、構造体が含むポインタの先のオ ブジェクトを全部解放しなければならないのか。 A: そのとおり。一般にmalloc()が返してきたポインタを(解放するとし たら)それぞれ一度だけfree()に渡すしくみを用意しなければいけな い。おおざっぱな指針としてはプログラム内のmallocの各呼出しに対 してそのmallocの呼出しで割り付けたメモリを解放するfreeがどれか プログラマは答えられなければならない。 質問7.24も参照のこと。 7.24: プログラムが終了する前に、割り付けたメモリを解放しなければなら ないか。 A: その必要はない。まともなオペレーティングシステムならきっとプロ グラムが終了した時点ですべてのメモリを取り返すだろう。にもかか わらず、個人向けコンピュータ(PC)の中にはメモリを取り戻すことが 確実にはできないものもあるようである。ANSI/ISO C規格から結論付 けられることは、解放してくれるかどうかは「実装の品質がどれくら い高いかによる」ということだけである。 References: ANSI Sec. 4.10.3.2; ISO Sec. 7.10.3.2. 7.25: 大量の記憶領域をmallocしてfreeしてまわるプログラムがある。けれ ど、メモリの使用状況をみると(psで見える値では)領域が戻ってきた ようには見えない。 A: たいていのmalloc/freeの実装は、解放されたメモリをオペレーティ ングシステムに(オペレーティングシステムがあったとして)返さない。 後からmalloc()が呼び出されたときのために取っておく。 7.26: free()は、何バイト解放するかをどうやって知るのか。 A: malloc/freeの実装は、メモリのブロックを割り付けて、そのブロッ クの先頭アドレスを返す。その時に、ブロックのサイズを記憶する。 よって解放するときにfreeに思い出させる必要はない。 7.27: だったら割り付けた領域の大きさをmallocパッケージに聞くことがで きるのか。 A: 移植性の高い方法では不可能である。 7.30: realloc()の最初の引数にヌルポインタを使うことは許されている のか。どうしてそんなことをするのか。 A: ANSI Cはこの使用方法を許している(これに関係するrealloc(...,0) も許している。これが領域を解放する)。けれども昔のコンパイラに は対応していないものもあるので、この方法は移植性が高いとはいえ ない。第1引数をヌルポインタにすることで、割り付ける領域をだん だん増やしていくアルゴリズムを実現するときに、起動する部分を書 くことが容易になる。 References: ANSI Sec. 4.10.3.4; ISO Sec. 7.10.3.4; H&S Sec. 16.3 p. 388. 7.31: calloc()とmalloc()の違いは。calloc()の0を埋めるという機能を、 ポインタや浮動小数点数に使っても問題ないか。free()はcalloc() が割り付けた領域にも働くか、それともcfree()を使わなくてはなら ないか。 A: calloc(m, n)は実質的に以下のコードと同じ動きをする。 p = malloc(m * n); memset(p, 0, m * n); 0を埋めるというのは全ビット0にするということで、ヌルポインタや浮 動小数点の0で埋めることを保証していない(5章 を参照のこと)。calloc()が割り付けた領域の解放にfree()を使うこ とにも正しく使える。 References: ANSI Sec. 4.10.3 to 4.10.3.2; ISO Sec. 7.10.3 to 7.10.3.2; H&S Sec. 16.1 p. 386, Sec. 16.2 p. 386; PCS Sec. 11 pp. 141,142. 7.32: alloca()とは何者で、なぜ使わないほうがよいと人は言うのか。 A: alloca()は領域を割り付け、alloca()を起動した関数を抜けた時点で その領域は自動的に解放される。すなわちalloca()によって割り付け られた領域は、特定の関数の「スタックフレーム」やその関数の前後 の状況に局所的となる。 alloca()を移植性が高いように書くことはできないし、スタックのな いマシン上に実装することは難しい。これを使うことは、戻り値を直 接別の関数に渡す場合に(すぐに思いつくような実装では、スタック に基づくマシンでは必ず失敗する)、たとえば fgets(alloca(100),100, stdin)のような式で問題を招く。 これらの理由により、alloca()は便利にみえるが、移植性が高く なければならないプログラムでは使うことはできない。 質問7.22も参照のこと。 References: Rationale Sec. 4.10.3. 8章 文字と文字列 8.1: どうして、 strcat(string, '!'); はうまく動かないのか。 A: 文字(character)と文字列(string)には大きな違いがある。strcat() は文字列を連結する。 Cの文字は文字集合での値に対応する小さな整数で表現される(以下の 質問8.6を参照)。文字列は文字の配列で表現される。そして普通は配 列の最初の文字を指すポインタを操作する。文字が来ること期待され ているところに文字列を使うことも、文字列が来ることを期待されて いるところに文字を使うことも間違いである。文字列に!を連結する には、 strcat(string, "!"); を使う。 質問1.32,7.2,16.6を参照のこと。 References: CT&P Sec. 1.5 pp. 9-10. 8.2: 文字列が、ある値と一致するかどうか調べるプログラムを書いている。 なぜ以下のコードではうまくいかないのか。 char *string; ... if(string == "value") { /* 文字列が"value"と一致した */ ... } A: Cの文字列は文字の配列で表現される。またCは行列を全体として操作 (代入、比較など)することはない。上のコードの==演算子は2つのポ インタを比較している。つまりポインタ変数stringと文字列 "value"へのポインタが等しいかどうか調べる。ということは両者 が同じ場所を指しているかどうか調べている。たぶん違うところを指 しているので比較が成功することはない。 2つの文字列を比較するには、ライブラリ関数strcmp()を使うのが普 通である。 if(strcmp(string, "value") == 0) { /* 文字列が"value"と一致した */ ... } 8.3: どうして、 char a[] = "Hello, world!"; と書けるのに、 char a[14]; a = "Hello, world!"; と書けないのか。 A: 文字列は配列で、配列に直接は代入できない。代わりにstrcpy()を使 う。 strcpy(a, "Hello, world!"); 質問1.32, 4.2, 7.2も参照のこと。 8.6: 文字に対応する数値(文字集合)の値や、その逆はどうやって求めれば よいか。 A: Cで文字は、その値に対応する(そのマシンが使っている文字集合の)小 さな整数で表現される。だから変換ルーチンは必要ない。文字を持っ ているということは、その値を持っているということである。 8.9: コンパイラが調子悪い。sizeof('a')の値が1ではなくって2になって いる(sizeof(char)の値と違う)。 A: 知らなくて驚くかもしれないが、Cの文字定数はint型を持つ。だから sizeof('a')はsizeof(int)である(ただしC++では話が違う)。質問7.8も 参照のこと。 References: ANSI Sec. 3.1.3.4; ISO Sec. 6.1.3.4; H&S Sec. 2.7.3 p. 29. 9章 ブール数 9.1: ブール値をC言語で扱うのに適切なデータ型は? なぜブール値を扱う データ型が、標準で用意されていないのか。真と偽を表わすのに、 #defineを使うべきか列挙体を使うべきか。 A: C言語は標準のブール型を用意していない。これは1つにはブール型の データ型を選ぶことは空間と時間を天秤にかけることであるからであ る。どちらを選択するかはプログラマにまかせるのが一番である。 (intをブール型に選べば速いだろうし、charをブール型に選べばデー タの保存領域の節約になる。けれどintより小さいデータ型を使うと、 intに変換したりintから変換するのでコードが大きくなったり遅くな るかもしれない。) #defineを使うか列挙体を使うかはどっちでもよいことで、とくに興 味を引くようなことはない(質問2.22と17.10を参照のこと)。プログ ラムやプロジェクトで首尾一貫している限り、以下のどれを使っても かまわない #define TRUE 1 #define YES 1 #define FALSE 0 #define NO 0 enum bool {false, true}; enum bool {no, yes}; あるいは生の1と0を使うのもいい(変数の値を表示するときに列挙体 の名前で表示してくれるデバッガを使っているのであれば、列挙体の ほうがいいかもしれない)。 以下のようなちょっと違った形を好む人もいる。 #define TRUE (1==1) #define FALSE (!TRUE) あるいは"補助の"マクロ、例えば #define Istrue(e) ((e) != 0) を定義する人もいる。どちらにしても大した効果はない(以下の質問 9.2を参照。また質問5.12と10.2も参照のこと)。 9.2: TRUEを1に#defineすることは危険ではないのか。なぜなら、C言語で は0でない値はすべて"真"と考えられるから。組み込みのブール値を 返す演算子や関係演算子が1以外の値を"返したら"どうするのか。 A: C言語では、どんな非0の値も真と考えられることは真実である。しか しこのことは「入力においてのみ」、すなわちブール値がくることを 期待されているところでだけなりたつ。組み込みの演算子によってブー ル値が産み出されるときは、1か0であることが保証されている。よっ てテスト if ((a == b) == TRUE) は(TRUEが1であるかぎり)期待したとおりの結果を返す。しかしバカ げたことである。一般にTRUEやFALSEを相手に明示的にテストするこ とは望ましくない。なぜならライブラリ関数の中には(有名なのは isupper()、isalpha()など)条件が成立したときに非0の値を返すが、 その値は必ずしも1ではないものがある(さらに、もし君が「if((a == b) == TRUE)」が「if(a == b)」の改良版であると信じるのなら、な ぜそこで止めるのか。なぜ「if (((a == b) == TRUE) == TRUE)」を 使わないのか)。おおまかな目安としては、TRUEとFALSEを(あるいは 似た物を)ブール値をあらわす変数に代入する際や、関数の引数、ブー ル値を返す関数の戻り値としてだけ使うこと。これらのマクロを決し て比較に使ってはいけない。 プリプロセッサのマクロのうち、TRUEやFALSE(もちろんNULLも)は コードの可読性を上げるのに使うのであって、あらわす値が変わる可 能性があるから使うのではない(質問5.3と5.10も参照のこと)。 一方、ブール値の取る値や、定義の仕方はどう見ても混乱を招く可能 性がある。プログラマの中にはTRUEとFALSEマクロは混乱の度合を 増すだけだと考えている人もいる。 References: K&R1 Sec. 2.6 p. 39, Sec. 2.7 p. 41; K&R2 Sec. 2.6 p. 42, Sec. 2.7 p. 44, Sec. A7.4.7 p. 204, Sec. A7.9 p. 206; ANSI Sec. 3.3.3.3, Sec. 3.3.8, Sec. 3.3.9, Sec. 3.3.13, Sec. 3.3.14, Sec. 3.3.15, Sec. 3.6.4.1, Sec. 3.6.5; ISO Sec. 6.3.3.3, Sec. 6.3.8, Sec. 6.3.9, Sec. 6.3.13, Sec. 6.3.14, Sec. 6.3.15, Sec. 6.6.4.1, Sec. 6.6.5; H&S Sec. 7.5.4 pp. 196-7, Sec. 7.6.4 pp. 207-8, Sec. 7.6.5 pp. 208-9, Sec. 7.7 pp. 217-8, Sec. 7.8 pp. 218-9, Sec. 8.5 pp. 238-9, Sec. 8.6 pp. 241-4; "亀がアキレスに言ったこと". 9.3: pがポインタだとしてif(p)は正しい条件か。 A: 正しい。質問5.3を参照のこと。 10章 Cプリプロセッサ 10.2: 見て見て下の気のきいたマクロ。 #define begin { #define end } どう思う。 A: ゲーッ。17章も参照のこと。 10.3: 2つの値を交換する汎用のマクロは。 A: この質問に確かな解答はない。もしも値が整数なら有名な排他論理和 を使った技を使うことができる。しかし、この技も浮動小数点表示の 数やポインタには使えない(整数の場合も同一の変数を2つの引数 として指定することはできない)。また整数型の"誰でもわかる"極端 に詰め込んだコードa^=b^=a^=bも、副作用が複数回起こることにより 厳密にいえば文法違反である)。もしマクロを任意の型の値に使いた いなら(普通はこれが目的である)、一時変数を使うことはできない。 なぜなら、どの型の一時変数が必要かわからないからである(もし一 時変数が使えたとしても、変数の名前の付けかたに苦労するだろう)。 標準Cはtypeof演算子を用意していない。 一番の万能の解決方法は、マクロを使うことを考えないことだ。ただ し型を渡すために第3の引数を渡すのが面倒でないなら話は別だ。 10.4: 複数の文(multi-statement)からなるマクロを書くにはどうすれば よいか。 A: たいてい目標は、関数を呼び出し1つからなる1つの文のように呼べるマ クロを書くことである。ということは使う人が最後のセミコロンを自 分で付けるから、マクロ本体にはセミコロンを付けてはいけないことに なる。マクロ本体は、単に括弧{}でくくった複数の文であってはな らない。なぜならマクロが(見た目は一つの文として、かつ余計なセ ミコロンを付けて呼ばれたときに)、else節を持つif/else文のifが成 立したときの分岐に使われたときに文法エラーとなる。 昔からの解決方法は以下に示すものである。 #define MACRO(arg1, arg2) do { \ /* declarations */ \ stmt1; \ stmt2; \ /* ... */ \ } while(0) /* (後ろの;は無し) */ 使う人がセミコロンを付けたときに、上記のマクロはどこで使われよ うと1つの文となる(最適化を行うコンパイラは、必ず偽になる"死ん だ"テストや定数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.7と10.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されると多重宣言エラーとなる。 Makefileの保守が面倒になる。一方#includeを入れ子にするとヘッダ ファイルをモジュールを組み上げるように使うことができる(ヘッダ ファイルが必要なファイルを#includeする。使い手はいちいち #includeしてまわらなくてすむ。いちいち#includeしてまわるのは手 に負えなくなって頭痛の種となる)。grep(やタグファイルのようなツー ル)があれば、定義がどこにあるのか見つけるのを楽になる。有名な トリック #ifndef HEADER_FILE_NAME #define HEADER_FILE_NAME …ヘッダファイルの中身… #endif は(各ヘッダファイルごとに異なるマクロ名を用意して、#ifndefでく くる)ヘッダファイルの「べき等(A * A = A)」を可能にする(2度以上 読み込まれても問題が発生しない)。こうすれば何回#includeしても 問題ない。Makefileを保守するための自動化ツール(どっちにしても、 この手のツールはプロジェクトが大きくなれば絶対必要となる。質問 18.1を参照のこと)は、入れ子になった#includeファイルの依存関係 をうまく作り出す。質問17.10を参照のこと。 References: Rationale Sec. 4.1.2. 10.8: ヘッダ(#include)ファイルを捜すのに、どこを捜しにいくのか。 A: 動作の細かいところは処理系依存である(ということは文章にしてな ければならないということを意味する。質問11.33参照)。通常は、<> の構文を持つヘッダファイルなら1つ以上ある標準の場所を探しに行 く。""の構文を持つヘッダファイルなら"現在のディレクトリ(カレン トディレクトリ: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.29, 16.2aも参照のこと。 10.11: 私の使っているシステムにはが見当たらない。だれか 送ってくれないだろうか。 A: 標準ヘッダは、君が使っているコンパイラ、オペレーティングシステ ム、プロセッサに適切な定義を与えるような形で存在している。他の 人のヘッダファイルを引っ張ってきて動くことを期待することはでき ない。もちろん相手が全く同じ環境を使っている場合は別である。コ ンパイラのベンダーになぜそのファイルが供給されなかったか尋ねて みること(あるいは代わりを送ってくれるように依頼してみること)。 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の内に あらかじめ定義された定数を使うか、"構成(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の行に使って、あるものを2つのまったく異なった 風に定義することができるか。 A: できない。「プリプロセッサをプリプロセッサにかけることはで きない」といったところか。 できるのは、#ifdefの設定によってまったく別個の2つの#defineの うちの1つを使うことである。 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: 残念ながら存在しない。あるtypedefが定義されたかどうかをマクロ (例 MY_TYPE_DEFINED)を使って記録しなければならない。(質問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で行われ、 そこにはアドレスの概念はない)。本当にマシンのエンディアンを明 示的に知りたいのか。エンディアンを気にしないコードを書くほうが よい。質問20.9も参照のこと。 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-4: マクロの展開で文字列リテラルの内側で引数を使いたい。どうすれば よいか。 A: 質問11.17と11.18を参照。 10.25: コンパイル時に凝った処理をしたいが、どうやってcppにやらせたら よいかわからない。 A: cppは汎用のプリプロセッサとしては作られていない。(別プログラム として使うことができることさえ保証されていない)。不似合いなこ とをcppにさせるよりは、特別な用途のための前処理を行うツールを 自分で作ったほうがよい。make(1)のような道具を使えば、自動的に 仕事をさせることができる。 もしC言語以外のものを前処理することを考えているのであれば、汎 用のプリプロセッサを使うことを考えること。(たいていのUnixシス テムで使える古くから存在するツールとしてはm4がある)。 10.26: 可変個の引数を取るcppのマクロをどうやって書けばよいか。 A: 有名なトリックは、引数を1つ取るマクロを定義し、使うときにも引 数を括弧でくくるというものである。そうすればマクロを展開したも のは括弧も何もかもprintfのような関数の引数リストになる。 #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.4と15.5を参照のこと。(マクロ置換が必要 なら、関数と#define printf myprintfのような関数型でないマクロ を使う。)  0, unseen,, *** EOOH *** Newsgroups: fj.archives.answers,fj.lang.c Path: newsmaster.tuis.ac.jp!news.chiba-u.ac.jp!chiba-ns!hagi!ume!igakukei!news.cs.ritsumei.ac.jp!odins-suita!aist-nara!wnoc-tyo-news!newsgate1.web.ad.jp!newsbase.nisiq.net!news1.nisiq.net!newsfeed.btnis.ad.jp!nspixp!spinnews!spin-hsd0-tky!yhqfm!xeroc!leia!133.140.40.2!kitano From: kitano@crd.yokogawa.co.jp (Kinichi - Kinchan - Kitano) Subject: comp.lang.c Answers to Frequently Asked Questions (FAQ List) in Japanese[3/4] Sender: news@leia.pa.yokogawa.co.jp (Leia news server) Message-ID: Supersedes: Date: Mon, 14 Jul 1997 17:30:51 GMT Reply-To: kitano@crd.yokogawa.co.jp Organization: Yokogawa Electric Corporation, Tokyo, Japan. Followup-To: fj.lang.c Lines: 1728 Xref: newsmaster.tuis.ac.jp fj.archives.answers:1291 fj.lang.c:5587 Archive-name: c-faq-j/part3 Last-modified: 14 July. 1997 ========================= C FAQ 日本語訳[3/4] ========================= 11章 ANSI/ISO規格C 11.1: 「ANSI C 規格」とは何を意味するのか。 A: 1983年に、アメリカ国内標準規格協会(ANSI)はC言語の標準化を目指 し委員会X3J11を発足させた。何度かの広範囲にわたる公開レビュー を含む長い困難な過程の後に、委員会の作業は、アメリカ国内標準規 格X3.159として1989年12月14日に批准され、1990年の春に出版された。 ANSI Cの大部分は世の中の慣習を規格化したもので、そのほかはC++ からの(一番有名なのは関数プロトタイプである)アイデアの拝借と、 多国語文字列への対応(酷評されている三連文字(trigraph)を含む)で ある。ANSI C規格は、Cの実行時のライブラリについても規定して いる。 もっと最近の話では、規格は国際標準ISO/IEC 9899:1990として採択 された(訳注:日本ではISO 9899を翻訳したものがJIS X 3010:1993と して発行されている)。そしてこのISOの規格がそれより前に存在した X3.159に取ってかわった。これはアメリカ合衆国でも同じである。章 番号の振り方が違っている(手短に言えばISOの5章から7章がだいたい 旧ANSIの2章から4章に対応する)。ISOの規格であるから、技術正誤表 (Technical Corrigenda)や規範補遺(Normative Addenda)を発行して 絶えず改訂しなければならない。 1994年に、技術正誤表1が発行されおよそ40ヶ所で規格が修正された (訳注: 日本では『X3101:96 プログラミング言語C(追補1)』として 1996年に発行された)。たいていは些細な修正もしくはわかりにくい ところの書き直しである。もっと最近の話では、規範補遺1でおよそ 50ページの新しい題材が追加された。ほとんどは国際化のための新し いライブラリ関数についてである。国際化のための新しいライブラリ 関数の技術正誤表の作成が進行中で、2番目の正誤表は1995年後半に 発行が予定されている。さらに、ANSIもISOも自分たちの規格の定期 的な見直しを命じている。この手続きは1995年に始まり、まったく改 定された規格を産み出すだろう("C9X"と愛称がつけられている。これ は1999年までには完成するだろうと考えられているところからつけら れている)。 出版物としての規格は、「Rationale(論理的根拠)」を含んでいる。 Rationaleは、規格の決定にまつわる多くの事柄について説明し、こ のFAQで取り上げているいくつかのことがらを含む規格の数多くの微 妙な点について説明している(RationaleそのものはANSI Standard X3.159-1989ANSI Standard X3.159-1989の一部ではない。ただし資料 として含まれている。ISO規格には含まれていない)。 11.2: どこから規格を手に入れることができるか A: アメリカ合衆国国内では、以下のところから手に入れることができる。 American National Standards Institute 11 W. 42nd St., 13th floor New York, NY 10036 USA (+1) 212 642 4900 あるいは Global Engineering Documents 2805 McGaw Avenue Irvine, CA 92714 USA (+1) 714 261 1455 (800) 854 7179 (合州国内およびカナダ) その他の国では、国の規格を取り仕切る団体かジュネーブのISOに問 い合わせてみること。(訳注: 日本では日本規格協会が発売している。 JISはTEL 03-3583-8002、ISOはTEL 03-3583-8003 より入手可能であ る。) ISO Sales Case Postale 56 CH-1211 Geneve 20 Switzerland (URL http://www.iso.chやニュースグループcomp.std.internatのFAQ であるStandards.Faqも参照のこと). 現時点では価格は、ANSIから購入するときは$130で、Globalから購入 する場合は$400.50である。Rationaleを含むオリジナルのX3.159も、 ANSIからは$205.00で、Globalからは$162.50で入手することができる。 ANSIは、その運営資金を規格の印刷物の販売から得ている。よって規 格の電子的なコピーは入手不可である。 合衆国国内では元のANSI X3.159(Rationaleを含む)を「FIPS PUB 160」 という名前で以下から入手可能かもしれない。 National Technical Information Service (NTIS) U.S. Department of Commerce Springfield, VA 22161 703 487 4650 誤解を招くような題の『注釈付きANSI C規格(Annotated ANSI C Standard)』という本がHerbert Schildtの注釈付きで Osborne/McGraw-HillからISBN 0-07-881952-0として出版されている。 これはISO 9899の、数ページを除いてほとんど全文を含んでいる。合 衆国国内では$40程度で手に入る。この本と公式の規格書の値段の差 は注釈の価値を反映していると考えられている。この本は誤りや抜け が多くて閉口する。規格そのもののほうも何ページか欠落している。 ネット上で多くの人が註釈部はまるっきり無視することを勧めている。 Clive Featherによる註釈の批評("註釈付きの註釈")は http://www.lysator.liu.se/c/schildt.htmlから手にいれることがで きる。 Rationaleのテキスト(規格全体ではない)はftp.uu.netのディレクト リdoc/standards/ansi/X3.159-1989からanonymous ftp(質問18.16参 照)可能である。http://www.lysator.liu.se/c/rat/title.htmlから も入手することができる。RationaleはSilicon Pressから出版もされ ている。ISBNは0-929306-07-4である。 See also question 11.2a below. 11.2a: 規格の改訂の進み具合に関する情報はどこから得られるか。 A: http://www.lysator.liu.se/c/index.htmlやhttp://www.dmk.com/か ら少しは得られる。 11.3: 私が使っているANSI Cコンパイラに以下のようなコードを渡すと、デー タの型が違うと文句を付ける。なぜか。 extern int func(float); int func(x) float x; {… A: 新しい書き方であるプロトタイプ宣言のextern int func(float);と、 古い書き方の定義のint func(x) float x;を混ぜて使っているからで ある。両方の書き方を混ぜて使ってもたいていの場合は問題ないが (質問11.4参照)、この場合は問題がある。 旧来のCは(ANSI Cもプロトタイプがないときや、引数が可変個数のと きはそうだが。質問15.2参照)関数に渡すときにー部の引数を"広げる "。floatはdoubleに格上げされ、charやshort intはintに格上げされ る。(古い書き方の関数定義では、値は呼ばれた側の関数で変数が狭 い型で宣言されているときには自動的に対応する"狭い"型に逆変換さ れた。) 新しい書き方であるプロトタイプ宣言を関数定義の中で首尾一貫して 使うか、 int func(float x) { … } 新しい書き方の関数プロトタイプ宣言を、古い書き方の定義に合うよ うに変更することで問題は解決する。 extern int func(double); この場合パラメータのアドレスを使う気がない限り、古い書き方の 宣言ではdoubleを使ったほうがわかりやすい。 "狭い"(char、short int、float)型は、関数引数や戻り値としては避 けたほうが安全であろう。 質問1.25も参照のこと。 References: K&R1 Sec. A7.1 p. 186; K&R2 Sec. A7.3.2 p. 202; ANSI Sec. 3.3.2.2, Sec. 3.5.4.3; ISO Sec. 6.3.2.2, Sec. 6.5.4.3; Rationale Sec. 3.3.2.2, Sec. 3.5.4.3; H&S Sec. 9.2 pp. 265-7, Sec. 9.4 pp. 272-3. 11.4: 古い書き方と新しい書き方の関数の構文を混ぜて使ってもいいのか。 A: 注意さえすれば(質問11.3を特に参照のこと)混在することは、まった く合法である。しかし古い書き方の構文は時代遅れと考えられており、 その公式サポートはある日止まってしまうかもしれない。 References: ANSI Sec. 3.7.1, Sec. 3.9.5; ISO Sec. 6.7.1, Sec. 6.9.5; H&S Sec. 9.2.2 pp. 265-7, Sec. 9.2.5 pp. 269-70. 11.5: なぜ宣言 extern f(struct x {int s;} *p); で意味不明の警告メッセージ「構造体xはプロトタイプのスコープ内 で導入された」が出るのか。 A: Cのブロックのスコープの一般規則の気まぐれにより、プロトタイプ 内で初めて宣言された構造体は(名前が出てくるだけでも)、同じソー スファイル内で宣言されている他の構造体と互換性を持つことができ ない(名前はプロトタイプの終わりでスコープから出てしまう)。 この問題を解決するには、プロトタイプの前に無意味にみえる宣言 struct x; を付ける。これによりファイル全体にわたるスコープで、構造体xの (不完全な) 定義を置く場所が確保される。これで後に続くstruct xを 使った宣言は少なくとも同じstruct xを指していることが確かになる。 References: ANSI Sec. 3.1.2.1, Sec. 3.1.2.6, Sec. 3.5.2.3; ISO Sec. 6.1.2.1, Sec. 6.1.2.6, Sec. 6.5.2.3. 11.8: なぜconstの値を、初期化指定子(initializer)や配列の大きさに使え ないか理解できない。 const int n = 5; int a[n]; A: const修飾子は実際は"読み込み専用"を意味する。constと修飾された オブジェクトは(普通は)代入不可能な実行時のオブジェクトである。 したがってconstと修飾されたオブジェクトは、定数式という用語の 全体の意味を考えると定数式とは呼べない(Cはこの面ではC++と違う)。 コンパイル時の真の定数が必要なら、プリプロセッサの#defineを使 う(列挙体が使える場面もある)。 References: ANSI Sec. 3.4; ISO Sec. 6.4; H&S Secs. 7.11.2,7.11.3 pp. 226-7. 11.9: char const *pとchar * const pの違いは。 A: char const *pは文字定数へのポインタで(文字を変更すること はできない)、char * const pは文字(の変数)へのポインタ定 数(ポインタを変更することはできない)である。 一番内側から"ひっくり返して"読む。質問1.21も参照のこと。 References: ANSI Sec. 3.5.4.1 examples; ISO Sec. 6.5.4.1; Rationale Sec. 3.5.4.1; H&S Sec. 4.4.4 p. 81. 11.10: なぜconst char **を引数として取ると関数プロトタイプに書かれた 関数に、char **を渡すことができないのか。 A: (どんな型Tに対しても)const Tへのポインタを想定しているところに Tへのポインタを使うことは可能である。しかしこの(これ以外は不可 能である)規則、つまり代わりに使うことのできるポインタ型に少し の不一致を許している規則は、再帰的に適用されるわけではなく、単 に一番上の階層で適用されるだけである。 ポインタが一段である場合を除いて、型が一致しないポインタを代入 する(または引数として渡す)ときは、明示的にキャスト(この場合は const char **)しなければならない。 References: ANSI Sec. 3.1.2.6, Sec. 3.3.16.1, Sec. 3.5.3; ISO Sec. 6.1.2.6, Sec. 6.3.16.1, Sec. 6.5.3; H&S Sec. 7.9.1 pp. 221- 2. 11.12: main()をvoidとして宣言して「mainの戻り値がない」という目障りな メッセージを消すことができるか。 A: できない。main()はintを戻り値とし、(適切な型の)0個か2個の引数 を持つと定義しなければならない。exit()を呼んでもまだ警告が出る のであれば、冗長であるがreturn文を挿入するしかない(あるいは使 える環境にいるのであれば「ここには届かないよ:NOTREACHED」命令 を使う)。 関数をvoidと宣言することは警告が出なくなったり警告の順が変わっ たりするだけではない。関数の異なる呼び出し/戻りの仕組みを使う ことになる可能性がある。これが呼び手(main()の場合、Cの実行時の スタートアップコード)が期待しているものと違うものになる可能性 がある。 (このmainに関する議論は"ホスト付き(hosted)"な環境にだけ適用可 能である。"フリースタンディング(一人立ち:freestanding)"な処理 系にはどれも適用されない。こういう処理系にはそもそもmain()がな いかもしれない。しかしながら"フリースタンディング"な処理系はま れで、そうした処理系を使っているなら、使っている人はおそらく承 知しているだろう。この違いについて聞いたことがないなら、ホスト 付きの処理系を使っている。だからこの質問で出てきた規則が当ては まる。) References: ANSI Sec. 2.1.2.2.1, Sec. F.5.1; ISO Sec. 5.1.2.2.1, Sec. G.5.1; H&S Sec. 20.1 p. 416; CT&P Sec. 3.10 pp. 50-51. 11.13: main関数の3番目の引数envpは。 A: これは(よく見かけるけれど)標準でない拡張である。標準で用意され ているgetenv()関数が提供すること以上に環境の情報が必要ならば、 グローバル変数environを使うほうがまだましな手段だろう(これも同 じように標準外だが)。 References: ANSI Sec. F.5.1; ISO Sec. G.5.1; H&S Sec. 20.1 pp. 416-7. 11.14: void main()と宣言してうまくいかないわけがないと思う。なぜなら main()から戻る代わりに、exit()を呼んでいるから。だいたい今使っ ているオペレーティングシステムはプログラムのexit値/戻り値を無 視する。 A: main()から戻ってくるかどうかは関係ないし、そのステータスを見る かどうかも関係ない。問題はmain()の宣言がおかしいと、呼び出し側 (実行時のスタートアップのコード)がmain()を正しく呼び出すことす らできないかもしれないことにある(呼出しの規約にあるかもしれな い不統一による。質問11.12参照)。君が使っているオペレーティング システムは終了時のステータスを無視して、void main()でもうまく 動くかもしれない。しかし、このやりかたは移植性が低いし、正しく もない。 11.15: 僕がいつも使っている『ほんとおの馬鹿向けのC』には、いつもvoid main()と書いてる。 A: たぶんその本の著者は自分も対象読者の一人に数えているのだろう。 不思議なことに、例題のコードでvoid main()と書いてる本は多い。 そういう本は間違っている。 11.16: exit(status)の値は、main()からの戻り値statusと本当に等しいのか。 A: 等しいときもあるし違うこともある。規格は等しいといってる。ただ し、規格に従っていないいくつかの古いシステムの上では何かしら問 題がある。また、mainにローカルなデータが、後片付けで必要な場合 はうまく動くことは期待できない。質問16.4を参照。(最後に、 main()を再帰的に起動する場合は上の2つは明らかに同じものではな い。) References: K&R2 Sec. 7.6 pp. 163-4; ANSI Sec. 2.1.2.2.3; ISO Sec. 5.1.2.2.3. 11.17: ANSIの"文字列を作り出す"プリプロセッサの演算子#を使って、メッ セージの中にシンボル定数を挿入しようとしている。けれど、#はマ クロの値ではなくマクロの名前を文字列にしてしまう。 A: マクロを展開して文字列を作り出したいときは以下のような2段の手 続きを踏まなければならない。 #define Str(x) #x #define Xstr(x) Str(x) #define OP plus char *opname = Xstr(OP); これでopnameが「OP」ではなく「plus」に設定される。 同じ様な回避手段が、トークンを連結する演算子##を使って、2つの (名前ではなく)マクロの値を連結するときに必要になる。 References: ANSI Sec. 3.8.3.2, Sec. 3.8.3.5 example; ISO Sec. 6.8.3.2, Sec. 6.8.3.5. 11.18: 「警告:マクロ展開が文字列リテラル内で発生」とは何を意味してい るのか。 A: ANSI規格成立より前のコンパイラ/プリプロセッサは以下のマクロ 定義を、 #define TRACE(var, fmt) printf("TRACE: var = fmt\n", var) 以下の様に使ったら、 TRACE(i, %d); 以下のように展開するものがある。 printf("TRACE: i = %d\n", i); つまり、マクロの引数は文字列リテラルや文字定数の中でも展開され た。 マクロの展開はK&Rでも標準Cでもこういう風には定義されていない。 マクロの引数を文字列に変換したいときは、新しく導入された#とい うプリプロセッサの演算子を文字列リテラルの連結(これもANSI C で導入された機能)とともに使う。 #define TRACE(var, fmt) \ printf("TRACE: " #var " = " #fmt "\n", var) 上の質問11.17も参照のこと。 References: H&S Sec. 3.3.8 p. 51. 11.19: #ifdefで消したコードで、奇妙な構文エラーが発生した。 A: ANSI Cでは#if、#ifdef、#ifndefによってコンパイルから"消される" テキストも「コンパイル前処理のトークンとして有効なものである」 としている。このことは終端のないコメントや引用符(短縮した単語 の中のアポストロフィは文字定数の始まりにみえるので要注意である) があってはならないし、また引用符で囲まれた部分に改行があっては ならないことを意味している。よって自然言語のコメントや擬似コー ドは「公式の」コメントの区切り記号である/*と*/の間に書かなけれ ばならない。(ただし質問20.20を参照のこと。質問10.25も。) References: ANSI Sec. 2.1.1.2, Sec. 3.1; ISO Sec. 5.1.1.2, Sec. 6.1; H&S Sec. 3.2 p. 40. 11.20: #pragmaとは何物で、何の役に立つのか。 A: #pragmaは、うまく定義された唯一の"非常口"を与える。#pragmaは、 実装に固有の制御や機能の拡張を行うのに使うことができる。たとえ ばソースコードの表示方法の制御や、警告メッセージの抑制(古い lintの/* NOTREACHED */というコメントのように)に使える。 References: ANSI Sec. 3.8.6; ISO Sec. 6.8.6; H&S Sec. 3.7 p. 61. 11.21: #pragma onceは何を意味しているのか。ヘッダファイルの中にいく つか書いてあった。 A: いくつかのプリプロセッサで、ヘッダファイルの「べき等」を作るの を(2度以上読み込まれても問題が発生しないようにするのを)容易に するための拡張として用意されている。質問10.7で説明した#ifndef を使った技と同等であるが移植性が低い。 11.22: char a[3] = "abc";は正しいのか。何を意味するのか。 A: ANSI Cでは文法的に正しい(たぶんANSI C成立以前のシステムのいく つかでも正しく動くだろう)。ただし本当に稀な状況でだけ役にたつ。 これは大きさが3の配列を宣言し、中身を'a', 'b', 'c'で初期化する。 通常の終端記号の'\0'は付かない。 よってこの配列はC言語の意味で の文字列ではなく、strcpyやprintf %sなどでは使えない。 たいていの場合、配列を初期化するときは、初期化指定子の個数はコ ンパイラに数えさせるべきである。(上の初期化指定子"abc"の場合は もちろん計算結果は4になる)。 References: ANSI Sec. 3.5.7; ISO Sec. 6.5.7; H&S Sec. 4.6.4 p. 98. 11.24: なぜvoid *ポインタを相手に算術演算をすることができないのか。 A: コンパイラにはポインタが指す先のオブジェクトの大きさがわからな い。算術演算を行う前には、ポインタをchar *または操作しようとし ているデータ型のポインタにキャストする(質問4.5を見てからにする こと)。 References: ANSI Sec. 3.1.2.5, Sec. 3.3.6; ISO Sec. 6.1.2.5, Sec. 6.3.6; H&S Sec. 7.6.2 p. 204. 11.25: memcpy()とmemmove()の違いは。 A: memmove()が、コピー元とコピー先に重なりがあったときもうまく扱 うことを保証しているのに対し、memcpy()はそんな保証はしていない。 ひょっとしたらmemcpy()はそれを利用して効率のいい実装方法を取っ ているかもしれない。疑しいときはmemmove()を使うほうが安全であ る。 References: K&R2 Sec. B3 p. 250; ANSI Sec. 4.11.2.1, Sec. 4.11.2.2; ISO Sec. 7.11.2.1, Sec. 7.11.2.2; Rationale Sec. 4.11.2; H&S Sec. 14.3 pp. 341-2; PCS Sec. 11 pp. 165-6. 11.26: malloc(0)は、どういう動作をすべきなのか。ヌルポインタを返す のか、0バイトの領域を指すポインタを返すのか。 A: ANSI/ISO規格にはどちらでもかまわないと書いてある。動作は処理系依 存(質問11.33参照)である。 References: ANSI Sec. 4.10.3; ISO Sec. 7.10.3; PCS Sec. 16.1 p. 386. 11.27: なぜANSI規格は、外部識別子の7文字目以降に意味があることや大文 字小文字の違いを保証しないのか。 A: 問題は古いリンカにある。古いリンカはANSI規格の管理下にないし、 古いリンカが載ったシステムを使ってCコンパイラを開発している人 の管理下にもない。識別子は最初の6文字しか有効でないと制限して いるのであって、識別子の長さを6文字に制限するということではな い。この制限はうんざりさせられるが耐えられないものでもない。こ の制限は、規格の中で「すたれつつある」と記述されている。将来の 改定で制限は緩和されそうである。 制限を持つ現状のリンカへの譲歩は、一部の人がどんなに反対しよ うと、行わなければならない(Rationaleはこの制限を残しておかなけ ればならないことを「一番辛いこと」と書いている)。もし賛成でき ないなら、または制限のあるリンカを使ったコンパイラが外部識別 子にもっと多くの文字数を有効に扱っているとプログラマに見せる トリックを思い付いたら、X3.159のRationale(質問11.1を参照)の 3.1.2項を読むこと。この章はいくつかの方法を議論し、なぜそうい う方法を強制することができなかったか説明している。 References: ANSI Sec. 3.1.2, Sec. 3.9.1; ISO Sec. 6.1.2, Sec. 6.9.1; Rationale Sec. 3.1.2; H&S Sec. 2.5 pp. 22-3. 11.29: 私の使っているコンパイラは、考えられる限りの一番単純なテストプ ログラムに対しても、ありとあらゆる文法エラーではねつける。 A: きっと君が使っているのはANSI規格が決まる前に作られたコンパイラ で、関数プロトタイプの類をはねつけるからである。 質問1.31, 10.9, 11.30, 16.2aも参照のこと。 11.30: ANSIコンパイラを使っているのに、ANSI/ISO規格のライブラリルー チンで未定義となるものがあるのはなぜか。 A: ANSIの構文は受け付けるけれどANSI互換のヘッダファイルやランタイ ムライブラリが導入されていないコンパイラは珍しくない。(実際に はこのような状況は、ベンダーが提供しているのではないgccのよう なコンパイラを使うときにはよくあることである。) 質問11.29, 13.25, 13.26も参照のこと。 11.31: 誰か古い書き方のCプログラムをANCI Cに変換するプログラムや、そ の反対を行うツール、また自動的に関数プロトタイプを生成する ツールを持っていないか。 A: protoizeとunprotizeというのが、関数プロトタイプから古い書き方 の関数定義や宣言へ、あるいはその逆を実行するツールである(これ らのツールは、"古い"書き方のCとANSI C間の完璧な変換ツールでは ない)。FSFのGNU Cコンパイラの正式な配布の一部である。質問18.3 も参照のこと。 プログラムunproto(サイトftp.win.tue.nlのファイル /pub/unix/unproto5.shar.Z)は、プリプロセッサとコンパイラの間 に入って、ANSI Cを古いCに(コンパイル時に)変換するフィルターで ある。 GNU GhostScriptパッケージにはansi2knrという、ちょっとしたプロ グラムが付いている。 ANSI Cで書いたプログラムを古いCに変換する前に、このような変換 は安全に行うことも自動的に行うこともできないことに注意すること。 ANSI CはK&Rにない新しい機能を導入し、より複雑になっている。プ ロトタイプ付きの関数の呼び出しには特に注意すること。たぶん明示 的なキャストが必要となるだろう。質問11.3と11.29も参照のこと。 関数プロトタイプの自動生成ソフトはいくつか存在する。多くはlint に手を入れたものになっている。CPROTOというプログラムが1992年3 月にcomp.sources.miscにポストされた。他にもcextractというツー ルが存在する。コンパイラのベンダーの多くは単純なユーティリティ を用意している。質問18.16も参照のこと。(古いコード用にプロトタ イプを生成するときは"狭い"引数に注意すること。質問11.3も参照の こと。) 最後に一言。大量の古いコードをANSI Cに変換する必要が本当にある のか。古い書き方の関数の構文はそのまま使える。あわてて変換する とバグを持ち込みやすい。(質問11.3を参照。) 11.32: なぜANSI準拠が売り物のCコンパイラ轟天が、このコードをはねつけ るのか。私はこのコードはANSIに規定された通りだと考える。なぜな らgccなら受け付けるからである。 A: たいていのコンパイラは規格外の拡張機能をいくつか用意している。 gccはその最右翼である。はねられたコードが、そのような拡張機能 を使っていない自信があるか。言語の性質を調べるのに特定のコンパ イラだけで実験することはよい考えではない。すなわち、規格が拡張 を容認しているかもしれないし、実験に使ったコンパイラの誤りかも しれない。質問11.35も参照のこと。 11.33: 処理系定義の(implementation-defined)動作、未規定の (unspecified)動作、未定義の(undefined)動作を区別することを世の 中では重視するようだ。違いは? A: 簡潔に説明する。処理系定義の動作とは、どう振る舞うかを実装が選 択して、その動作を文書にすることを意味する。未規定の動作とは、 どう振る舞うかを実装が選択しなければならないが文書にする必要は ないことを意味する。未定義とは、本当にどんなことがおこっても不 思議ではないことを意味する。どの場合にも規格は、必要条件を課し ていない。最初の2つの動作について規格は時々ありそうな動作をい くつか提案している(そのなかから選択しなければならないかもしれ ない)。 規格は未定義の動作に直面したときのコンパイラの動作について何も 必要条件を設定していないことに注意すること。だからコンパイラは 本当に何をしてもいいことになる。プログラムの中に未定義の動作が あっても何とかやっていけると考えるのは大変危険である。比較的簡 単な例が質問3.2に出てくる。 移植性のあるコードを書きたいのなら上の3つの違いを無視すること ができる。これら3つの動作のどれにも頼らないコードを書こうと思 うだろうから。 質問3.9と11.34も参照のこと。 References: ANSI Sec. 1.6; ISO Sec. 3.10, Sec. 3.16, Sec. 3.17; Rationale Sec. 1.6. 11.34: ANSI規格には多くの論点が未定義のまま放置してあることを考えると ぞっとする。規格の仕事はこれらの問題を標準化することではなかっ たのか。 A: C言語は昔からその一部はコンパイラやハードウエアがどう実装され てどう振る舞ってもいいようになっている。こういうふうにわざとはっ きりさせないことで、コンパイラはありそうもないような状況にまで 適切に定義された動作を保証することのためにすべてのプログラムに 余計なコードの重荷を背負わせることなく、よくある場合についてずっ と効率のいいコードを生成することができる。よって規格はそれまで に存在した習慣を単に明文化したにすぎない。 プログラミング言語の規格は言語の使用者とコンパイラの実装者の間 の契約と考えればいい。その契約の一部はコンパイラ実装者が提供を 約束した機能で、使い手は用意されているものだと思いこんでいい。 しかしながら、別の部分はユーザーが従うと約束した決まりで、実装 者は守ってもらえると思いこんでいい。両方が自分の約束を守るかぎ り、プログラムは頑張れば動く可能性がある。どちらかでも公約を破 れば、どんな部分についても確実に動くとは保証できない。 質問11.35も参照のこと。 References: Rationale Sec. 1.1. 11.35: i = i++の動作が未定義だとうるさくいう人がいるけれどANSI準 拠のコンパイラで試して私が思うとおりの結果を得た。 A: 未定義の動作に出くわしたらコンパイラは好きなように振る舞う(実 装が定義した動作、あるいは未規定の振る舞いに出くわしたときにも ある程度好きなように振る舞う)。その中にはあなたが期待した結果 も含む。こんなことに頼るのは馬鹿げている。質問11.32と11.33と 11.34を参照のこと。 12章 標準入出力(stdio)ライブラリ 12.1: なぜ以下のコードはうまく動かないのか。 char c; while((c = getchar()) != EOF) ... A: getchar()の戻り値を格納する変数はintでなければならない。 getchar()は、文字型のあらゆる値を返すだけでなくEOFも返す。 getchar()の戻り値をcharで渡すと、普通の文字が返ってきたのにEOF と誤解されたり、EOFが他の値に変えられて(特にデータ型charが符号 なしの場合)、いつになってもEOFが出てこないかもしれない。 References: K&R1 Sec. 1.5 p. 14; K&R2 Sec. 1.5.1 p. 16; ANSI Sec. 3.1.2.5, Sec. 4.9.1, Sec. 4.9.7.5; ISO Sec. 6.1.2.5, Sec. 7.9.1, Sec. 7.9.7.5; H&S Sec. 5.1.3 p. 116, Sec. 15.1, Sec. 15.6; CT&P Sec. 5.1 p. 70; PCS Sec. 11 p. 157. 12.2: なぜ以下のコードは最後の行を2回コピーするのか。 while(!feof(infp)) { fgets(buf, MAXLINE, infp); fputs(buf, outfp); } A: Cでは、EOFは入力ルーチンが読もうとしてファイルの終わり (End-Of-File)にたどり着いた後であることを示しているだけである (言い換えればC言語の入出力はPascalの入出力とは異なる)。たいて いは入力関数(この場合はfgets)の戻り値をチェックすればよい。 feof()を使う必要が全くない場合が多い。 References: K&R2 Sec. 7.6 p. 164; ANSI Sec. 4.9.3, Sec. 4.9.7.1, Sec. 4.9.10.2; ISO Sec. 7.9.3, Sec. 7.9.7.1, Sec. 7.9.10.2; H&S Sec. 15.14 p. 382. 12.4: 私のプログラムのプロンプトと中間出力が、画面上にあらわれないこ とがある。特に、パイプを通して出力を他のプログラムに渡したとき に、この問題が起こる。 A: 出力が見えて欲しいところでは、必ず明示的にfflush(stdout)を使っ て出力をはきだすこと。ほっておいてもいくつかの仕組みが、 fflush()を"適切なときに"実行してくれる。しかし、これはstdoutが 端末のときにしか適応されないことが多い(質問12.24も参考のこと)。 References: ANSI Sec. 4.9.5.2; ISO Sec. 7.9.5.2. 12.5: RETURNキーが押されるのを待つことなく、一度に一文字ずつ読むこと ができるか。 A: 質問19.1を参照のこと。 12.6: どうすれば、printfのフォーマット文字列を使って'%'を出力できる のか。\%を試したけれど、うまくいかなかった。 A: %%というふうに、%を二回続けて書く。 \%ではうまくいかない。バックスラッシュ\はコンパイラのエスケー プシーケンスで、printfのエスケープシーケンスは%である。 質問19.17も参照のこと。 References: K&R1 Sec. 7.3 p. 147; K&R2 Sec. 7.2 p. 154; ANSI Sec. 4.9.6.1; ISO Sec. 7.9.6.1. 12.9: %lfをprintf()で使うのは間違っていると教えてくれた人がいた。 scanf()が%lfを使うのに、どうしてprintf()は%fを使うのか。 A: printf()の%f指定子はfloatの引数にもdoubleの引数にも作用する。 "省略時の引数の格上げ"により(この規則はprintfのような可変個数 の引数を取る関数に、その関数のプロトタイプがスコープ内でも外で も適応される)、データ型floatの変数はdoubleに格上げされる。よっ てprintf()が見るのはdoubleの変数だけである。質問12.13, 15.2も 参照のこと。 References: K&R1 Sec. 7.3 pp. 145-47, Sec. 7.4 pp. 147-50; K&R2 Sec. 7.2 pp. 153-44, Sec. 7.4 pp. 157-59; ANSI Sec. 4.9.6.1, Sec. 4.9.6.2; ISO Sec. 7.9.6.1, Sec. 7.9.6.2; H&S Sec. 15.8 pp. 357-64, Sec. 15.11 pp. 366-78; CT&P Sec. A.1 pp. 121-33. 12.10: printf()でフィールドの幅を可変に実装するのはどうすればいいのか。 つまり、%8dと書く替わりに、実行時に幅を指定したい。 A: printf("%*d", width, n)が君の望むことをやってくれる。質問12.15 も参照のこと。 References: K&R1 Sec. 7.3; K&R2 Sec. 7.2; ANSI Sec. 4.9.6.1; ISO Sec. 7.9.6.1; H&S Sec. 15.11.6; CT&P Sec. A.1. 12.11: 千単位でコンマで切って数を表示するのはどうすればよいか。通貨に 合わせた書式で表示するのは。 A: にあるルーチンでこれらの操作に対応するものも出始めた。 しかし上のどちらの仕事についても標準のルーチンというのは存在し ない。(printf()が、その土地の慣習に従うことといえば小数点の表 示に使う文字を変更することだけである。) References: ANSI Sec. 4.4; ISO Sec. 7.4; H&S Sec. 11.6 pp. 301-4. 12.12: なぜコードscanf("%d", i);がうまく動かないのか。 A: scanf()は、値を書き込む変数へのポインタを必要とする。 scanf("%d", &i);と書かなければならない。 12.13: なぜ以下のコードは動かないのか。 double d; scanf("%f", &d); A: printf()と違ってscanf()ではdoubleには%lfを、floatには%fを使う。 質問12.9も参考のこと。 12.15: scanf()の書式文字列で可変の幅を指定するのはどうやればよいか。 A: できない。scanf()の書式文字列に*を書くと、代入を抑 制することとなる。ANSIの文字列作成(stringizing)や文字列連結の 機能を使って、ほとんど同じことができる。scanf()の書式文字列を その場で作るのもいい。 12.17: scanf()を使ってキーボードから読み取ると、もう1行余計に打ち込 むまで、ハングするようだ。 A: 知らなくて驚くだろうけど、scanfの書式文字列で\nは改行を意味す るのではなく、空白が続くかぎり読んでは捨てることを意味している。 質問12.20も参照のこと。 References: K&R2 Sec. B1.3 pp. 245-6; ANSI Sec. 4.9.6.2; ISO Sec. 7.9.6.2; H&S Sec. 15.8 pp. 357-64. 12.18: scanfの%dを使って数字を読んで、文字列をgets()で読もうとしてい る。けれどコンパイラはgets()の呼び出しを飛ばしているようだ。 A: scanfの%dはおしりの改行は食べてくれない。もし入力の数のすぐ後 ろに改行が来たら、その改行はgets()の入力としての条件を満足する。 一般に、scanf()の呼び出しと、gets()(その他の入力ルーチンも)の 呼び出しを混ぜて使ってはいけない。scanf()の改行の扱いが妙なの で必ずやっかいなことになる。scanf()ですべて読み込むか、scanf() を使わないかのどちらかである。 質問12.20, 12.23も参照のこと。 References: ANSI Sec. 4.9.6.2; ISO Sec. 7.9.6.2; H&S Sec. 15.8 pp. 357-64. 12.19: scanf()を使うのときに、こっちの望んだ数値をユーザーが入力した ことを確かめるのに、scanf()の戻り値を調べるほうが安全だと考え た。けれど、たまに無限ループに入ってしまうようだ。 A: scanf()が数字を変換しようとするときには、数字でない文字に出く わすと変換を終了し、その上その文字を入力ストリームに放置す る。したがって他の手段を取らないと、数字でない入力が思いがけな いところで出てくるとscanf()が何度も"ジャム"ってしまう。scanf() は問題となる文字を通り過ぎて、その後ろにある正しいデータに行く ことはない。もしユーザーがscanf()の数値のフォーマットである%d や%fなどに対して'x'のような文字を入力したとすると、単にプロン プトをまた出して同じscanf()を呼び出すようなコードは、即座に同 じ'x'に出くわすことになる。 質問12.20も参照のこと。 References: ANSI Sec. 4.9.6.2; ISO Sec. 7.9.6.2; H&S Sec. 15.8 pp. 357-64. 12.20: どうして誰もがscanf()を使わないほうがいいというのか。代わりに 何を使えばいいのか。 A: scanf()には数多くの問題がある。質問12.17, 12.18, 12.19を参照の こと。さらにscanf()の%sフォーマットはgets()が抱えているのと(質 問12.23を参照)同じ問題を抱えている。つまり受け取りに使うバッファ があふれる心配がないと保証するのは難しい。 もっと一般的な話として、scanf()はかなりかっちりした構造を持っ た、書式にのっとった入力を対象に設計されている(実際scanfという 名前は「scan formatted(書式を持ったものをスキャンする)というと ころから来ている」)。注意していれば、処理が成功したかどうかわ かる。しかし、どこで失敗したかは大体のところしか分からないし、 どういう風にとか何故かはぜんぜんわからない。scanf()を使った場 合エラーからのまっとうな復帰はほとんど不可能である。たいていは 1行丸々(fgets()かなにかで)読み込んで、sscanf()かその他の技を使っ て解釈するほうがずっと簡単である。(strtol()とかstrtok()とか atoi()といったルーチンも役に立つことが多い。質問13.6も参照のこ と。)どうしてもsscanf()を使わなければならなければ、戻り値を調 べてこちらが期待した数だけ項目が見つかったことを確認するのを忘 れてはいけない。さらに、%sを使うときは、バッファをあふれさせな いように注意すること。 References: K&R2 Sec. 7.4 p. 159. 12.21: sprinftの呼び出しで、書く先のバッファがどれくらいの大きさが必 要かどうすればわかるか。sprinftの呼び出しで書く先のバッファを、 どうすればあふれさせずにすむか。 A: この2つの鋭い質問には立派な解答は(まだ)ない。ないということは、 これまでの標準入出力ライブラリの最大の欠陥をたぶん表わしている。 書式文字列が既知で結構単純なときは、毎回特別な方法でバッファの 大きさを予想することができる。書式が1つか2つの%sから出来ている ときは、固定の文字の部分は自分で数えて(または代わりにsizeofに 数えさせて)、挿入される文字列の分はstrlen()を呼んで数えさせ、 結果を加える。%dがどれくらいの大きさを占めるかは、以下の大きさ を越えることはない。 ((sizeof(int) * CHAR_BIT + 2) / 3 + 1) /* +1 for '-' */ (CHAR_BITはに定義されている。)ただし上の計算は少し控 え目過ぎるかもしれない。(上のコードは8進数表現の数を表わすのに 必要な文字の数を計算するものである。10進数表現なら必ず同じかよ り小さい領域で済む。) 書式文字列がもっと複雑だったり実行時までわからない場合は、どれ くらい大きなバッファが必要か予想することはsprintf()を改めて実 装するのと同じくらい困難となり、それだけ間違いを犯しやすくなる (したがって勧めない)。最後の手段として時々提案されるのは fprintf()を使って同じテキストをビットバケツや一時ファイルに書 き込んで、fprintf()の戻り値やファイルの大きさを見ることである (ただし質問19.12も参照のこと。書き込みエラーについても注意する こと)。 バッファがそんなに大きくないかもしれない場合は、バッファがあふ れないとかメモリの別の部分を上書きしないと保証されない限り、 sprintf()を呼びたくないだろう。いくつかの標準入出力ライブラリ (GNUや4.4BSDの)は名前から機能がわかるsnprintf()関数を用意して いる。これは以下のように使う。 snprintf(buf, bufsize, "You typed \"%s\"", answer); ANSI/ISO Cの将来の改訂がこの機能を取り入れることを願うばかりで ある。 References: Rationale Sec. 4.9.7.2; H&S Sec. 15.7 p. 356. 12.23: なぜgets()を使うなと誰もが言うのか。 A: fgetsと違ってgetsには、これから読もうとしているバッファの大き さを伝えることができないので、バッファがあふれてしまうことを防 ぐことができない。一般的な規則として、いつもfgets()を使う。質 問7.1の、gets()の代わりにfgets()を使う方法を説明したコードを参 考のこと。 12.24: なぜprintf()を呼ぶと変数errnoにENOTTYが設定されるのか。 A: 標準入出力パッケージの多くの実装はstdoutの先が端末のときは動作 を少し変える。こういう実装では、出力先を判断するためにstdoutの 先が端末でないときは必ず失敗する操作(これがENOTTYを設定する)を 行う。よって出力の操作が完全に成功するにもかかわらずerrno の値はENOTTYとなる。(プログラムがerrnoの中身を調べることは、エ ラーが報告されたときだけ意味があることに注意。) References: ANSI Sec. 4.1.3, Sec. 4.9.10.3; ISO Sec. 7.1.4, Sec. 7.9.10.3; CT&P Sec. 5.4 p. 73; PCS Sec. 14 p. 254. 12.25: fgetpos/fsetposとftell/fseekの違いは。fgetpos()やfsetpos()は何 の役に立つのか。 A: ftell()とfseek()はファイル内でのオフセット(位置)を表すのにlong intを使う。したがって、オフセットは20億(2**31-1)までに限定され てしまう。一方、新しい関数であるfgetpos()とfsetpos()は特別な typedefであるfpos_tをオフセットを表すのに使う。fpos_tを適切に 選べばオフセットの大きさを好きなように選ぶことができる。質問 1.4も参照のこと。 References: K&R2 Sec. B1.6 p. 248; ANSI Sec. 4.9.1, Secs. 4.9.9.1,4.9.9.3; ISO Sec. 7.9.1, Secs. 7.9.9.1,7.9.9.3; H&S Sec. 15.5 p. 252. 12.26: ユーザーが先打ちした入力が、次に入力待ちするところで読まれるこ とがないように、処理されていない入力を捨てることができるか。 fflush(stdin)は効くか。 A: fflush()は、出力ストリームにしか定義されていない。fflushの "flush(押し流す)"は、バッファリングされた文字の(捨てるのではな く)書き込みを達成することを意味している。読まれてない入力を捨 てることは、入力ストリームをfflushするということと同じ意味では ない。 標準入出力の入力バッファの読んでいない入力を捨てる一般的な方法 はないし、そんなことをするだけでは充分でない。読まれていない文 字は、オペレーティングシステムレベルのバッファにもため込まれて いる可能性がある。\nが出るまで文字を読んでは捨てて、それから cursesのflushinp()やシステム固有の技を使うことで済ますことがで きるかもしれない。質問19.1と19.2も参照のこと。 12.30: fopenのモード「r+」を使ってファイルを開いて、次にある文字列を 読んで、最後に修正した文字列を書き戻すことでファイルを更新しよ うとしている。でもうまくいかない。 A: 書く前にfseek()を必ず呼ぶこと。上書きしようとしている文字列の 先頭にシークして戻すことが必要であるし、読み書き両方可能の「+」 モードでデータを読むときと書くときの間にはfseek()かfflush()が 必要である。もともとあった文字と同じ個数しか上書きできないこと、 テキストモードで上書きするとファイルがその点で切り捨てられてし まうかもしれないことを忘れてはいけない。質問19.14も参照のこと。 References: ANSI Sec. 4.9.5.3; ISO Sec. 7.9.5.3. 12.33: プログラムの中からstdinやstdoutの先をファイルにリダイレクトす るのはどうすればよいか。 A: freopen()を使う(ただし以下の質問12.34を参照のこと)。 References: ANSI Sec. 4.9.5.4; ISO Sec. 7.9.5.4; H&S Sec. 15.2. 12.34: freopen()を使った後で、元のstdout(あるいはstdin)に戻すことがで きるか。 A: よい方法は存在しない。もし行ったり来たりしたいのであれば、最高 の方法はfreopen()なんて使わないことである。自分で明示的に定義 した出力(あるいは入力)ストリーム変数を使うこと。そうすれば思う ままに出力(または入力)を割り付けて、しかも元のstdout(あるいは stdin)を壊さなくてすむ。 12.38: バイナリデータのファイルを、ちゃんと読むにはどうすればよいか。 ときどき0x0aとかx0dといった値が壊れてしまうのを目撃する。デー タに0x1aが含まれていると本来より早くEOFに出くわすようだ。 A: バイナリデータを読み込むときには、fopen()を呼ぶ際に、テキス トファイルの変換が実行されないよう、"rb"モードを指定すること が必要である。同じように、バイナリファイルを書き込むときは、 "wb"を使わなければならない。 テキストとバイナリはファイルをオープンするときに区別される。 いったんファイルを開いたら、ファイルにどちらの入出力コールをして いるかは関係ない。質問20.5も参照のこと。 References: ANSI Sec. 4.9.5.3; ISO Sec. 7.9.5.3; H&S Sec. 15.2.1 p. 348. 13章 ライブラリ関数 13.1: どうすれば数を文字列に変換することができるか(atoiの反対)。関数 itoaというのは存在するのか。 A: なにも考えずにsprintf()を使え。(「sprintfはやりすぎだ。実行時 間とコード領域を無駄遣いする。」という声は無視すること。とにか く動くんだから。) 質問7.5の解答内の例を参照のこと。質問12.21も 参照のこと。 すぐわかるようにsprintf()を使えば(%ldや%fを使って)longや浮動小 数点数も文字列に変換することができる。 References: K&R1 Sec. 3.6 p. 60; K&R2 Sec. 3.6 p. 64. 13.2: なぜstrncpy()はコピー先の文字列に、終端文字の'\0'を付けないこ とがあるのか。 A: もともとはstrncpy()は、今となっては時代遅れになってしまったデー タ構造、すなわち固定長で必ずしも\0で終わるとは限らない"文字列" を扱うために設計された(strncpy()の関連する奇妙なところは、指定 された長さになるまで\0を埋めることである)。確かにこの"文字列" 以外を扱うときは少し間抜けである。しばしば目的の文字列に、自分 で' \0'を追加しなければならない。この問題はstrncpy()の代わりに strncat()を使うことで回避することができる。もしコピー先の文字 列が最初は空なら、strncat()が、strncpy()がたぶんやってくれると 思っていたことをやってくれる。他にはsprintf(dest, "%.*s", n, source)が考えられる。 (文字列ではなく)任意のバイトをコピーするときには、普通は memcpy()のほうがstrncpy()よりも適切なルーチンである。 13.5: どうしてtoupper()の中には大文字を与えると変な動きをするものが あるのか。どうしてコードによっては、toupper()を呼ぶ前に islower()を呼んでいるのか。 A: 古い版のtoupper()やtolower()は変換が必要でない引数(数字や句読 点や既に文字が望みのcaseに(toupper()の場合は大文字に、 tolower()の場合は小文字に)なっているときは)には正しく動かない 場合があった。ANSI/ISO 規格のCでは、これらの関数はすべての文字 に対してきちんと動くことが保証されている。 References: ANSI Sec. 4.3.2; ISO Sec. 7.3.2; H&S Sec. 12.9 pp. 320-1; PCS p. 182. 13.6: 文字列を空白文字列によって区切られたフィールドに分けるのはどう やればよいか。これをmain()の引数のargcとargvのように分けるには どうすればよいか。 A: この種の"字句単位に切る(tokenizing)"ルーチンで標準で使えるのは strtok()だけである。ただし使うにはコツがいるし、やりたいことを 全部やってくれるとは限らない(例えば引用符の問題)。 References: K&R2 Sec. B3 p. 250; ANSI Sec. 4.11.5.8; ISO Sec. 7.11.5.8; H&S Sec. 13.7 pp. 333-4; PCS p. 178. 13.7: 正規表現とかワイルドカードを使った比較をするコードが必要となっ た。 A: まず、古典的な正規表現(Unixのedやgrepといったユーティリティで 使われている)とファイル名のワイルドカード(たいていのオペレーティ ングシステムで使われている)の違いが分かっているかどうか確認す ること。 正規表現による比較をするパッケージは数多く用意されている。たい ていのパッケージは一組の関数からなっている。正規表現を"コンパ イル"する関数と、その結果を"実行"する関数である(結果と文字列の 比較をする)。とかといった名前のヘッダファ イルや、regcmp()/regex()とかregcomp()/regexec()とか re_comp()/re_exec()といった名前の関数の組を探すこと(これらの組 は独立の正規表現ライブラリに存在するかもしれない)。自由に配布 していい人気のある正規表現パッケージとしてHenry Spencerの書い たものがある。これはcs.toronto.eduのpub/regexp.shar.Zや、その 他のアーカイブから入手可能である。GNUプロジェクトはrxという名 前のパッケージを用意している。質問18.16も参照のこと。 ファイル名のワイルドカードによる一致(globbingとも呼ばれる)はシ ステムによってやりかたが異なる。Unixでは、ワイルドカードはプロ セスが起動される前にシェルによって自動的に展開されるので、プロ グラムが心配しなければならない場合は滅多にない。MS-DOSのコンパ イラの多くは、特別なオブジェクトファイルを用意していて、プログ ラムとリンクするとargvを作るときにワイルドカードを展開してくれ る。いくつかのシステム(MS-DOSやVMSを含む)はワイルドカードで指 定されたファイルの一覧を表示したりファイルを開いたりするサービ スを用意している。コンパイラ/リンカについてきた資料をよく読む こと。質問19.20と20.3も参照のこと。 13.8: 文字列の配列をqsort()でソートするのに、strcmp()を比較用の関数と して使用しているが、うまくいかない。 A: "文字列の配列"とはたぶん"charへのポインタからなる配列"を意味し ていると思う。qsortの比較用の関数の引数は、ソートの対象へのポ インタである。ここではcharへのポインタへのポインタであるしかし ながら、strcmpはcharへの単なるポインタを引数とする。したがって strcmp()を直接使うことはできない。以下のような間に入る比較の関 数を書く。 /* ポインタを通して比較する */ int pstrcmp(const void *p1, const void *p2) { return strcmp(*(char * const *)p1, *(char * const *)p2); } 比較に使う関数の引数は"汎用のポインタ"const void *で表わされる。 引数は実際の型(char **)に再び変換されて、間接参照される。その 結果char *の引数となってstrcmp()に渡される。(ANSI以前のコンパ イラで使うときは、ポインタ引数をvoid *ではなくchar *と宣言して、 constを削る。) (K&R 2 Sec. 5.11 pp. 119-20の議論を読むときは用心すること。そ こでは標準ライブラリのqsortを議論しているわけではない。) References: ANSI Sec. 4.10.5.2; ISO Sec. 7.10.5.2; H&S Sec. 20.5 p. 419. 13.9: qsort()を使って構造体の配列をソートしようとしている。私が書いた 比較ルーチンは、構造体へのポインタを引数として取る。けれどコ ンパイラは、私の関数がqsort()の引数としては間違ったデータ型だ と文句を付ける。関数へのポインタをどのようにキャストすれば警 告を消し去ることができるのか。 A: 型の変換を比較関数内で行わなければならない。比較関数は上の Q12.2で議論したように"汎用のポインタ" (const void *)を引数と して取ると宣言されていなければならない。コードは以下のようになる だろう。 int mystructcmp(const void *p1, const void *p2) { const struct mystruct *sp1 = p1; const struct mystruct *sp2 = p2; /* これからsp1->whateverとsp2-> ...を比較する */ (汎用のポインタから構造体mystructを指すポインタへの変換が、初 期化sp1 = p1 と sp2 = p2で行われる。p1とp2がvoidのポインタであ るから、コンパイラが暗黙のうちに変換を行う。ANSI以前のコンパイ ラを使うときは明示的なキャストとchar *のポインタを使うことが必 要となる。質問7.7も参照のこと。) 一方、構造体へのポインタをソートするのであれば質問13.8のように 間接参照が必要である。 sp1 = *(struct mystruct **)p1 ) 一般に、"コンパイラを黙らせる"だけのためにキャストをつけるのは いい考えではない。コンパイラの警告は、たいてい何かを伝えようと してるわけで、自分がなにをやってるかよく理解していないのに、無 視したり黙らせたりするとひどいめにあう。質問4.9も参照のこと。 References: ANSI Sec. 4.10.5.2; ISO Sec. 7.10.5.2; H&S Sec. 20.5 p. 419. 13.10: リンク付きリストをどうやってソートすればよいか。 A: リストを作っている間ずっとリストの要素の順が乱れないようにして おくほうが(あるいは木(tree)を使うほうが)、後でソートするよりも やさしいこともある。挿入ソート(insertion sort)やマージソート (merge sort)のようなアルゴリズムはリンク付きリストといっしょに 使うのに向いている。標準のライブラリ関数を使いたいなら、ポイン タの配列を一時的に確保して、配列内の各ポインタをそれぞれリスト の各ノードを指すようにする。qsort()を呼んで、最後にソート済み の配列に基づいてリストのポインタを再構築する References: Knuth Sec. 5.2.1 pp. 80-102, Sec. 5.2.4 pp. 159-168; Sedgewick Sec. 8 pp. 98-100, Sec. 12 pp. 163-175. 13.11: メモリに収まりきらない量のデータをどうやってソートすればよい か。 A: 「外部ソート(external sort)」を使えばよい。これは例のKnuthの第 3巻で読むことができる。基本的な概念はデータを塊にしてソートし て(一度にメモリに納まるほどの大きさで)、ソート済みの各塊を一時 的にファイルに書き込んで、ファイルを1つにまとめる。オペレーティ ングシステムが汎用のソートユーティリティを提供してるかもしれな い、提供されていたらプログラムの中から呼び出してみればいい。質 問19.27と19.30も参照のこと。 References: Knuth Sec. 5.4 pp. 247-378; Sedgewick Sec. 13 pp. 177-187. 13.12: Cのプログラムで時刻を得るのはどうすればよいか。 A: 関数time()とctime()かlocaltime()、またはこれらのすべてを使う (これらの関数は昔から存在するしANSI規格の中にも存在する)。以下 に簡単な見本を載せておく。 #include #include main() { time_t now; time(&now); printf("It's %.24s.\n", ctime(&now)); return 0; } References: K&R2 Sec. B10 pp. 255-7; ANSI Sec. 4.12; ISO Sec. 7.12; H&S Sec. 18. 13.13: ライブラリ関数localtime()はtime_tを構造体tmに変換することと、 ctime()はtime_tを印刷可能な文字列に変換するということは知って いる。さて構造体tmや文字列からtime_tへの逆変換はどうやって 行なえばよいか。 A: ANSI Cは、構造体tmをtime_tに変換するライブラリ関数mktime()を 用意している。 文字列をtime_tに変換するのは、もう少し骨が折れる。なぜなら解析 しなければならない日付や時間の書式の種類が広範囲にわたるからで ある。strptime()という関数を用意しているシステムもある。これは 基本的にはstrftime()の逆の動作をする。他に人気のあるルーチンと してはpartime(RCSのパッケージとともに配布されている)や getdate() (その他のいくつかの関数と共にCニュースのパッケージで 配布されている)がある。質問18.16を参照のこと。 References: K&R2 Sec. B10 p. 256; ANSI Sec. 4.12.2.3; ISO Sec. 7.12.2.3; H&S Sec. 18.4 pp. 401-2. 13.14: ある日からN日後が何月何日かをどうやって計算するのか。2つの日付 の差はどうやって計算するのか。 A: ANSI/ISO規格のC言語は、mktime()とdifftime()という関数を、上の 要求に答えるために用意している。mktime()は正規化されていない日 付を引数に取る。データを設定済みの構造体tmを用意して、tm_field フィールドに日付を足したり引いたりしてからmktime()を呼んで年・ 月・日のフィールドを正規化(ついでにtime_t型の値に変換)すること は難しくない。difftime()は二つのtime_tの値の差を秒で計算する。 引き算に使う日付のtime_tを計算するのにmktime()を使うことができ る。 これらの解はtime_tで表現できるに日付が収まるときにだけ正しく動 くことを保証されている。tm_mdayフィールドはintで、32,736より大 きな日付のオフセットはオーバーフローする可能性がある。夏時間と の切替時には、その土地の1日は24時間ではないことにも注意するこ と(したがって86400で割り切れると思い込まないこと)。 両方の質問の別の取り組みかたとしては"ユリアス日(Julian day)"数 を使う方法がある。ユリアス日ルーチンの実装はSimtel/Oaklandアー カイブ(質問18.16参照)からJULCAL10.ZIPという名前で入手可能であ る。参考文献に挙げた"Data conversions"という記事からも得ること ができる。 質問13.13, 20.31, 20.32も参照のこと。 References: K&R2 Sec. B10 p. 256; ANSI Secs. 4.12.2.2,4.12.2.3; ISO Secs. 7.12.2.2,7.12.2.3; H&S Secs. 18.4,18.5 pp. 401-2; David Burki, "Date Conversions". 13.15: 乱数発生器が欲しい。 A: 標準のCライブラリにrand()というのが存在する。君が使っているシ ステムのrand()の実装は完璧でないかもしれないが、よりよい関数を 書くのは容易でないことも事実である。 自分で乱数発生器を実装するはめに陥ったとしても数多くの文献が出 まわっている。Referencesを参照のこと。ネットの上にもたくさんの パッケージが流れている。r250とかRANLIBとかFSULTRAといった名前 のパッケージを探すこと(質問18.16参照)。 References: K&R2 Sec. 2.7 p. 46, Sec. 7.8.7 p. 168; ANSI Sec. 4.10.2.1; ISO Sec. 7.10.2.1; H&S Sec. 17.7 p. 393; PCS Sec. 11 p. 172; Knuth Vol. 2 Chap. 3 pp. 1-177; Park and Miller, "Random Number Generators: Good Ones are hard to Find". 13.16: ある範囲の整数からなる乱数はどうやったら生成することができるか。 A: すぐに思い付く、 rand() % N /* タコ */ (これは0からN-1までの数を返そうとする)は乱数の質が低い。なぜな ら乱数発生器の多くで下位のビットは悲しいほどランダムでない(質 問13.18を参照のこと)。よりよい方法は以下のようなものである。 (int)((double)rand() / ((double)RAND_MAX + 1) * N) 浮動小数を使うことが気になるのなら、以下の方法を試せばよい。 rand() / (RAND_MAX / N + 1) どちらの方法もRAND_MAX(ANSIはで定義している)の値を知っ ていることが当然必要である。またどちらもNがRAND_MAXにくらべて 十分小さいことを仮定している。 (ところで、RAND_MAXはCライブラリのrand()関数が返す値の範囲を 表わす定数である。RAND_MAXを他の値に変更することはできないし、 rand()に別の範囲の数を返すように指定することもできない。) 0から1の間の小数を返す乱数発生器を元にするなら、単にその乱数発 生器の出力にNを掛けると0からN-1の範囲の乱数発生器が得られる。 References: K&R2 Sec. 7.8.7 p. 168; PCS Sec. 11 p. 172. 13.17: 私のプログラムを走らせるたび、いつも関数rand()から同じ乱数列が 返ってくる。 A: srand()を使って、擬似乱数発生器に本当にランダムな初期値を与え ればよい。よくある乱数の種は、ユーザーがキーを押してからの経過 時間や時刻である(ただしキーが押された時間というのは移植性の高 い方法では求めにくい。質問19.37を参照のこと)。(プログラム中で srand()を2回以上呼んで役に立つことは滅多にない。"本当にランダ ムな"数を得ようと思って、rand()を呼ぶ前に毎回srand()を呼ぶよう な真似はぜったいにしてはいけない。) References: K&R2 Sec. 7.8.7 p. 168; ANSI Sec. 4.10.2.2; ISO Sec. 7.10.2.2; H&S Sec. 17.7 p. 393. 13.18: 真偽値からなる乱数が欲しいのでrand() % 2を使ったところ、結果は 0と1が交互に現れるだけだった。 A: 出来の悪い擬似乱数発生器では(いくつかのシステムに乗っているも のは不幸なことにそうである)下位のビットはあまりランダムではな い。上位のビットを使うこと。質問13.16を参照のこと。 13.20: 正規分布つまりガウス分布の乱数を生成するのはどうすればよいか。 A: 以下のBoxとMullerの方法はKnuthご推薦である。 #include #include double gaussrand() { static double V1, V2, S; static int phase = 0; double X; if(phase == 0) { do { double U1 = (double)rand() / RAND_MAX; double U2 = (double)rand() / RAND_MAX; V1 = 2 * U1 - 1; V2 = 2 * U2 - 1; S = V1 * V1 + V2 * V2; } while(S >= 1 || S == 0); X = V1 * sqrt(-2 * log(S) / S); } else X = V2 * sqrt(-2 * log(S) / S); phase = 1 - phase; return X; } この他の方法については、このFAQの拡張版(質問20.40参照)を参照の こと。 References: Knuth Sec. 3.4.1 p. 117; Box and Muller, "A Note on the Generation of Random Normal Deviates"; Press et al., _Numerical Recipes in C_ Sec. 7.2 pp. 288-290. 13.24: 古いプログラムを移植しようとしている。なぜ「未定義の外部シンボ ル」というエラーが出るのか。 A: これから(左側に)挙げるルーチンはそれぞれ時代遅れである。代わり に右側のルーチンを使え。 index? strchrを使え。 rindex? strrchrを使え。 bcopy? 1番目の引数と2番目の引数を入れ替えて memmoveを使え(質問11.25も参照のこと)。 bcmp? memcmpを使え。 bzero? 2番目の引数を0にしてmemsetを使え。 逆に、右側の列の関数が載っていない古いシステムを使っているのな ら、右側の関数を左側の関数を使って実装、あるいは代用することが できるかもしれない。 References: PCS Sec. 11. 13.25: 「ライブラリのルーチンが未定義」というエラーが出たままである。 正しいヘッダファイルをすべて#includeしたのに。 A: 一般に、ヘッダファイルはライブラリ関数の宣言を与えるだけで、 ライブラリ関数そのものを与えるわけではない。いくつかの場合 (とくに関数が標準でない場合)、プログラムをリンクするときに 正しいライブラリが見つかるように、パスを明記しなければならない (ヘッダファイルを#includeするだけではそんなことまでやってくれない)。 質問11.30, 13.26, 14.3を参照のこと。 13.26: それでも「ライブラリのルーチンが未定義」というエラーが出たまま である。今度はリンクするときにライブラリを明示的に指定したの に。 A: 多くのリンカは、指定したオブジェクトファイルとライブラリの リストを一回のパスで作り出し、ライブラリからその時点で未定義 の関数を含むモジュールだけを取り出す。だからオブジェクトファイ ルとライブラリのコマンドラインでの順序は重要である(オブジェ クトファイル間の順序も大事である)。普通はライブラリを最後に 探してもらいたい。(例えば、UNIXでは-lスイッチの類はすべてコマン ドラインの最後のほうに付けること。)質問13.28も参照のこと。 13.28: リンカが_endが未定義だと文句をつけてくるのはどういうときか。 A: このメッセージは、古いUnixのリンカの変なところである。_endが 未定義であるというエラーが出るのは、他にも未定義のものがあると きだけである。その他の部分を修正する。そうすれば_endのエラーは 消える。(質問13.25, 13.26も参照のこと。) 14章 浮動小数点 14.1: 浮動小数点の変数を、例えば3.1に設定すると、どうしてprintf()は 3.0999999と出力するのか。 A: たいていのコンピューターは浮動小数点数に、整数にと同じように、 2進数を用いている。2進数の1/1010(これは10進数では1/10)は無限循 環小数となる。2進表現は0.0001100110011...となる。コンパイラ の2進/10進変換ルーチン(たとえばprintf()で使われているもの)がど れくらい注意深く作られているかによって、2進数では正確に表現で きない数が(とくに精度の低い浮動小数が)代入されたり読み込まれて出 力されると(すなわち、2進から10進に変換し、10進から2進に変換し なおすと)食い違いが生じるかもしれない。質問14.6も参照のこと。 14.2: 平方根を求めようとしている。けれど、とんでもない数字が返ってく る。 A: まずを#includeしたことを確認して、次にdoubleを返す関数 を正しく宣言したことを確認する。(その他のライブラリルーチン で注意しなければいけないのは、atof()である。これはで 宣言されている。) 下の質問14.3も参照のこと。 References: CT&P Sec. 4.5 pp. 65-6. 14.3: ちょっとした三角関数の計算がしたくてを#includeしたけれ ど「_sin未定義」というコンパイルエラーが返ってきた。 A: まずは数学関数のライブラリが本当にリンクされていることを確認す ること。たとえばUnixではコンパイルあるいはリンクをする時に、コ マンドの一番最後に-lmオプションをつける必要がある。質問13.25と 13.26も参照のこと。 14.4: 浮動小数点の計算の結果が変で、しかもマシンによって違った答が返っ てくる。 A: まずは上の質問14.2を参照すること。 問題がそんなに単純でないときは、デジタルコンピュータが使ってい る浮動小数のフォーマットは、実数の演算のよくできたシミュレーショ ンを与えるものであって、決して正確なシミュレーションを与えるも のではないことをお忘れなく。桁落ちや、精度が徐々に失われていく ことや、その他の不合理なところが問題になることも多い。 浮動小数の結果が正確なんて考えないこと。とくに、浮動小数点数が 比較に使えるなんて考えないこと(いんちきな"誤差定数(fuzz factor)"を使うこともね。質問14.5参照)。 これらはC言語だけでなく他のプログラム言語でも問題となることで ある。浮動小数点の実装の多少の部分は、通常「プロセッサがやるこ とはなんでもあり」に定義されている。そうでなければ"正しい"浮動 小数点のモデルを持たないマシンではエミュレーションをするしかな く、そんなことになればコードのサイズと処理速度が犠牲になって使 い物にならない。 この記事では、浮動小数点を扱う際の落とし穴や抜け道をいちいち挙 げることはできない。よくできたプログラミングの教科書は、基礎的 な事柄を教えてくれる。以下の参考文献も参照のこと。 References: Kernighan and Plauger, _The Elements of Programming Style_ Sec. 6 pp. 115-8; Knuth, Volume 2 chapter 4; David Goldberg, "What Every Computer Scientist Should Know about Floating-Point Arithmetic". 14.5: 2つの浮動小数点の値が"十分近い"ことを判定するよい方法は。 A: 浮動小数点の値の絶対的な精度は、浮動小数点の定義により、その大 きさによって変化するので、2つの浮動小数点の値を比較する最上の 方法は精度のしきい値を使うことである。精度のしきい値は、比較し あう数の大きさに比例させる。 double a, b; ... if(a == b) /* 間違い */ ではなく、以下のコードを #include if(fabs(a - b) <= epsilon * a) 適切に選んだdegree of closenessのepsilonと共に使う(aが0でない 限り)。 References: Knuth Sec. 4.2.2 pp. 217-8. 14.6: 数を丸めるにはどうすればよいか。 A: 一番単純で容易なのは以下のようなコードを書くことである。 (int)(x + 0.5) しかしこの方法は負の数に対してはうまくいかない(負の数には (int)(x < 0 ? x - 0.5 : x + 0.5)のような式を使えばよい)。 14.7: なぜCに、べき乗が組み込み関数で用意されていないのか。 A: べき乗を組み込みの命令として持っているプロセッサが少ないからで ある。Cにはpow()が用意され、で宣言されている。ただし小 さな整数が対象なら、たいていは自分で掛け算で書いたほうがよい。 References: ANSI Sec. 4.5.5.1; ISO Sec. 7.5.5.1; H&S Sec. 17.6 p. 393. 14.8: 私が使ってるマシンのからあらかじめ定義されているはずの 定数M_PIが漏れているようだ。 A: この定数は(これはπの値を、マシンの精度一杯まで正確に、表わし ていることになっている)標準ではない。πが必要ならば、自分で #defineすること。あるいは4*atan(1.0)で計算する。 References: PCS Sec. 13 p. 237. 14.9: IEEEのNanとか、その他の特別な値かどうかのテストはどうやればよい か。 A: IEEEの浮動小数点数を高品質に実装しているシステムの多くは、これ らの値のテストを簡潔に行う機能を(例えばあらかじめ定義した定数 やisnan()マクロをまたはに)用意してい る。上のようなテストの標準化の作業が続けられている。Nanかどう かの力ずくだけれど、たいていはうまくいくテスト方法を以下の例で 示す。 #define isnan(x) ((x) != (x)) けれどIEEEフォーマットを知らないコンパイラは、このテストを最適 化の結果、削除するかもしれない。 その他の方法としては、問題にしている値をsprintf()で整形するこ とが考えられる。多くのシステムでsprintf()は"NaN"とか"Inf"といっ た文字列を生成するので、困ったときには役に立つ。 質問19.39も参照のこと。 14.11: Cで複素数を実装するよい方法は。 簡単なのは、単純な構造体とその構造体を操作する算術関数をいくつ か用意することである。質問2.7, 2.10, 14.12を参照のこと。 14.12: 以下の仕事をするコードを探している。 高速フーリエ変換(FFT's) 行列演算(乗算、逆行列など) 複素数演算 A: Ajay Shahがフリーで手に入る算術ソフトウエアの目録を管理してい る。これは定期的に投稿されているし、このFAQリストが入手できる のと同じ場所に保存されている(質問20.40参照)。質問 18.13,18.15c,18.16も参照のこと。 14.13: Turbo Cでプログラムを実行すると「浮動小数点フォーマットがリン クされていない」といってクラッシュするので困っている。 A: 小規模マシン用のコンパイラの中には、Borland社のC(Dennis RitchieのオリジナルのPDP-11用のコンパイラも)も含めて、必要がな さそうにみえるときは浮動小数点数への対応を実行しないようにでき ているものがある。特に浮動小数点数に対応しないprintf()や scanf()は%e、%f、%gを扱うコードを省略することでメモリを節約し ている。Borlandの、プログラムが浮動小数点数を使っているかどう かを判定する規則は、たまたま不十分なようである。そんなときはプ ログラマは浮動小数点数に対応したコードをロードさせるため、明示 的に浮動小数ライブラリルーチン(たとえばsqrt()。なんでもいい)を 呼ばなければならない(詳細はcomp.os.msdos.programmer FAQを参照 のこと) (訳注:Turbo Cではソースファイル内にextern void _ floatconvert(); #pragma extref _floatconvertと明示的に書くこと で、このエラーを回避することができる。) 15章 可変個数実引数リスト 15.1: printf()を呼ぶ前に必ずを必ず#includeするようにと言わ れた。なぜ? A: printf()の正しいプロトタイプがスコープに入るようにす るためにである。 コンパイラは可変個の実引数リストを取る関数には別の呼び出す仕組 みを使っているかもしれない。(そうするのは固定長の実引数リスト の関数で使っているのより効率の悪い呼び出しかたになっているから かもしれない。) よってプロトタイプは(省略記号「...」を使って実 引数リストが可変個数であることを示すことで)、可変個数実引数リ ストを使うときはいつも可変個数引数用の呼び出す仕組みを使う必要 があるとコンパイラに分かるように、スコープに入るようにしなけれ ばならない。 References: ANSI Sec. 3.3.2.2, Sec. 4.1.6; ISO Sec. 6.3.2.2, Sec. 7.1.7; Rationale Sec. 3.3.2.2, Sec. 4.1.6; H&S Sec. 9.2.4 pp. 268-9, Sec. 9.6 pp. 275-6. 15.2: どうして%fをprintf()のfloatの変数にもdoubleの変数にも使うこと ができるのか。この2つは別のデータ型ではないのか。 A: 可変個数実引数リストの可変個数の部分には「省略時の引数の格上げ (default argument promotions)」が適用される。charとshort intは intに、floatはdoubleにそれぞれ格上げされる(これはプロトタイプ がスコープ外の場合に適用される格上げと同じものである。これは" 古い書き方"として知られている。質問11.3を参照のこと)。したがっ てprintfの書式%fに見えるのはいつもdoubleの数である(同様に%cが 見るのは、%hdと同じく、intの数である)。質問12.9と12.13も参照の こと。 References: ANSI Sec. 3.3.2.2; ISO Sec. 6.3.2.2; H&S Sec. 6.3.5 p. 177, Sec. 9.4 pp. 272-3. 15.3: 解けなくてイライラしていた問題が実は、 printf("%d", n); で起きていることがわかった。ANSIの関数プロトタイプは上のような 引数の型の不一致から守ってくれると思っていたのだが。 A: 関数が可変個数の引数を取るとき、プロトタイプは可変個数の引数の 数やそのデータ型についての情報を与えない(そもそも出来ない)。よっ て通常の保護の仕組みは、可変個数の引数リストの可変個数の部分には あてはまらない。コンパイラは暗黙の変換も(通常の)データ型の不 一致も警告もできない。 質問5.2, 11.3, 12.9, 15.2も参照のこと。 15.4: 可変個数の引数を取る関数をどうやって書けばよいか。 A: にある機能を使う。 以下はmallocされた記憶領域に任意の個数の文字列を連結して格納す る関数である。 #include /* malloc, NULL, size_t用 */ #include /* va_ stuff用 */ #include /* strcatなど用 */ char *vstrcat(char *first, ...) { size_t len; char *retbuf; va_list argp; char *p; if(first == NULL) return NULL; len = strlen(first); va_start(argp, first); while((p = va_arg(argp, char *)) != NULL) len += strlen(p); va_end(argp); retbuf = malloc(len + 1); /* おしりの\0用に+1 */ if(retbuf == NULL) return NULL; /* エラー */ (void)strcpy(retbuf, first); va_start(argp, first); /* 2回目のスキャンのために再スタート */ while((p = va_arg(argp, char *)) != NULL) (void)strcat(retbuf, p); va_end(argp); return retbuf; } 使い方は以下のようになる。 char *str = vstrcat("Hello, ", "world!", (char *)NULL); 最後の引数をキャストする必要があることに注意。質問5.2と15.3参 照(使う人が、返ってきたmallocされた領域を解放する必要があるこ とにも注意)。 質問15.7も参照のこと。 References: K&R2 Sec. 7.3 p. 155, Sec. B7 p. 254; ANSI Sec. 4.8; ISO Sec. 7.8; Rationale Sec. 4.8; H&S Sec. 11.4 pp. 296-9; CT&P Sec. A.3 pp. 139-141; PCS Sec. 11 pp. 184-5, Sec. 13 p. 242. 15.5: printf()のように書式文字列と可変個数の引数をもらって、引数を printf()に渡して、仕事のほとんどprintf()にやらせるような関数を、 どうやって書けばよいのか。 A: vprintf()かvfprintf()かvsprintf()を使う。 「error: 」という文字列を先頭にエラーメッセージを出力し、改行 で終わる関数error()を以下に示す。 #include #include void error(char *fmt, ...) { va_list argp; fprintf(stderr, "error: "); va_start(argp, fmt); vfprintf(stderr, fmt, argp); va_end(argp); fprintf(stderr, "\n"); } 質問15.7も参照のこと。 References: K&R2 Sec. 8.3 p. 174, Sec. B1.2 p. 245; ANSI Secs. 4.9.6.7,4.9.6.8,4.9.6.9; ISO Secs. 7.9.6.7,7.9.6.8,7.9.6.9; H&S Sec. 15.12 pp. 379-80; PCS Sec. 11 pp. 186-7. 15.6: scanf()に類似の関数を書いて、scanf()を呼んでほとんどの仕事をさ せたい。どうすればよいか。 A: 残念ながら、vscanfの類は標準ではない。自分で書くしかない。 15.7: ANSI規格決定前に開発されたコンパイラを使っているが、 がない。どうすればいいか。 A: 古いヘッダというのがあって、これがほとんど 同じ機能を提供する。 References: H&S Sec. 11.4 pp. 296-9; CT&P Sec. A.2 pp. 134-139; PCS Sec. 11 pp. 184-5, Sec. 13 p. 250. 15.8: 関数が、実際にいくつ引数を渡されたか知る方法はあるか。 A: この情報は、移植性の高いプログラムには使えない。古いシステムに は、標準ではないnargs()関数を持つものもあるが、この関数が役に たつかどうかは必ず疑ったほうがいい。この関数は、たいてい引数の 数ではなくワード数を返す(構造体やlongの整数、浮動小数点表示の 数はたいてい、いくつかのワードとして渡される)。 可変個数引数を取る関数は、引数自身から引数の数を判断できるよう に作らなければならない。printfの類は、書式識別子(%dの類)を書式 文字列から捜して引数の数を判断する(だからこの手の関数は書式文 字列が実際の引数と一致していないとおかしな動作をする)。よく使 う別の手で、引数がすべて同じ型のときに便利なのは、番兵(0、-1ま たは適切な型にキャストしたヌルポインタ)をリストの最後に置くと いうものである(質問5.2と15.4のexeclとvstrcatの例を参照)。最後 に、各引数のデータ型が予測可能なら、可変個数の引数の数を明示的 に渡すことができる(ただし、呼び手にしてみれば、いちいち数える のは面倒だろう)。 References: PCS Sec. 11 pp. 167-8. 15.9: 今使っているコンパイラは以下のようには関数を宣言させてくれな い。 int f(...) { } すなわち固定の引数なしでは駄目なようだ。 A: 標準Cは少なくとも1つ固定の引数を必要とする。1つにはva_start() に渡せるように。質問15.10も参照のこと。 References: ANSI Sec. 3.5.4, Sec. 3.5.4.3, Sec. 4.8.1.1; ISO Sec. 6.5.4, Sec. 6.5.4.3, Sec. 7.8.1.1; H&S Sec. 9.2 p. 263. 15.10: 可変個数引数を取る関数があって、引数としてfloatを取る。なぜ、 va_arg(argp, float) ではうまくいかないのか。 A: 可変個数の実引数リストの可変個数の部分には、古い「省略時の引数 格上げ」が適応される。つまりfloatの引数は必ずdoubleに格上げさ れ(幅を広げられ)、charとshort intの引数はintに格上げされる。よっ てva_arg(argp, float);と書くのは常に誤りである。代わりに va_arg(argp, float)と常に書かなければならない。同じように、も ともとはcharやshortやintであった引数を取り出すのには va_arg(argp, int)と呼ばなければならない。質問11.3と15.2も参照 のこと。 References: ANSI Sec. 3.3.2.2; ISO Sec. 6.3.2.2; Rationale Sec. 4.8.1.2; H&S Sec. 11.4 p. 297. 15.11: va_arg()マクロを使ったが、関数へのポインタのデータ型を持つ引 数を取り出すことができない。 A: va_argマクロを使うことによるデータ型の書き換えは、関数へのポイ ンタなどの極端に複雑なデータ型を相手にすると困難となる。しかし、 関数へのポインタ型にtypedefを使えば、すべてうまくいくだろう。 質問1.21も参照のこと。 References: ANSI Sec. 4.8.1.2; ISO Sec. 7.8.1.2; Rationale Sec. 4.8.1.2. 15.12: 可変個数の引数を持ち、その引数を別の関数(その関数も可変個数の引 数を取る)に渡す関数をどうやって記述すればよいか。 A: 一般的にはできない。理想的には2番目の関数を書き直してva_listを 引数として取る(vfprintf()と同じように、上の質問15.5参照)ように する。引数を本当に引数として直接渡さなければならないときや、2 番目の関数をva_listを引数として取るように書き換える手が選べな いなら(言い換えれば、この2番目の、呼ばれる側の関数が、va_list ではなくて本当に可変個数の引数を取らなければならないなら)移植 性のある解は存在しない。(この問題は機種依存であるアセンブラ言語 まで持ち出せば解決できるだろう。以下の質問15.13も参照のこと)。 15.13: 実行時に、引数リストをつくって関数を呼び出すことができるか。 A: 動くことが保証される方法も移植性の高い方法もない。興味があるの ならこのFAQの編者に聞いてみること。彼はヘンテコリンなアイデア をいくつか持っている 引数リストの代わりに、汎用のポインタ(void *)の配列を渡すこと を考えたほうがいいかもしれない。呼ばれた関数は配列を、main()が argvを処理するのと同じように、一つ一つ処理することができ(すぐ わかるように、これは呼ばれる側の関数全体をこちらで管理している 場合に限られる)。 (質問19.36も参照のこと。)  0, unseen,, *** EOOH *** Newsgroups: fj.archives.answers,fj.lang.c Path: newsmaster.tuis.ac.jp!news.chiba-u.ac.jp!chiba-ns!sakunami!kjnews!ngwyrit!nsvyrit!news2.sphere.ad.jp!nspixp!spinnews!spin-hsd0-tky!yhqfm!xeroc!leia!133.140.40.2!kitano From: kitano@crd.yokogawa.co.jp (Kinichi - Kinchan - Kitano) Subject: comp.lang.c Answers to Frequently Asked Questions (FAQ List) in Japanese[4/4] Sender: news@leia.pa.yokogawa.co.jp (Leia news server) Message-ID: Supersedes: Date: Mon, 14 Jul 1997 17:31:37 GMT Reply-To: kitano@crd.yokogawa.co.jp Organization: Yokogawa Electric Corporation, Tokyo, Japan. Followup-To: fj.lang.c Lines: 2097 Xref: newsmaster.tuis.ac.jp fj.archives.answers:1294 fj.lang.c:5594 Archive-name: c-faq-j/part4 Last-modified: 14 July. 1997 ========================= C FAQ 日本語訳[4/4] ========================= 16章 奇妙な問題 16.2a: わけがわからない文法エラーが出て困っている。 プログラムの大部分がコンパイルされないままのように見える。 A: コメントを閉じ忘れてないかとか、 #if/#ifdef/#ifndef/#else/#endif指令が対応するものが抜けてない かをチェックする。ヘッダファイルもチェックすることを忘れてはい けない。(質問2.18, 10.9, 11.29も参照のこと。) 16.2b: どうしてprocedure呼出しがうまくいかないのか。コンパイラはそこ のところを飛ばしているように見える。 A: そのコードは以下のようなものか。 myprocedure; Cには関数しかない。その上、関数呼出しには括弧でくくられた引数 リストが、引数がなくても必要である。 myprocedure(); と書く。 16.3: プログラムが走る前にクラッシュする(デバッガを使って一行づつ ステップ実行で追うとmain()の最初の行を実行する前に死んでしまう)。 A: このプログラムは、非常に大きな(1キロバイトかそれ以上の)ローカ ルな配列を持っているに違いない。多くのシステムでスタックの大きさ は固定だし、(Unixのように)自動的にスタックを動的に割り付けるシス テムでもスタックのサイズが突然巨大になると、混乱する場合がある。 大きな配列を使うときはstaticと宣言したほうがいい(もちろん再帰 呼び出しするたびに新しい配列が必要な場合は除く。こういう場合は malloc()を使って動的に割り付ける。質問1.31を参照のこと)。 (質問11.12, 16.4, 16.5, 18.4も参照のこと) 16.4: このプログラムは正しく動いているように見えるけれど、main()から 抜けるところで、つまりmain()の最後の文の後で、クラッシュする。 なにがこんなことを引き起こすのか。 A: main()のプロトタイプ宣言のおかしくないかとか(質問2.18, 10.9参 照)、setbuf()とかsetvbuf()にローカルのバッファを渡してないかと か、atexit()に登録した後片付けの関数に間違いはないかを調べる。 質問7.5や11.16も参照のこと。 References: CT&P Sec. 5.3 pp. 72-3. 16.5: このプログラム、あるマシンではうまく走るのに、別のマシンだと変 な結果を返す。もっと変なことに、デバッグ用の出力を付けたり外し たりすると症状が違ってくる。 A: おかしくなる可能性のあるものはたくさんある。以下に可能性の高い ものをいくつか示す。 初期化してないローカル変数(質問7.1参照) 整数のオーバーフロー、特に16ビットマシンで、とりわけ a * b / cのような計算の中間結果の桁あふれ(質問3.14も参照 のこと) 未定義の評価の順序(質問3.1から3.4も参照のこと) 外部関数の宣言のし忘れ、特にint以外や"狭い"型を返す関 数や可変個数引数の関数で(質問1.25, 14.2, 15.1参照) ヌルポインタの間接参照(5章参照) 誤ったmalloc/freeの使い方。mallocしたメモリが全部0になっ てるとかfreeした領域がまだ使えると思い込んだり、同じも のを2回freeすること(質問7.20や7.19を参照のこと) ポインタの問題全般(質問16.8も参照のこと) printf()の書式と実引数の不一致、特にlongの整数を%dを使っ て出力しようとする(質問12.9も参照) unsigned intで数えることができる大きさより大きな領域を 割り付けようとすること。とくに、メモリの量が限られてい るマシンで(質問7.16と19.23も参照のこと) 配列の境界の問題。特に小さな一時的バッファで、おそらく sprintf()を使って文字列を構築するのに(質問7.1と 12.21も参照) typedefと実際のデータ型の対応の誤った思い込み。とりわ けsize_t 浮動小数点の問題(質問14.1と14.4を参照) 対象となる具体的なシステムで コードの生成されかたを決 め付けて、こういう使いかたを考え付くとは俺って頭がいい なと思っていることはなんでも 関数プロトタイプを適切に使うことで、これらの問題のかなりの部分 を捕まえることができる。lintを使えばもっと多くの問題を捕まえる ことができる。質問16.3, 16.4, 18.4も参照のこと。 16.6: なぜ以下のコードはクラッシュするのか。 char *p = "hello, world!"; p[0] = 'H'; A: 文字列定数は、(ようするに)配列の初期化指定子として使われるとき を除いては、必ずしも変更可能ではない。以下の方法を試すこと。 char a[] = "hello, world!"; 質問1.32も参照のこと。 References: ANSI Sec. 3.1.4; ISO Sec. 6.1.4; H&S Sec. 2.7.4 pp. 31-2. 16.8: 「セグメンテーション違反(Segmentation violation)」とか「バスエ ラー(Bus error)」は何を意味するのか。 A: これらの症状は一般に、アクセスすべきでない記憶領域にプログラム がアクセスしようとしたことを意味している。きっとスタックが壊れ たかポインタの使い方が適切でなかったからに違いない。考えられる 原因としては以下のものが考えられる。 ローカル("自動の"、スタックに割り付けられる)変数のオーバーフロー、 ヌルポインタの誤った使い方(質問5.2, 5.20も参照)。初期化されて いないポインタ/境界の合ってないポインタ/その他適切でない割り付 けのされかたのポインタ(質問7.1と7.2参照)。mallocした領域が壊れ た(質問7.19参照)。関数引数の不一致、特にポインタが絡んだ場合、 2つ考えられるのはscanf()(質問12.12参照)とfprintf()(最初のFILE *の引数を渡されていることを確認すること)。 質問16.3と16.4も参照のこと。 17章 スタイル 17.1: Cのコードの書き方で最良のものは。 A: K&Rである。しばしば真似られる良い例を載せているだけでなく、避 けるべき悪い例には、立派な理由も載せている。 括弧の位置はたいして重要ではない。ただし人は括弧の位置 に強い信念を持っている。我々は世にあるいくつかの例から 選んだだけである。自分に向いている書き方を取り入れて、 首尾一貫して使い続けること。 大事なのはコードの配置が"完全な"ことよりも、選んだコードの配置 を首尾一貫して使うことである(それ自身、回りのコードと、あるい は共通部と)。もし君がコーディングを行うときの環境が(たとえば職 場の習慣や会社の方針)コーディングスタイルを示していなくて、自 分自身の書き方を作り出す気もないのなら、単にK&Rをまねればよい。 (さまざまな字下げや括弧の位置の持つ利点と欠点に関する調査は、 徹底的かつ細かく行うことができる。しかしここでは議論を繰り返さ ない。Indian Hillスタイルガイドを参照のこと)。 定義しにくい"よいスタイル"が持つ特質には、コードの配置の詳細以 上のものがある。もっと大事なコードの質の問題を抜きにして体裁を 整えるのに時間を割かないこと。 質問10.6も参照のこと。 References: K&R1 Sec. 1.2 p. 10; K&R2 Sec. 1.2 p. 10. 17.3: ほら、このわざを見て。 if(!strcmp(s1, s2)) これはよい書き方? A: とくによい書き方ということはない。ただし人気のある書き方ではあ る。このテストは、二つの文字列が同じときに成功する。しかし「!」 ("...でない")を使っていることで等しくないことをテストしている ように思える。 より優れた選択肢としては以下のようなマクロを使うことがある。 #define Streq(s1, s2) (strcmp((s1), (s2)) == 0) コーディングスタイルに関する考え方は、宗教に関する考え方と同じ で、議論に終りがない。よい書き方は価値ある目標であるし、たいて いは見ればよいか悪いかわかるが、文章にすることはできない。質問 17.10も参照のこと。 17.4: どうしてif(x == 0)と書く代わりに、if(0 == x)と書く人がいるのか。 A: これはよく以下のように書いてしまうことを防ぐためのコツである。 if(x = 0) 定数を==の前に持ってくる習慣を付けておけば誤って、 if(0 = x) と打ち込むとコンパイラが文句を付ける どうやら2回=を打ち込むことを覚えるよりは、テストのオペランドの 順をひっくり返すことを覚えることのほうがやさしい。 (訳注:本のほうには、これは特によい書き方というわけではないと書 いてある。実際これでは両辺が変数の場合のif(a = b)という誤りを 捉えることはできない。こんな技を覚える暇があれば、lintの使い方 を覚えること。) References: H&S Sec. 7.6.5 pp. 209-10. 17.5: printf()を呼び出しているところすべてに、いちいち(void)のキャス トを付けているコードを見かけた。なぜこんなことをするのか。 A: printf()は値を返す。ただしprintf()の戻り値をわざわざ確かめるプ ログラムは数少ない。コンパイラによっては(lintも)戻り値を読まず に捨てると警告を出すので、(void)に明示的にキャストすることは 「はい、この呼び出しの戻り値を無視することにしました。でも引き 続きその他の関数で戻り値を(うっかり)無視したら注意してください。」 というコンパイラへの依頼の表現である。strcpy()やstrcat()の呼び 出しにもvoidのキャストはよく行われる。これらの戻り値が、とんで もない値になることはないから。 References: K&R2 Sec. A6.7 p. 199; Rationale Sec. 3.3.4; H&S Sec. 6.2.9 p. 172, Sec. 7.13 pp. 229-30. 17.8: 「ハンガリー記法(Hungarian Notation)」とは。使う価値があるか。 A: ハンガリー記法は名前の付けかたの取り決めで、Charles Simonyiが 発明した。これは変数の型を(おそらく、その使い道も)変数の名前 に符号化する。一部のグループでは愛され、その他の人達からは容赦 なく酷評されている。主な利点は、データ型や意図する使い方が名前 を見ればわかるということである。主な欠点は、データ型は変数の名 前に付けて運んでまわるような大事な情報では必ずしもないことであ る。 References: Simonyi and Heller, "The Hungarian Revolution" . 17.9: Indian Hillスタイルガイドやそのほかのコーディング規約をどこか ら手に入れることができるのか。 A: さまざまな資料がanonymous ftpにより入手可能である。 場所 ファイルあるいはディレクトリ cs.washington.edu pub/cstyle.tar.Z (インディアンヒルガイドの改訂版) ftp.cs.toronto.edu doc/programming (Henry Spencerの 「Cプログラマの十戎」も含む) ftp.cs.umd.edu pub/style-guide 『プログラム書法』、『Plum Hall Programming Guidelines』、『C Style: Standards and Guidelines』のような本も参考にしたほうが いいかもしれない。参考文献参照のこと。(『C Style: Standards and Guidelines』は実際はスタイルガイドではない。これはスタイル ガイドを選んだり作り上げる際の指針を集めたものである。) 質問18.9も参照のこと。 17.10: gotoは悪で絶対に使ってはならないという人がいる。ちょっとこれは 行き過ぎではないか。 A: プログラミングの作法は文章作法と同じで、ちょっとした技能であっ てガチガチの規則で明文化することはできない。ただし書き方につい ての議論はもっぱらそういう規則を中心に回るようである。 goto文については、gotoを好き勝手に使うとすぐにスパゲッティのよ うにもつれた保守不能なコードになると昔からいわれている。しかし、 石頭にgoto文を使うことを禁止してもすぐに美しいプログラミングに 結び付くとは限らない。体系立てて考えないプログラマならgoto文を まったく使わなくても同じように複雑怪奇にもつれたコードを書いて しまう(たぶん代わりに妙に入れ子になったループやブール値の制御 変数を使って)。 プログラムの書き方に関するたいていの意見や"規則"は規則としてよ りは指針として考えたほうがうまくいく。プログラマが、この指針 で何を成し遂げたいのか理解すればもっとうまくいく。ある種の構文 を無闇に敬遠したり、理解することなく規則に従うことは、規則を使 えば避けられることになっているのと同じくらい多くの問題を引き起 こす可能性がある。 さらに、プログラムの書き方に関する見解は所詮見解にすぎない。" 書き方論争(style wars)"に引きずり込まれても、たいてい何も産み 出さない。一部の論点(質問9.2, 5.3, 5.9, 10.7で挙げたような)に ついてはお互い相手の意見を認めたり、意見が違うことを認めたり、 議論を打ち切ったりすることはないように見える。 18章 道具と資源 18.1: こんなのを捜している。 A: 以下の名前を持つプログラムを捜すこと (質問18.16も参照のこと)。 相互参照を生成するツール cflow または cxref, calls, cscope, xscope, ixfw 整形ツール/プリティプリンタ cb または indent, GNU indent, vgrind 改訂履歴管理、構成管理ツール RCS または SCCS Cソースを解読不能にするツール、 obfus または shroud, opqcp (シュレッダー) makeコマンドの依存関係を makedepend, あるいはcc -Mまたは 作り出すツール cpp -M コードのメトリックスを求める ccount, Metre, lcount, または ツール csize, もしくは URL http://www.qucis.queensu.ca:1999/Software- Engineering/Cmetrics.htmlを参照 McCabe and Associatesが販売している 製品もある。 行数測定ツール 大まかでよければUnixに標準で付いてくる wcで、またgrep -c ";"を使えばもう少し正確な 結果が得られる。 Cの宣言の補助ツール 質問1.21参照 (cdecl) プロトタイプ生成ツール 質問11.31参照 mallocの問題を追跡するツール 質問18.2参照 "選択的"プリプロセッサ 質問10.18参照 他のプログラム言語からの 質問11.31と20.26参照 変換ツール Cプログラム検定器(lint) 質問18.7参照 Cコンパイラそのもの 質問18.3参照 (上のツールの一覧表は完璧を求めたものではない。上の表に載って いないツールを知っていれば、この表の管理者に連絡を取って欲しい。) 別の編者によるツールの一覧表、各ツールに関する議論がUsenetのニュー スグループcomp.compilersやcomp.software-engに投稿されている。 質問18.16と18.3も参照のこと。 18.2: malloc()の厄介な問題はどうやって追跡すればよいか。 A: malloc()にまつわる問題の追跡を支援するデバッグ用パッケージは数 多くある。人気があるのはConor P. Cahill作の"dbmalloc"、これは comp.sources.miscに1992年に投稿された。volume 32に入っている。 その外には"leak"というのがcomp.sources.unixのvolume 27アーカイ ブから入手可能である。JMalloc.cとJMalloc.hは"Snippets"所蔵であ る。MEMDEBUGはftp.crpht.luのpub/sources/memdebugにある。質問 18.16も参照のこと。 商品版のデバッグ用ツールも多数存在する。これらはmalloc()に関係 した問題や、その他のなかなか解けない問題を追い詰めるのに非常に 役にたつ。 Nu-Mega Technologies社のBounds-Checker for DOS, P.O. Box 7780, Nashua, NH 03060-7780, USA, 603-889-2386. Centerline Software社の(かってはSaberという社名だった) CodeCenter(以前はSaber-Cという名前だった), 10 Fawcett Street, Cambridge, MA 02138-1110, USA, 617-498-3000. ParaSoft Corporation社のInsight, 2500 E. Foothill Blvd., Pasadena, CA 91107, USA, 818-792-9941, insight@parasoft.com . Pure Software社のPurify, 1309 S. Mary Ave., Sunnyvale, CA 94087, USA, 800-224-7873, info-home@pure.com . PLATINUM Technology社のFinal Exam Memory Advisor (以前 はAIB Software社のSentinelという名前だった), 1815 South Meyers Rd., Oakbrook Terrace, IL 60181, USA, 630-620-5000, 800-442-6861, info@platinum.com, www.platinum.com . AIB Software社のSENTINEL, 46030 Manekin Plaza, Dulles, VA 20166, USA, 703-430-9247, 800-296-3000, info@aib.com . The Kernel Group社のZeroFault 1250 Capital of Texas Highway South, Building Three, Suite 601, Austin, TX 78746, 512-433-3333, http://www.tkg.com, zf@tkg.com . 18.3: フリーまたは安く手に入るCコンパイラにはどのようなものがあるか。 A: 人気があって高品質なコンパイラとしてはFSFのGNU Cコンパイラすな わちgccが挙げられる。これはprep.ai.mit.eduのディレクトリ pub/gnuもしくは幾つか存在するFSFのアーカイブサイトから入手可能 である。MS-DOSへの移植であるdjgppもある。これはftp.delorie.com のpub/djgppから入手可能である。Simtelのミラーサイト(例: ftp.simtel.netのpub/simtelnet/gnu/djgppやftp.coast.netの SimTel/vendors/djgpp)からも入手可能である。 PCCというシェアウエアのコンパイラがある。PCC12C.ZIPという名 前で入手可能である。 Mix SoftwareのPower Cは非常に安価なMS-DOS用のコンパイラである。 連絡先は、1132 Commerce Drive, Richardson, TX 75801, USA, 214-783-6001. 最近開発されたコンパイラとしてはその他にlccがある。これは ftp.cs.princeton.eduのpub/lccからanother ftpで入手可能である。 MS-DOS用のシェアウエアのCコンパイラがftp.hitech.com.auの /hitech/pacificから入手可能である。非商用ならレジストレーショ ンしなくてもよい。 comp.compilersに関連したアーカイブは入手可能な(多くのプログラ ム言語の)コンパイラ、インタープリター、文法等についての大量の 情報を収集している。John R Levineが管理する(FAQリストも含む) comp.compilersのアーカイブはiecc.comにある。入手可能なコンパイ ラと関連する資源の一覧でMark Hopkins, Steven Robenalt, David Muir Sharnoffが保守しているものが、ftp.idiom.comの pub/compilers-list/から入手可能である。(rtfm.mit.eduや ftp.uu.netにあるnews.answersのアーカイブのcomp.compilersのディ レクトリも参照のこと。質問20.40参照。) 質問18.16も参照のこと。 18.4: プログラムを打ち込んだところだけれど、このプログラムは奇妙な動 きをする。どこか変なところがあるか。 A: まずはlintを(-a、-c、-h、-p や他のオプションをつけて)動かして みること。多くのCコンパイラは、実際にはコンパイラの仕事の半分 しかしていない。つまりソースコードの問題点の診断を、コードの生 成の邪魔にならない限りしないことになっている。 質問16.5, 16.7, 16.8を参照のこと。 References: Ian Darwin, _Checking C Programs with lint_ . 18.5: mallocを呼ぶたびにlintが出す「警告:ポインタの整合に問題の可 能性あり(warning:possible pointer alignment problem)」という メッセージを消す方法はあるか。 A: 問題は古いlintが、mallocが「どんな型のオブジェクトもうまく収ま るように整合した領域へのポインタを返す」ということを知らないし、 教える手段もないということである。#ifdef lintのなかでmalloc() の定義まがいを#defineしてこのメッセージを消すことは可能である が、そんなことをすれば本当に間違った使用法に対しても大事なメッ セージが出なくなってしまう。grep -vを使って勝手に無視するほう がやさしい。(lintのメッセージをあまり多く無視する習慣を付けな いこと。さもないと、ある日大事なメッセージを見落とすことになる。) 18.7: ANSI互換のlintはどこで手に入るか。 A: PC-LintとFlexlintという製品が(シュレッダーにかけたようなソー スで提供される。これはたいていのシステムでコンパイル可能である)、 Gimpel Software 3207 Hogarth Lane Collegeville, PA 19426 USA (+1) 610 584 4261 gimpel@netaxs.com から入手可能である。SVR4のlintはANSI互換で、UNIX Support Labs あるいは SYSTEM Vの再販業者から(他のC言語ツールと共に)購入可能 である。 もう一つのANSI互換のlintとしてはLCLintがある(上流工程の形式的 仕様の検査も可能である)。これは larch.lcs.mit.edu://pub/Larch/lclint/からanother ftp可能である。 lintがなくても、最近のコンパイラは、よくできたlintと同じくらい の多くの種類の診断をしようとする。(gcc -Wall -pedanticを進める 人が多い。) 18.8: ANSIの関数プロトタイプが用意されたことでlintは時代遅れになった のか。 A: まさか。まずプロトタイプは実際に存在して、しかも正しいときに だけ目的通りに機能する。うっかり間違ったプロトタイプは有害無益 だ。次にlintは複数のソースファイルに渡って一貫性を検査するし、 関数だけでなくデータの宣言も検査する。lintのように独立したプロ グラムになっていると、特定の実装に依存した余計な機能や余計な拡 張のついたコンパイラよりも、互換性が高く移植性の高いコーディン グの習慣を強制することにに気を配っているだろう。 ファイルをまたがった一貫性の検査をするのにlintの代わりに、どう しても関数プロトタイプを使いたいならば、ヘッダファイルでプロト タイプを正しく設定していることを確かめること。質問1.7と10.6を 参照のこと。 18.9: Cの独習書やその他の知的資源をインターネットで入手することはできるか。 A: いくつか存在する。 Christopher Sawtellの『Cプログラマへの覚え書き:Notes for C programmers』svr-ftp.eng.cam.ac.ukのmisc/sawtell_C.sharや garbo.uwasa.fiの/pc/c-lang/c-lesson.zipから入手可能である。 Tim Loveの『C for Programmers』svr-ftp.eng.cam.ac.ukのmiscディ レクトリから入手可。HTML版は http://club.eng.cam.ac.uk/help/tpl/languages/C/teaching_C/teaching_C.html にある。 Coronado EnterprisesのC入門はSimtelのミラーサイトの pub/msdos/c/にある。 Rick Roweの書いた独習書はftp.netcom.comの pub/rowe/tutorde.zipやftp.wustl.edu asの pub/MSDOS_UPLOADS/programming/c_language/ctutorde.zipから入手 可能である。 どうやらWebを使ったチュートリアルが http://www.strath.ac.uk/CC/Courses/CCourse/CCourse.htmlにある ようだ。 Martin BrownはC course materialをhttp://www- isis.ecs.soton.ac.uk/computing/c/Welcome.htmlで提供している。 Unixマシンの中には、シェルのプロンプトから「learn c」 と打ち込むとチュートリアルが始まるものもある。 最後に、本FAQの原著者はC言語のクラスを教えており、その講義ノートを ウエブで公開し始めた。これは http://www.eskimo.com/~scs/cclass/cclass.html から入手可能である。 [声明(Disclaimer): これらのチュートリアルを吟味したわけではな い。これらの中の少なくとも一つには多数の間違いがあると聞いてい る。また、この種の情報は急速に時代遅れになる。上に挙げたアドレ スは、このFAQを読んで試すころには、無効になっているかもしれな い。 これらのチュートリアルのいくつかと、Cに関する大量の情報が、 http://www.lysator.liu.se/c/index.htmlからWEBを通してアクセス 可能である。 Vinit CarpenterがCやC++を学ぶ際に使用可能な資源の一覧を管理し ている。これはcomp.lang.cやcomp.lang.c++に投稿され、このFAQが アーカイブされているのと同じサイトにアーカイブされている(質問 20.40参照)。http://vinny.csd.mu.edu/でWebを使って閲覧可能であ る。 以下の質問18.10も参照のこと。 18.10: C言語を学ぶのによい本は? A: Cに関する本は多すぎて、ここで名前を全部挙げることは不可能であ る。全部評価することも無理である。多くの人が最初の参考書が最上 のものであったと信じている。KernighanとRitchieによる『Cプログ ラミング言語』("K&R"今は第二版)のことである。プログラミングの 最初の教科書としてK&Rが向いているかどうかは意見が分かれる。実 際多くの人がK&Rを使って学んだし、しっかりと身に付けた。しかし ながらプログミング一般の基礎知識があまりない人の最初の教科書と してはちょっと冷たく客観的であると感じる人もいる。注釈と正誤表 がいくつかネットから入手可能である。 http://www.csd.uwo.ca/~jamie/.Refs/.Footnotes/C-annotes.htmlまたは http://www.eskimo.com/~scs/cclass/cclass.htmlや http://www.lysator.liu.se/c/c-errata.html#main 。 素晴らしい参照用マニュアルとしてはSamuel P. HarbisonとGuy L. Steeleによる『新・詳説C言語H&Sリファレンス H&Sリファレンス (C: A Reference Manual)』がある。これは第四版まで出ている(訳注: 日本語訳は3版に対応したものまで出ている)。 一からCを学ぶのには向いていないが、このFAQは書籍の形態で出版さ れている。参考文献を参照のこと。 Mitch WrightがCとUnixの注釈付きの文献一覧を管理している。これ はftp.rahul.net のディレクトリpub/mitch/YABLからanonymous ftp が可能である。 The Association of C and C++ Users (ACCU)がCとC++の教科書の comprehensiveなbibliographic reviewsをmaintainしている。 場所はhttp://bach.cis.temple.edu/accu/bookcaseと http://www.accu.org/accu 。 FAQリストの編者は、この質問のこれまでの回答を集めたものを持っ ている。希望があれば送る。上の質問18.9も参照のこと。 18.13: 標準のCライブラリのソースはどこにあるのか。 A: 1つは(パブリックドメインではないけれど)、P.J. Plaugerの『標準C ライブラリ(The Standard C Library)』にある(参考文献参照)。Cラ イブラリの全体または一部はNetBSDやGNUプロジェクト(Linuxプロジェ クトも含む)の一部として書かれ容易に入手可能である。質問18.16を 参照のこと。 18.14: 式を構文解析して、式の演算結果を得るコードが欲しい。 A: 2つのパッケージが入手可能である。1つ目は「defunc」というパッ ケージ。comp.source.miscに1993年12月(V41 i32,33)に、また alt.sourcesに1994年1月にポストされ、sunsite.unc.eduのディレク トリpub/packages/development/libraries/defunc-1.3.tar.Zから入 手可能である。もう1つは「parse」でlamont.ldgo.columbia.eduか ら入手可能である。その他の選択肢としてはS言語のインタープリター がある。これはamy.tch.harvard.eduのpub/slangからanonymous ftp で入手可能である。またシェアウエアーのCmm(C-minus-minusまたはC から難しいところをマイナスしたもの)もある。質問18.16も参照のこ と。 構文解析/式の評価のコードは『Software Solutions in C』にも載っ ている(12章, pp. 235-55)。 18.15: C言語の文法をBNFやYACCで書いたものは、どこで手に入るか。 A: 最も確かな文法は当然ANSI規格の中にある。質問11.2を参照のこと。 その他にJim Roskindによるものがics.uci.eduのディレクトリ pub/*grammar*に置かれている。ANSI文法に肉付けして動くようにし たものが(Jeff Leeによる) uunet(Q17.12を参照)の usenet/net.sources/ansi.c.grammar.Zに、対応する字句解析ツール とともに置かれている。FSFのGNU Cコンパイラは文法を含んでいる。 K&Rの第2版の付録にも付いている。 comp.compilersのアーカイブは文法についてもっとたくさんの情報を 保存している。質問18.3を参照のこと。 References: K&R1 Sec. A18 pp. 214-219; K&R2 Sec. A13 pp. 234- 239; ANSI Sec. A.2; ISO Sec. B.2; H&S pp. 423-435 Appendix B. 18.15a: Cコンパイラの妥当性を検査する一連のテストをだれか持っていない か。 A: Plum Hall(以前はCardiff, NJ、現在はHawaii)が売っている。other その他のパッケージとしてはRonald GuilmetteのRoadTest(tm) Compiler Test Suites (ftp to netcom.com, pub/rfg/roadtest/announce.txt for information)やNullstoneのAutomated Compiler Performance Analysis Tool (http://www.nullstone.com参照)がある。FSFの GNU C(gcc)の配布にはc-torture-test.tar.Zというパッケージが含ま れている。これはコンパイラのよくある問題点をチェックするもので ある。Kahanのパラノイア・テストはnetlib.att.comのディレクト リnetlib/paranoiaから手に入れることができる。これはCの浮動小数 点計算の実装に厳しいテストを行う。 18.15c: 有用なコードの断片やコード例を集めたものはどこから入手可能か。 A: Bob Stoutの"SNIPPETS"コレクションはftp.brokersys.comのディレク トリpub/snippetsかウエブでhttp://www.brokersys.com/snippets/か ら入手可能である。 Lars Wirzeniusの"publib"ライブラリがftp.funet.fiの pub/languages/C/Publib/から入手可能である。 質問14.12, 18.9, 18.13, 18.16も参照のこと。 18.16: どこから、これらのパブリックドメインのプログラムを手に入れるこ とができるのか。 A: 入手可能なプログラムの数や、おおっぴらにアクセス可能なアーカイ ブサイトの数や、サイトにアクセスしようとする人の数が増えるにつ れ、この質問への解答はやさしくなる一方で難しくもなっている。 社会の役に立つことを目的とした大きなアーカイブサイトがたくさん ある。たとえばftp.uu.netとかarchive.umich.edu、oak.oakland.edu、 sumex-aim.stanford.edu、wuarchive.wustl.eduなどである。これら のサイトには莫大な量のソフトウエアやその他の情報を保存していて、 すべて自由に入手可能である。FSFのGNUプロジェクトの中心となる配 布のサイトはprep.ai.mit.eduである。これらの有名なサイトは概し て混んでいてつながりにくい。そこで"ミラー(mirror)"サイトがたく さんあって負荷の分散に努めている。 インターネットにアクセスできる人には、アーカイブサイトからファ イルを取り出す昔からの方法はanonymous ftpである。ftpが使えない 人には、メールによるftpのサービスを行っているサーバーがいくつ かある。ますますワールドワイドウエブ(WWW)が配布の公表や索引だ けでなく大きなデータファイルの転送にまで使われるようになってき ている。きっともっと新しいアクセス方法が発表されていることだろ う。 ここまでは答えやすい部分である。難しいのは詳細である。このこの 記事はプログラムを保存しているサイトのすべてや、サイトにアクセ スする方法を突き止めたり、挙げることはできない。そもそもネット にアクセスできるなら、動いているサイトや便利なアクセス方法につ いての、このFAQに載ってるのより最新の情報にアクセスすることが できる。 この質問のその他のやさしそうで本当は難しい面は当然どうやってど のサイトが探しているものを保存しているかを見つけることである。 この分野には途方もない労力がつぎ込まれていて、毎日のように新し い索引つけのサービスが現れている。こういうサービスの最初のもの の一つが「アーチー(archie)」である。ネット上で入手可能なプログ ラムや資源で名前が分かっているものなら、アーチーサーバーがどの anonymous ftpサーバーにあるかたいてい教えてくれる。アーチーの コマンドが手元のシステムに載ってるかもしれないし、なかったとし たら「help」と本文に書いてarchie@archie.cs.mcgill.caへメールを 送ればいい。 Usenetへアクセスできるのなら、comp.sources.unixや comp.sources.miscといったグループへの定期的なポストを参照する こと。そこには保存の方針や取り出しかたが記述してある。これらの グループに投稿されたものは ftp://gatekeeper.dec.com/pub/usenet/comp.sources.unix/や ftp://ftp.uu.net/usenet/comp.sources.unix/といったところにアー カイブされている。ニュースグループcomp.archivesの記事には様々 な情報の、anonymous ftpによる入手方法の紹介が含まれている。最 後にcomp.souces.wantedが、たいていはソースのありかをたずねるの に最もふさわしいニュースグループである。その場合でもポストする 前にFAQのリストである「How to find sources(どうやってソースを 見つかればよいか)」をチェックすること。 質問14.12も参照のこと。 19章 システム依存 19.1: Returnキーが押されることを待つことなしにキーボードから1文字読 むのはどうすればいいのか。入力を受け付けてる間、文字が画面に エコーされないようにするにはどうすればいいのか。 A: やれやれ、Cには上のようなことを行う標準的な方法も移植性の 高い方法もない。画面とかキーボードとはどういうものを指すのかさ え規格の中では出てこない。規格は簡単な、文字の入出力ストリームし か扱っていない。 どこかの層で、キーボードからの入力は通常いったん集められ一度に 1行ずつ、入力を要求しているプログラムに渡される。これによって オペレーティングシステムが入力行の編集機能(バックスペース/デリー ト/ruboutなど)を一貫した方法で、しかもプログラムごとに組み込む 必要なく用意するすることが可能となる。ユーザーが納得して改行キー (あるいは類似のキー)を叩いて初めて入力行は呼び出した側のプログ ラムから使えるようになる。プログラムが一度に一文字(getchar()の ようなルーチンを使って)入力を読んでいるように見えても、最初の 呼び出しはユーザーが行全体を入力するまでブロックされる。行全体 が読み込まれた時点では、多くの文字が利用可能になり、矢継ぎ早の 文字の取り出し要求も(例:getchar()の呼び出し)満たされる。 プログラムに文字が到着するとすぐに読ませたいなら、処理の流れは 入力ストリームのどこで入力を1行分集めているかと、1行分集めるの をやめるにはどうすればよいかに依存する。システムによっては(例: MS-DOS, あるモードでのVMS)、プログラムは異なった、または修正さ れたOSレベルの入力コールの一組を使うことで、一度に1行分丸々処 理することを迂回することができる。その他のシステムでは(例:Unix、 別のモードでのVMS)、オペレーティングシステムのシリアル入力を担 当している部分("ターミナルドライバー"と呼ばれることが多い)を、 一度に一行分丸々は処理しないモードに移す。移った後は、通常の入 力ルーチン(例:read(), getchar()など)は文字を即座に返す。いくつ かのシステムでは(特に古い、バッチ処理中心の汎用機では)入力の処 理は周辺のプロセッサで行われ、一度に一行丸々処理させるかどう かしか命令することはできない。 よって、一度に1文字を入力することが必要なら(あるいはキーボー ドからの入力時の画面へのエコーをとめる必要があるなら。これは類 似の問題である)、使っているシステムに固有の技を使う必要がある。 comp.lang.cはCで手におえる話題が中心なので、上のような質問は comp.unix.questionsやcomp.os.msdos.programmerといった特定のシ ステムのニュースグループで聞くか、そういうグループのFAQを見る ほうが普通はよい解答が返って来る。システムが違っても解答は共通 であることが多いが、システム特有の質問に答えるときは、君のシス テムにあてはまる解答が他の人全部にあてはまる解答でないかもしれ ないことに注意すること。 しかしながら、これらの質問はあまりにも何度も聞かれるので、以下 によくある状況用の簡潔な解答をつける。 cursesの中には関数cbreak()を用意しているものもある。これが望み の機能を果たす物である。画面に表示することなくパスワードを読み たいというのであればgetpass()を試せばよい。Unixでは、ターミナ ルドライバーのモードをいじるのにioctlを使えばよい(「古典的な」 システムではCBREAKやRAW、SYSTEM VやPOSIX準拠のシステムでは ICANON、c_cc[VMIN]、c_cc[VTIME]、すべてのバージョンでECHO)。最 後の最後の方法としてはsystem()とsttyコマンドを使う。(もっと詳 しく知りたければ、古典的なバージョンではとtty(4)、 System Vではとtermio(4)、POSIXではと termios(4)を参照せよ。) MS-DOSでは、getch()やgetche()を使うか、 対応するBIOSの割り込みを使う。VMSでは、スクリーン管理機能 (SMGZ$)やcurses、一度に一文字欲しいのであれば低レベルの$QIOを IO$_READVBLKを(たぶんIO$M_NOECHOも必要だろう)付けて試すこと (VMSの端末ドライバーで一度に一文字、すなわち"素通し"モードを設 定することも可能である)。そのほかのシステムでは、自分でやるし かない。 (ちなみに、setbuf()やsetvbuf()を使って標準入出力をバッファしな いようにするだけでは一般には1度に1文字の入力を実現することが できないことに注意。) 移植性の高いプログラムを書くつもりなら、よい取り組みかたは(1) ターミナルドライバーや入力システムを(必要なら)一度に1文字ずつ のモードに移す関数、(2)文字を取得する関数、(3)関数の処理が終わっ たらターミナルドライバーを最初の状態に戻す関数の3つの関数を一 組にしたものを自分で定義ことである。(理想をいえば、このような関 数の集合は、いつかC規格の一部になるかもしれない。) このFAQの拡 張版には(質問20.40参照)、いくつかの普及しているシステム用の例 も載っている。 質問19.2も参照のこと。 References: PCS Sec. 10 pp. 128-9, Sec. 10.1 pp. 130-1; POSIX Sec. 7. 19.2: 読み込むことができる文字が残っているかどうか(できるなら後いく つ残っているか)知ることができるか。できないとしたら、文字が入っ てこないときに処理が止まってしまわないような読み込みはどうやれ ばよいか。 A: これも、まったくオペレーティングシステムに依存した問題である。 cursesの中には、そのような目的のために関数nodelay()を用意して いるものもある。システムによっては、「ブロックしないI/O」や 「select」とか「poll」という名のシステムコール、FIONREAD ioctl、 c_cc[YTIME]、kbhit()、rdchk()、あるいはopen()やfcntl()の O_NDELAYオプションを用意しているかもしれない。質問19.1も参照の こと。 19.3: 処理の何%まで終了したかを表示して、それをその場で更新させるに はどうすればよいか。また仕事の進んでいることを"バトンを回す"こ とで知らせるのはどうやればよいのか。 A: こういう簡単なことであれば、結構移植性高く行うことができる。文 字'\r'を出力すると、普通は行送り(line feed)することなく復帰 (carriage return)する。それで現在行を上書きすることができる。 文字'\b'はバックスペースを表わし、普通はカーソルを左に1文字動 かす。 References: ANSI Sec. 2.2.2; ISO Sec. 5.2.2. 19.4: どうすれば画面に表示されている文字を一掃することができるか。 どうすれば反転文字で出力することができるか。 どうすればカーソルを、指定したx,y位置に動かすことができるか。 A: この手の話は、端末(や表示装置)の種類に左右される。こういう 処理にはtermcapやcursesのようなライブラリ、あるいはシステム 固有のルーチンを使わなければいけない。 画面をクリアするのに多少でも移植性の高い方法は、用紙送り (form-feed)文字('\f')を印字することである。この文字を印字する ことで表示されている内容がクリアされるものもある。もっと移植性 の高い方法は、今表示されているものが全部見えなくなるだけ改行を 印字することである。最後の手段として、system() (質問19.27参照) を使ってオペレーティングシステムが用意している画面クリアの命令 を呼び出すこともできる。 References: PCS Sec. 5.1.4 pp. 54-60, Sec. 5.1.5 pp. 60-62. 19.5: 矢印キーを読むにはどうしたらよいか。ファンクションキーは。 A: terminfoやいくつかのtermcapやいくつかのcursesは、こういうASCII コードにないキーに対応している。よくあるのは、特殊なキーは複数 の文字を連続して送り出す(普通はESC, '\033'で始まる)。こういう 文字の並びの構文解析は慎重を要するものになる可能性がある (cursesなら、最初にkeypad()を呼べば、構文解析までやってくれる)。 MS-DOSではキーボードからの入力を読んでいて値0('0'ではない)を持 つ文字を受け取ったら、それは次に読む文字は特別なキーを表わす符 号であることを意味している。どんなDOSのプログミングガイドでも いいからキーボードのコードを参照すること。(簡単に説明する。 上、左、右、下矢印キーはそれぞれ72、75、77、80である。ファンク ションキーは59から68に対応する。訳注: これはPC互換機の話である。 NECの98シリーズには当てはまらない) References: PCS Sec. 5.1.4 pp. 56-7. 19.6: どうすればマウスの出力を読むことができるか。 A: 対象とするシステムの資料を参照すること。あるいは適切なシステム 専門のニュースグループでたずねること(当然FAQを先に調べること)。 マウスの取り扱いは、X WindowとMS-DOSとMacintoshではまったく 異なる。たぶん他のシステムでも異なるだろう。 19.7: シリアル(COM)ポートを使ったI/Oはどうやればよいか。 A: それはシステム次第である。Unixではよく、/devにあるデバイスをオー プンしたり(open)、読んだり(read)、書き込んだり(write)する。ま たデバイス ドライバの機能を使って、I/Oの特性を調整する。(質問 19.1と19.2を参照。) MS-DOSでは、あらかじめ定義されたストリーム であるstdauxや、COM1のようなスペシャルファイルや、原始的なBIOS の割り込みや、(かなりの性能を要求するときは)世の中にたくさんあ る割り込み駆動のシリアルI/Oのパッケージを使う。ネットワーク上 で何人かがJoe Campbellの書いた『C Programmer's Guide to Serial Communications』を推薦している。 19.8: 出力先をどうすればプリンターに切り替えることができるか。 A: Unixではpopen()(質問19.30参照)を使ってlpやlprプログラムに書き 込むか、/dev/lpのようなスペシャルファイルをオープンする。 MS-DOSでは、(C規格の標準ではないが)あらかじめ定義された標準入 出力のストリームstdprnに書き込むか、スペシャルファイルである PRNやLPT1をオープンする。 References: PCS Sec. 5.3 pp. 72-74. 19.9: エスケープシーケンスを使って、端末やその他のデバイスを制御する のはどうすればいいのか。 A: そもそもデバイスに文字を送り込むのはどうやればいいかわかってい れば(上の質問19.8参照)、エスケープシーケンスを送り込むのは何で もない。ASCIIでは、ESCのコードは033(10進では27)なので、 fprintf(ofd, "\033[J"); のようなコードを書けばESC [ Jというシーケンスを送り出す。 19.10: グラフィックするのは。 A: 昔々、Unixにはデバイスに依存しない、plot(3)や plot(5)に記述された、小さくまとまった結構よくできた図示ルーチンが 存在した。しかし、ほとんど使われなくなってしまった。 MS-DOSでプログラムするときは、VESAやBGIに準拠したライブラリ を使ったほうがいいだろう。 プロッタの種類がはっきりしてるときは、絵を描かせることは適切な エスケープシーケンスを送る話になる。したがって質問19.9を参照の こと。プロッタのベンダーがCから呼べるライブラリを提供してるか もしれないし、インターネットから引っ張って来ることができるかも しれない。 特定のウインドウシステム(Macintosh、X Window、Microsoft Windows)用のプログラムには、そのシステムが持つ機能を使う。関 連する資料やニュースグループやFAQを参照のこと。 References: PCS Sec. 5.4 pp. 75-77. 19.11: ファイルが存在するかどうかはどうやって調べればよいか。指定され た出力ファイルがすでに存在するときにはユーザーに了解を取りたい。 A: この判定を信頼性が高く移植性も高く実行することは意外にも難しい。 どんなテストも、テストをしてからファイルを開こうとするまでの間 に(その他のプロセスによって)生成されたり削除されたりすれば、無 効になってしまう可能性がある。 解答として考えられる3つはstat()とaccess()とfopen()である。 (fopen()を使ってファイルの存在をざっとテストするには、ファイル を開いて即座に閉じればいい。)この3つの中でfopen()だけが広く移 植可能で、access()はもし存在しても、Unixのset-UID機能を使った プログラムでは注意深く使う必要がある。 ファイルを開くような作業がうまくいくか前もって予測しようとする より、開いてみて、戻り値を見て、失敗してたら文句をつけるほうが 良い考えである。(すぐわかるように、これでは、ファイルが存在す るときは上書きしたくないときには、ファイルを開くときにO_EXCLオ プションのようなものがないとうまくいかない。O_EXCLオプションは この場合やって欲しいことをぴったりやってくれる) References: PCS Sec. 12 pp. 189,213; POSIX Sec. 5.3.1, Sec. 5.6.2, Sec. 5.6.3. 19.12: どうすれば読む前にファイルの大きさを知ることができるか。 A: もしも"ファイルの大きさ"が、C言語を使ってファイルから読むこと のできる文字の数ということであれば、一般にはこの数を前もって知 ることは不可能である。 Unixでは関数stat()が正確な答えを与えてくれる。他のシステムの中 にもUnixのstat()に類似の関数を用意しているものがあって、そうい う場合は近似値を与えてくれる。fseek()を呼んでファイルの最後ま で進んで、ftell()を使えばよい。しかしこれもstat()が抱えている のと同じ問題を抱えている。fstat()は移植性が高くないし、たいて いstat()が返すのと同じ情報を返す。ftell()はバイナリーファイル を除いては正しくバイト数を返すとは限らない。filesize()とか filelength()というルーチンを用意しているシステムもある。これら のルーチンも移植性が高いわけではない。 本当にファイルのサイズを前もって決めておかなければならないのか。 Cプログラムから見えるファイルのサイズを決定する最も正確な方法 は、ファイルを開いて読むことであるから、ファイルを読みながらサ イズを知るように書き換えたほうがいい。 References: ANSI Sec. 4.9.9.4; ISO Sec. 7.9.9.4; H&S Sec. 15.5.1; PCS Sec. 12 p. 213; POSIX Sec. 5.6.2. 19.12a: ファイルがいつ変更されたかはどうすればわかるか。 A: UnixやPOSIXにはstat()というのがある。 これはその他の多くのシステムでも用意されている。 (質問19.12参照。) 19.13: まったく消し去ることや上書きすることなくファイルをその場で短く することはできるか。 A: BSDシステムはftruncate()を、他のものはchsize()を、またシステム によっては(ドキュメントには書かれていないだろうけれど)fcntlの オプションとしてF_FREESPを用意している。MS-DOSではwrite(fd, "", 0)が使えることがある。けれど頭のほうにあるブロックを削除す る、本当に移植性の高い解決策も方法も存在しない。質問19.14も参 照のこと。 19.14: ファイルの真ん中に1行(あるいは1レコード)挿入する、あるいは削除 するのはどうやればよいか。 A: ファイルを書き直すのでなければ、たぶん不可能だろう。普通は、単 純にファイルを書き直す。(レコードを削除する代わりに、削除したと 印を付けるだけにする手もある。こうすれば書き直しをしなくてもす む。) 質問12.30と19.13も参照のこと。 19.15: オープンしたファイルのファイル識別子からファイル名を復活するこ とができるか。 A: この問題は、たいてい解決不能である。たとえばUnixではディスク全 体を端から端まで調べることが(たぶん特別な許可が必要となるだろ うが)理屈の上では必要で、ファイル識別子がパイプや削除された後 のファイル(複数のリンクが張られているファイルに対しては誤解を まねくような答えを返すだろう)を指していたら、この方法もうまく いかない。オープンしたファイルの名前を(たぶんfopen()に関数を一 段かぶせて)自分で覚えておくのが一番よい。 19.16: どうやればファイルを削除することができるか。 A: 標準Cのライブラリ関数にremove()がある。(よってこれは、この章 の数少ない、解答が「システム依存」でない質問である。) 古い、 ANSI制定以前のUnixシステムにはremove()はないので、その場合は unlink()を試してみること。 References: K&R2 Sec. B1.1 p. 242; ANSI Sec. 4.9.4.1; ISO Sec. 7.9.4.1; H&S Sec. 15.15 p. 382; PCS Sec. 12 pp. 208,220- 221; POSIX Sec. 5.5.1, Sec. 8.2.4. 19.17: どうして明示的にパスを指定してファイルが開けないのか。 fopen("c:\newdir\file.dat", "r") だとうまくいかない。 A: ここで開こうとしているファイルは、つまりファイル名に\nや\fを含 んだファイルは、たぶん存在しないし、開こうとしてるつもりのファ イルでもない。 文字定数や文字列リテラルでは、バックスラッシュ\は拡張文字(エス ケープキャラクタ)であり、次に続く文字に特別な意味を与える。 バックスラッシュそのものをパス名の一部としてfopen()(その他のルー チンでも)に正しく渡すには、バックスラッシュを2回続けて書かなけ ればいけない。1つめのバックスラッシュは2つめのバックスラッシュ をエスケープする。 fopen("c:\\newdir\\file.dat", "r"); 別のやりかたとしては、MS-DOSでは、スラッシュ'/'もディレクトリ の分離文字に使えることがわかっている。そこで以下のように書くこ とができる。 fopen("c:/newdir/file.dat", "r"); (ところで、プリプロセッサの#include前処理指令で出てくるヘッ ダファイルの名前は文字列リテラルでないので、その中ではバック スラッシュのことは心配しなくてよい。) 19.18: 「ファイルを開きすぎ(Too many open files)」というエラーが出た。 同時に開けるファイルの数をどうすれば増やせるのか。 A: 同時に開くことのできるファイルの数には、実際には少なくとも2つ の資源の制限がある。下位の層の話である"ファイルディスクリプタ" や"ファイルハンドル"をオペレーティングシステムがいくつ持ってい るかと、標準入出力ライブラリの持つFILE構造体がいくつあるかであ る。両方とも十分な数でなければならない。MS-DOSでは、オペレーティ ングシステムのファイルハンドルの数をCONFIG.SYSに1行記述するこ とで制御することができる。コンパイラの中には標準入出力のFILE構 造体の数を増やす命令(たぶんソースにも追加が必要)を持っているも のもある。 19.20: ディレクトリの内容を読むにはどうすればよいか。 A: opendir()やreaddir()が使えるかどうか調べること。これらのルーチ ンはPOSIXの一部で、たいていのUnixシステムに載っている。MS-DOS、 VMS、その他のシステム用の実装も存在する(MS-DOSには実質的に同じ 仕事をするFINDFIRST、FINDNEXTというルーチンも存在する)。 readdir()はファイルの名前しか返さない。ファイルについてもっと 情報が必要であれば、stat()を呼ぶ。ファイル名がワイルドカードと 一致するかどうかについては、質問13.7を参照のこと。 19.22: どれだけメモリが使えるかはどうやればわかるか。 A: オペレーティングシステムが、そういう情報を返すルーチンを用意し てるかもしれない。ただし、これはまったくシステム依存の話だ。 19.23: どうやれば64Kより大きな配列や構造体を割り付けることができるのか。 A: まっとうなコンピューターなら使用可能なすべてのメモリに、こっ ちが意識しないでアクセスできるのが当たり前である。運悪くまっと うでないコンピューターの相手をすることになったら、プログラム内 のメモリの使いかたを考え直すか、さまざまなシステム依存の技を 使うかする必要がある。 64Kというのは(それでも)けっこう大きなメモリの塊である。どんな にたくさんメモリがコンピューターに載っていても、大量のメモリを 連続に切れ目なく割り付けようとするのは欲張りすぎである。(C規格 は、1つのオブジェクトが32Kより大きくなることを保証していない。) メモリ全体に切れ目がないことを必要としないようなデータ構造を使 うようにするほうが、良い考えであることが多い。動的に割り付けた 多次元の配列には、質問6.16のところで紹介したように、ポインタへ のポインタが使える。構造体の大きな配列を使う代わりに、線形リス トや構造体へのポインタの配列を使えばよい。 (8086ベースの)PC互換システムを使っていて640Kの壁にぶつかったら、 "ヒュージ(huge)"メモリモデルを使うか、拡張メモリ(訳注:日本語で はexpanded memoryとextende memoryを区別する用語がない)を使うか、 halloc()やfarmalloc()のようなmallocの変形を使うか、セグメント を意識させない32ビットの"平らな(flat)"コンパイラ(例: djgpp、質 問18.3参照)を使うか、何かしらのDOSエクステンダーを使うか、オペ レーティングシステムを取り替える。 References: ANSI Sec. 2.2.4.1; ISO Sec. 5.2.4.1. 19.24: 「DGROUPデータの割り付けが64Kを越えた(DGROUP data allocation exceeds 64K)」というエラーメッセージは何を表わしているのか。ど うすればよいのか。ラージモデルを使えば64K以上のデータが使える ものだと思っていた。 A: ラージモデルを使ってもMS-DOSのコンパイラは、ある種のデータ(文 字列、初期値付きのグローバル変数やスタティック変数の一部)をデ フォルトのデータセグメントに押し込む。そしてこのセグメントがあふ れる。グローバル変数の数を減らすか、すでにまあまあの量まで抑え ていたら(あるいは問題が文字列の数か何かなら)、コンパイラにデフォ ルトのデータセグメントをそんなにたくさん使わないようにさせるこ とができるかもしれない。"小さな"データオブジェクトだけをデフォ ルトのデータセグメントに置き、"小ささ"のしきい値を設定する手段 (たとえばマイクロソフト社のコンパイラでは/Gtオプション)を提供 しているコンパイラもある。 19.25: あるアドレスに位置するメモリ(メモリマップされたデバイス、 あるいはグラフィックメモリ)にどうやってアクセスすればよいか。 A: 適切なデータ型のポインタを、望むアドレスの値に設定する。 (明示的にキャストをして、コンパイラにこちらが移植性のない変換 をする気があることを伝える) unsigned int *magicloc = (unsigned int *)0x12345678; そうすれば*magiclocは望む場所を指す。(MS-DOSでは、セグメントと オフセットの相手をするのにMK_FP()のようなマクロが便利かもしれ ない。) References: K&R1 Sec. A14.4 p. 210; K&R2 Sec. A6.6 p. 199; ANSI Sec. 3.3.4; ISO Sec. 6.3.4; Rationale Sec. 3.3.4; H&S Sec. 6.2.7 pp. 171-2. 19.27: Cプログラムの中から別のコマンド(単体の実行可能プログラム、オペ レーティングシステムのコマンド)を起動したい。どうすればよいか。 A: ライブラリ関数であるsystem()を使う。これはまさしく上記を実行す る。system()の戻り値は、良くてコマンドのexitのステータスで、コ マンドの出力とは普通はぜんぜん関係ない。system()は起動すべきコ マンドを表わす文字列を1つだけ引数として取る。複雑なコマンド行 を組み立てる必要があればsprintf()を使う。質問19.30を参照のこと。 References: K&R II Sec. B6 p. 253; ANSI Sec. 4.10.4.5; H&S Sec. 21.2; PCS Sec. 11 p. 179; 19.30: 別のプログラムやコマンドを実行して、その出力を捕まえるのはどう すればよいのか。 A: Unixや他のいくつかのシステムはpopen()ルーチンを用意している。 このルーチンはコマンドを走らせているプロセスにつなげたパイプへ の標準入出力のストリームを、出力を読み取れる(あるいは入力を用 意する)ように設定するものである。 popen()が使えない環境なら、system()を使って、出力をファイルに 書き出し、後からファイルを開いて中身を読むことができるかもしれ ない。 Unixを使っていてpopen()では不足なら、pipe(), dup(), fork(), exec()といった関数を調べること。 (ところで、ひとつだけおそらく絶対うまくいかないのは、freopen() をつかうことである。) References: PCS Sec. 11 p. 169. 19.31: どうすればプログラムが起動されたときの絶対パスを知ることができ るか。 A: argv[0]に絶対パスやパスの一部が入っているかもしれないし、何も 入っていないかもしれない。argv[0]にパス名が入っているが完全で ないときは、シェルのようなコマンド行を解釈するソフトがコマンド を探す筋道を真似ればいい。しかしながら確実な方法は存在しない。 References: K&R1 Sec. 5.11 p. 111; K&R2 Sec. 5.10 p. 115; ANSI Sec. 2.1.2.2.1; ISO Sec. 5.1.2.2.1; H&S Sec. 20.1 p. 416. 19.32: 実行可能プログラムと同じディレクトリにあるプログラムの設定ファ イルがどこにあるかを自動的に捜したい。どうやればよいか。 A: これは難しい。上の質問19.31も参照のこと。使える方法を思い付い たとしてもプログラムの補助(ライブラリ用の)ディレクトリを、環 境変数かなにかを使って、変更可能にしたいと思うようになるかもし れない。(プログラムの設定ファイルを様々な場所に置けるようにし ておくのは、何人かでそのプログラムを使うときに、大事な話となる。 例:マルチユーザシステムで) 19.33: どうすればプロセスが、起動した側の環境変数を変更することができ るか。 A: 可能かもしれないし不可能かもしれない。それぞれのオペレーティン グシステムが、Unixに類似の変数名と値を結び付ける機能を、それぞ れの方法で実装している。"環境"が実行中のプログラムによって都合 よく変更できるかどうかと、できるとすればその方法は、システムに 依存する。 Unixのプロセスは自分の環境を変更することができる(setenv()や putenv()をこの目的のために用意しているシステムもある)。そして、 その環境はたいてい子プロセスに渡される。しかし親プロセスにまで 戻ることはない。 19.36: どうすればオブジェクトファイルを読み込んで、その中にあるルーチ ンに飛び込むことができるか。 A: ダイナミックリンカやダイナミックローダーが必要である。領域を割 り付けて、その領域にオブジェクトファイルを読み込むことはできる。 しかしオブジェクトファイルのフォーマットやその配置などについて、 かなりの知識が必要となる。BSDではsystem()とld -Aを使ってリンク させることができる。SunOSやSystem Vの多くのバージョンには-ldl というライブラリが載っていて、これを使えばオブジェクトファイル を動的にロードすることができる。VMSではLIB$FIND_IMAGE_SYMBOLを 使う。GNUのパッケージにdldというのが存在する。質問15.13も参照 のこと。 19.37: 1秒より細かい遅れや、ユーザの反応の1秒より細かい計測は、どう実 装すればよいか。 A: 残念ながら移植性の高い方法は存在しない。V7 Unixやそこから派生 したシステムは、かなり役に立つftime()を用意していた。この関数 はミリ秒単位の精度を持っていた。しかしSYSTEM VやPOSIXから消え てしまった。他に探すべき関数としてはclock()やdelay(), gettimeofday(), msleep(), nap(), napms(), setitimer(), sleep(), times(), usleep()がある(しかしながらwait()というルー チンは、少なくともUnixでは、望みのものではない)。select()や poll()は(もし手に入れば)、簡単な遅れを実装するのに使える。 MS-DOSマシンでは、システムタイマーやタイマーの割り込みをプログ ラムしなおすことで実現できる。 これらの関数の中でclock()だけがANSI規格の一部である。clock()を 2回呼んで、その差が経過時間を表わす。もしCLOCKS_PER_SECが1より 大きければ、差は秒以下の分解能を持つ。しかしながら、clock()が 返すのはclock()を呼び出したプログラムのプロセッサの使用時間で、 マルチタスクのシステムでは実際に経過した時間とぜんぜん違うかも しれない。 遅れを実装するのに使えるのが時間を報告する関数しかない場合でも、 CPUパワーを一点に集中し処理待ちの状態を作り出して遅れを実装す ることができる。けれどこれはシングルユーザーのシングルタスクの システムでだけ許される選択肢である。というのも、これは他のプロ セスにとってはまったく迷惑な話であるからである。マルチタスクの システムでは、指定した時間だけプロセスを眠らせる関数を使うこと。 たとえばsleep()やselect()やpause()を、alarm()やsetitimer()と共 に使う。 本当に短い遅れなら、なにも実行しないループを使いたい気がしてく る。 long int i; for(i = 0; i < 1000000; i++) ; けれど、どうやってでもこの誘惑を払いのけなければいけない。1つ には、注意深く計算したつもりの遅れを実現するループが、次の月に もっと速いプロセッサが登場してうまく動かなくなると相場が決まっ ているからである。もっと悪いことに、賢いコンパイラならループは 何も仕事をしてないことに気が付いて最適化の際にまったく取り除い てしまうかもしれない。 References: H&S Sec. 18.1 pp. 398-9; PCS Sec. 12 pp. 197-8,215- 6; POSIX Sec. 4.5.2. 19.38: Control-Cのようなキーボード割り込みを捕まえたり無視するの はどうすればよいか。 A: 基本的な手順は、signal()を呼んで、 #include signal(SIGINT, SIG_IGN); のように割り込みシグナルを無視するとか、 extern void func(int); signal(SIGINT, func); として割り込みシグナルを受け取ったら関数func()に制御が移るよう にする。 Unixのようなマルチタスクのシステムでは、もう少し手の込んだ以下 のような技法を使うほうがよい。 extern void func(int); if(signal(SIGINT, SIG_IGN) != SIG_IGN) signal(SIGINT, func); テストと余分にsignal()の呼び出しを用意することでフォアグランド で打ち込まれて発生したキーボード割り込みがうっかりバックグラン ドで走っているプログラムに割り込みがかからないようにしている (どんなシステムで動かすにしても、このようにsignal()を呼ぶよう にコーディングしても悪いことにはならない)。 システムによっては、キーボードの割り込みの処理は端末の入力を処 理するサブシステムの特定のモードの機能である。 システムによってはキーボードの割り込みを調べるのは、プログラム が入力を読み込んでいるときにだけ実行され、どの入力ルーチンが呼 ばれているかによってキーボード割り込みの処理が変わってしまう (そもそも入力ルーチンが動いているかどうかによる)。MS-DOSシステ ムではsetcbrk()またはctrlbrk()が必要となるかもしれない。 References: ANSI Secs. 4.7,4.7.1; ISO Secs. 7.7,7.7.1; H&S Sec. 19.6 pp. 411-3; PCS Sec. 12 pp. 210-2; POSIX Secs. 3.3.1,3.3.4. 19.39: 浮動小数点の例外を扱う気の利いた方法は。 A: 多くのシステムで、matherr()というルーチンを定義することができ る。このルーチンはの中にある数学ルーチンで起きたエラー のような、ある種の浮動小数点のエラーが発生すると呼ばれる。また、 signal()(上の質問19.38を参照)を使ってSIGFPEを捕まえることがで きるだろう。質問14.9を参照のこと。 References: Rationale Sec. 4.5.1. 19.40: えーっと、どうやってソケットを使えばいいのか。ネットワークは。 クライアント/サーバーのアプリケーションの書き方は。 A: 上のすべての質問はこのFAQの範囲外で、C言語よりは使用するネット ワークの設備との関係のほうが強い。この件に関するよい本としては Douglas Comerの三部作であるInternetworking with TCP/IPや W. R. StevensのUNIX Network Programminが挙げられる。(ネットワー クそのものの上にもたくさんの情報が流れている) 19.40b: BIOSコールをどうやって使えばいいのか。どうすれば常駐ソフトが書 けるのか A: こういうのは特定のシステムに特有な話である(十中八九、MS-DOSが 走っているPC互換機だろう)。 特定のシステムの話を扱うcomp.os.msdos.programmerのようなニュー スグループやそういうグループのFAQからのほうがもっと情報が得ら れるだろう。その他の素晴らしい情報源としてはRalf Brownの割り込 みの一覧表がある。 19.41: そんなこといっても、そんな標準でないシステム依存の関数を使うわ けにはいかない。私のプログラムはANSI規格に適合してなければいけ ない。 A: ついてないね。君が要求事項を誤解してるか、そういう要求を満たす のは不可能なのかのどちらかだ。ANSI/ISO標準Cは上のような事柄を どうやって実行するのかを定義するものでは全くない。それは言語の 規格であって、オペレーティングシステムの規格ではない。 国際的な規格で、こうした点について取り組んでいるのは POSIX((IEEE 1003.1, ISO/IEC 9945-1)である。(Unixだけではなく) 多くのオペレーティングシステムが今ではPOSIX互換のプログラミング インタフェースを用意している。 システム依存の機能を、移植するシステムごと に書き直す少数のファイルの少数のルーチンに任せて、プログラムの ほとんどの部分をANSI規格適合にすることは可能であるし、そうする ことが望ましい。 20章 その他 20.1: 関数から複数の値を返すことはできるか。 A: 値を格納する領域をいくつか起動する側で用意して、それらの領域を 指すポインタを引数として渡し、関数にポインタの先に値を埋めても らう。あるいは、関数の戻り値を構造体にして、その構造体に希望の 値を設定してもらう。あるいは(最後の手段として)グローバル変数を 使うことを考える。質問2.7, 4.8, 7.5を参照のこと。 20.3: コマンド行の引数をどうやって手にいれることができるか。 各引数は配列argvによって指されている。main()はargvを引数として 起動される。質問13.7と19.20も参照のこと。 References: K&R1 Sec. 5.11 pp. 110-114; K&R2 Sec. 5.10 pp. 114- 118; ANSI Sec. 2.1.2.2.1; ISO Sec. 5.1.2.2.1; H&S Sec. 20.1 p. 416; PCS Sec. 5.6 pp. 81-2, Sec. 11 p. 159, pp. 339-40 Appendix F; Schumacher, ed., _Software Solutions in C_ Sec. 4 pp. 75-85. 20.5: どうすれば、ワードの大きさが異なったり、バイトの並びかたが異なっ たり、浮動小数点数の内部表現が異なるようなマシンでも読めるようにデー タファイルに書き込むことができるのか。 A: 最も移植性が高い解はテキストファイル(通常はASCII)を使うこと である。fprintf()を使って書き込み、fscanf()の類を使って読む(同 様の助言はネットワークのプロトコルにもあてはまる)。「テキスト ファイルは大きすぎて、その読み書きは遅すぎる」という意見は疑っ てかかること。その効率はしばしば実用に耐えるものであるし、マシ ン間で容易にデータ交換できることや標準的な道具だけで操作できる という利点は否定できない事実である。 バイナリフォーマットを使わなければならない場合でも、 標準化されたフォーマットを利用して、移植性を 向上させたり、既存の I/Oライブラリをうまく使うことが可能である。標準のフォーマット としてはSUNのXDR(RFC 1014)、OSIのASN.1(CCITTのX.409やISO 8825 で「基本符号化則:Basic Encoding Rules」として参照されている)、 CDF、netCDF、HDFが存在する。質問2.12や12.38を参照のこと。 References: PCS Sec. 6 pp. 86,88. 20.6: char *の変数があって、その変数は関数の名前を指している。どうす ればこの関数を呼ぶことができるか。 A: 一番単純な方法は、関数の名前と関数へのポインタの対応表を用意 することである。 int func(), anotherfunc(); struct { char *name; int (*funcptr)(); } symtab[] = { "func", func, "anotherfunc", anotherfunc, }; そうして名前を求めてテーブルを探し、名前に対応する関数へのポイ ンタを使って関数を起動すればよい。質問2.15と19.36も参照のこ と。 References: PCS Sec. 11 p. 168. 20.8: ビットの集合や配列はどうやって実装すればいいのか。 A: charやintの配列を使う。マクロを使って、配列のしかるべき要素 にある望みのビットにアクセスできるようにする。以下にcharの配列 を使った簡単なマクロの例を紹介する。 #include /* for CHAR_BIT */ #define BITMASK(b) (1 << ((b) % CHAR_BIT)) #define BITSLOT(b) ((b) / CHAR_BIT) #define BITSET(a, b) ((a)[BITSLOT(b)] |= BITMASK(b)) #define BITTEST(a, b) ((a)[BITSLOT(b)] & BITMASK(b)) (がないときはCHAR_BITの代わりに8を使う) References: H&S Sec. 7.6.7 pp. 211-216. 20.9: マシンの並び順(byte order)がビッグエンディアンかリトルエンディ アンかはどうやって調べればよいか。 A: ポインタを使う手がある。 int x = 1; if(*(char *)&x == 1) printf("little-endian\n"); else printf("big-endian\n"); 共用体を使って調べることも可能である。 質問10.16も参照のこと。 References: H&S Sec. 6.1.2 pp. 163-4. 20.10: 整数をどうやれば2進数や16進数に変換できるか。 A: 自分の質問の内容を本当に理解しているか。整数は内部的には2進数 で保存されている。だから、数が8進だとか10進だとか16進など自分 に都合いいN進法で(底で)あると考えるのはたいていの場合間違いで ある。どの底で(何進法で)数を表わすかは外界から数を読んだり書い たりするときにだけ問題になる。 ソースコードの上では、10進法以外の場合は数の前に0や0x(それぞれ 8進、16進を表わす)を付けて表わす。入出力中は、整形済みの数が何 進数かは、printfやscanfの類では書式指定子(%d, %o, %xなど)を選 択することで、strtol()やstrtoul()では3番目の引数で制御可能であ る。ただしバイナリ入出力中は、ここでも何進法かはどうでもいい ことになる。 "バイナリ"入出力についてもっと知りたいならば、質問2.11を参照 のこと。質問8.6や13.1も参照のこと。 References: ANSI Secs. 4.10.1.5,4.10.1.6; ISO Secs. 7.10.1.5,7.10.1.6. 20.11: 2進数の定数(0b101010のように)を使うことはできるか。2進数用の printf()の書式は存在するか。 A: 2進数の定数は存在しないし、2進数用の書式も存在しない。底2の(2 進数の)文字列による表現をstrtol()を使って整数に変換することは 可能である。 20.12: 値の中で立っているビットの数を数えるもっとも効率のよい方法を教 えて欲しい。 A: この手の"ビットをいじる問題"は参照用テーブル(lookup table)を使 うことで、高速かつ能率よく処理することができる(ただし以下の質 問20.13を参照のこと)。 20.13: どうすれば、このコードをもっと効率的にすることができるか。 A: よいアルゴリズムを選んで、それを注意深く実装して、プログラムが 余分なことまでしないようにする。たとえば、文字をコピーするルー プを局所的にどんなに最適化しても文字をまったくコピーしないコー ドには勝てない。 効率について心配するなら、いくつかのことがらを視野にいれておく ことが大事である。まず、効率はとても人気のある話題だけれど、人 が考えているほど、たいした問題ではない。たいていのプログラムの たいていのコードは処理速度を要求されているわけではない。処理速 度を要求しないコードの場合、最大限に効率化を計るより、わかりや すさや移植性を考えるほうがずっと大事である(コンピューターは非 常に高速である。だから"能率の悪い"コードも目立った遅れなく走る かもしれないということをお忘れなく)。 プログラムの中でどこが問題点になるか予想することは非常に困難な ことで有名である。効率に関心があるのならプログラムのどの場所に 関心を置くべきか調べるために、処理の輪郭を記録(profiling)する ソフトウェアを使うことは大事である。たいていは、実際の計算時間 はI/Oやメモリの割り付けなどの周辺装置の操作に集中する。そして これらの作業は、バッファリングやキャッシングの技を使って高速化 することができる。 コードの処理時間の制約が厳しいところでも、コードの詳細を局所的 に最適化しなければならないほど重要であることはない。しばしば提 案される"効率的なコーディングの技"の多くは(たとえば2のべき乗を 掛けるときに掛け算の代わりにシフト演算子をつかうこと)、単純なコ ンパイラでも自動的にやってくれる。不器用な"最適化"の試みはコー ドをふくらませることで性能を悪化させる可能性さえあるし、その移 植性が高いことはめったにない(すなわち、あるマシンでは高速化さ れかもしれないけれど、他のマシンでは遅くなってしまう)。いずれ にせよ、コードをいじってもせいぜい効率が線形に改善されるだけで ある。大きな見返りはよりよいアルゴリズムから得られる。 効率化のトレードオフに関する議論や、効率化が本当に重要なときの 良い助言についてはKernighanとPlaugerの『プログラム書法』の7章 やJon Bentleyの『プログラム改良学』を参考のこと。 20.14: ポインタは本当に配列よりも高速なのか。関数呼び出しをするとど れだけ遅くなるのか。++iはi=i+1よりも高速か。 A: これらの質問や多くの類似の質問への正確な解答は、もちろん使用し ているプロセッサやコンパイラに依存する。結果が知りたければテス トプログラムで注意して測定するしかない(多くの場合、その差は非 常に小さいので、差を知るのに何万回もくり返しが必要となる。もし 手に入るのなら、両方の技を使ったコードが別のコードを生成したか どうか、コンパイラの生成したアセンブラ出力を見比べてみること)。 たいていは大きな配列の中をアクセスしてまわるのにポインタを使う ほうが、配列の添え字を使うより高速であるが、その反対のプロセッ サーも存在する。 関数呼び出しは、インラインで記述するよりも、それだけ明らかに遅 くなるけれど、モジュール化やコードのわかりやすさに役に立ってい ることを考えれば、使わない理由なんてめったに存在しない。 i = i + 1といった書き方を書き直す前に、相手はCコンパイラであっ てキー入力をプログラムできる電卓を相手にしているのではないこと をお忘れなく。よくできたコンパイラならi++、++i、i = i + 1、ど れについても同じコードを生成する。i =i + 1を押しのけて++iやi += 1を使うのは、書き方の面からであって効率を問題にしているから ではない。(質問3.12も参照のこと。) 20.17: 文字列の内容によって処理を切り替える方法はあるか。 A: 直接には不可能である。文字列を整数コードに対応させる別の関数を 用意して、その整数コードを使って処理を切り替えるのが適切な場合 がある。もちろん、その他の手としては、strcmp()に頼って普通に if/elseを並べることが考えられる。質問10.12, 20.18, 20.29を参照 のこと。 References: K&R1 Sec. 3.4 p. 55; K&R2 Sec. 3.4 p. 58; ANSI Sec. 3.6.4.2; ISO Sec. 6.6.4.2; H&S Sec. 8.7 p. 248. 20.18: caseのラベルに定数以外(範囲指定とか、こちらの好きな式など)を持っ てくることはできるか。 A: できない。switch文はもともとの設計でコンパイラがコードを変換す るのが楽なように単純になっている。だからcaseのラベルは単体の定 数の整数式に限られている。一つの式に複数のcaseのラベルを付ける ことは可能である。こうすれば、すべての場合をいちいち記入するこ とを面倒に思わなければ、小さな範囲を覆うことができる。 任意の範囲指定や定数以外の式をもってきたいなら、if/elseをずら ずら並べるしかない。 質問20.17も参照のこと。 References: K&R1 Sec. 3.4 p. 55; K&R2 Sec. 3.4 p. 58; ANSI Sec. 3.6.4.2; ISO Sec. 6.6.4.2; Rationale Sec. 3.6.4.2; H&S Sec. 8.7 p. 248. 20.19: returnの後ろに来る式をくくる括弧は本当に省略可能か。 A: 省略可能だ。 大昔、Cの初期には、必要であった。そのころにCを学んだ人がたくさ んいるし、そのころ書かれたコードが今でも世の中に出まわっている。 それで括弧が今でも必要であるという考えが広まっている。 (ちなみにsizeof演算子も、オぺランドが変数か単項式なら、括弧は 省略可能である。) References: K&R1 Sec. A18.3 p. 218; ANSI Sec. 3.3.3, Sec. 3.6.6; ISO Sec. 6.3.3, Sec. 6.6.6; H&S Sec. 8.9 p. 254. 20.20: なぜC言語のコメントは入れ子にすることができないのか。コメント を含むコードをコメントアウトするのはどうすればよいか。引用符で かこまれた文字列の中にコメントがあるのは文法上許されるのか。 A: Cのコメントが入れ子にできないのは、PL/Iのコメントが入れ子にで きないからである。CはPL/Iからコメントについてのアイデアを借用 している。コメントを含むような大きな領域を"コメントアウト"する ときは#ifdefや#if 0を使ったほうがいい(ただし、質問11.19を参照)。 /*や*/は二重引用句でくくられた文字列のなかでは特別な意味を持た ない。だからこれらの文字列がコメントを意味する事はない。なぜな らプログラムが(とくにに出力としてCのコードを生成するプログラム が)、これらの文字列を出力したいかもしれないから。 C++風の//で始めるコメントは現在のところCでは文法上許されていな いことに注意。だから、Cプログラムで使うのはよい考えではない(た とえ使用中のコンパイラが拡張機能として対応しているとしても)。 References: K&R1 Sec. A2.1 p. 179; K&R2 Sec. A2.2 p. 192; ANSI Sec. 3.1.9 (esp. footnote 26), Appendix E; ISO Sec. 6.1.9, Annex F; Rationale Sec. 3.1.9; H&S Sec. 2.2 pp. 18-9; PCS Sec. 10 p. 130. 20.24: どうしてCには入れ子の関数が用意されていないのか。 A: 呼ぶ側の関数のローカル変数に正しくアクセスするように入れ子の関 数を用意することは簡単な話ではない。そういうわけで、入れ子の関 数はCが単純さを失わないように故意に省略された。(gccは拡張機能と して入れ子の関数を許している。) 入れ子の関数があったとしたら使 えそうな状況の多くの(例:qsortの比較に使う関数)少し面倒だけれど 要求を満たす解としては、static宣言した隣り合う関数を使い、 static変数で情報をやりとりする方法がある。(そういう関数間で情 報をやりとりが必要な場合のもっときれいな解は、必要なコンテキス トを含んだ構造体へのポインタをやりとりすることである。) 20.25: どうすればCからFORTRAN(あるいはC++、BASIC、Pascal、Ada、LISP) の関数を起動することができるか(あるいはその反対は)。 A: 解答は、使用中のマシンやコンパイラが関数を起動する方法に依存す る。ひょっとしたらまったく不可能かもしれない。コンパイラの資料 を注意深く読むこと。ときどき「複数のプログラム言語を使ったプロ グラミングの手引き」が載っている。それでも引数を渡す方法や、実 行時の適切な立ち上がりを保証することは、神秘的ですらある。その 他にGlenn Geers作のFORT.Zからも手掛かりが得られる。このプログ ラムはsuphys.physics.su.oz.auのsrcディレクトリに置かれている。 cfortran.hというCのヘッダファイルは、世の中に出まわっている 多くのマシン上で、C/FORTRAN間のインタフェースを簡単にする。こ れはzebra.desy.de(131.169.2.244)よりanonymous ftpで手に入れる ことができる。 C++では外部関数の宣言に"C"修飾子を付けることで、Cの関数の起動 方法に従って起動することを指定することができる。 References: H&S Sec. 4.9.8 pp. 106-7. 20.26: Pascal(あるいはFORTRAN、LISP、Ada、awk、"古いC")をCに変換する プログラムを知らないか。 A: いくつかの自由に配布可能なプログラムが存在する。 p2c Dave Gillespieによって書かれ、comp.sources.unixに1990 年4月(Volume 21)に投稿された。csvax.cs.caltech.eduから anonymous ftpすることもできる。ファイル名は pub/p2c-1.20.tar.Z。 ptoc これもPascalをCに変換するツール。これはPascalで書かれ ている(comp.sources.unixのVolume 10、パッチはVolume 13?にある)。 f2c ベル研とBellcoreとカーネギーメロン大学の共同開発による FORTRANのプログラムをCに変換するソフトウエア。f2cにつ いて知りたければ「send index from f2c」というメッセー ジと共にnetlib@research.att.comあるいは reseach!netlibにメールを出すこと。(research.att.comの ディレクトリdist/f2cからのanonymous ftpも可能である。) このFAQの編者は、商品となった変換ツールや、ここに挙げていない 言語との間の変換ツールのリストを用意している。 質問11.31と18.16も参照のこと。 20.27: C++はCの上位集合か。C++のコンパイラをCのコードのコンパイルに使 うことはできるか。 A: C++はCに起源を持ち、大部分Cに基づく。しかしC言語としては正しい 構造であるが、C++では文法違反なものがいくつか存在する。逆に、 ANSI Cはプロトタイプやconstのような、いくつかの機能をC++から受 け継いでいる。よってどちらが上位集合とか下位集合ということはい えない。これらの違いにもかかわらず、Cのプログラムの多くはC++の 環境で正しくコンパイルできるし、最近のコンパイラの多くはCとC++ 両方のコンパイルモードを用意している。質問8.9と20.20も参照のこと。 References: H&S p. xviii, Sec. 1.1.5 p. 6, Sec. 2.8 pp. 36-7, Sec. 4.9 pp. 104-107. 20.28: "近似の"strcmpとでもいうようなルーチンが欲しい。つまり二つの文 字列が近い、けれど必ずしもぴったりでなくてもよい、ことを調べる ルーチンが欲しい。 A: 文字列のおよその一致に関するよく書けた情報やアルゴリズムが、参 考文献の役に立つ一覧と共に、Sun WuとUdi Manberの論文"AGREP -- A Fast Approximate Pattern-Matching Tool"に載っている。 その他のやり方はとしては「soundex」がある。これは同じ様な発音 の単語を同じ数値のコードに写像する。soundexは同じ様な発音の名 前を捜すのに(ちなみに電話帳の助けになるように)設計されたが、任 意の単語を処理するようにすることもできる。 References: Knuth Sec. 6 pp. 391-2 Volume 3; Wu and Manber, "AGREP -- A Fast Approximate Pattern-Matching Tool" . 20.29: ハッシュ法とは。 A: ハッシュ法とは、文字列を整数に写像する処理のことをいう。通常、 整数は比較的小さな範囲のものを使う。「ハッシュ関数」は文字列 (または、その他のデータ構造を)を有界の整数("ハッシュバケツ (hash bucket)")に写像する。この整数は配列のインデックスとして 使えるし、くり返しの比較に使うのも容易である。(すぐわかるよう に、巨大になる可能性のある文字列の集合を整数の小さな集合に写像 すれば一意にはならないものである。ハッシュ法を使ったどんなアル ゴリズムも、この"衝突"が起こることを考慮にいれなければならない。) これまでに、さまざまのハッシュ関数や関連するアルゴリズムが開発 されてきた。これらを満足に論じることは、このFAQの範囲を越えて いる。 References: K&R2 Sec. 6.6; Knuth Sec. 6.4 pp. 506-549 Volume 3; Sedgewick Sec. 16 pp. 231-244. 20.31: 日付から曜日を求める方法は。 A: ルーチンmktime()かlocaltime()を使うか(Q12.6、Q12.7を参照。ただ しtm_hourが0のときにはDST adjustmentに注意すること)、Zellerの 公式(sci.mathのFAQを参照)を使うか、するか、あるいは坂本智彦が ポストした以下の気の利いたコードを試すこと。 dayofweek(y, m, d) /* 0 = Sunday */ int y, m, d; /* 1 <= m <= 12, y > 1752 or so */ { static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4}; y -= m < 3; return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7; } (Copyright 1993, Tomohiko Sakamoto) 質問13.14と20.32も参照のこと。 References: ANSI Sec. 4.12.2.3; ISO Sec. 7.12.2.3. 20.32: 紀元2000年はうるう年か。(year % 4 == 0)はうるう年かどうかの正 確なテストか。 A: 一つめの問題の答えはYES、二つめはNOである。グレゴリオ暦のカレ ンダー用の正しい式は以下の式で表わされる。 year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) 詳細については、まともな天文学年鑑かその他の参考書を見ること。 (永遠に続く議論を未然に防ぐために言っておく。4000年に1度うる う年が来ると主張する文献は間違っている。) 質問13.14も参照のこと。 20.34: さて問題です。どうやれば出力として、自分自身のソースコードを産 み出すコードをどうやったら書けるか。 A: 本当に移植性の高い自己再生プログラムを書くのは大変難しい。これ は特に、一重/二重引用符の使い方、文字集合(ASCII, EBCDIC...) に何を使うかからくる困難による。 以下は古典的な例である(普通は1行で紹介される。しかし最初に走っ たときに自身を"修正"する) char*s="char*s=%c%s%c;main(){printf(s,34,s,34);}"; main(){printf(s,34,s,34);} (このプログラムは、この種のプログラムの多くと同じように二重引 用符 " がASCIIのように34であると決めてかかっている.) 20.35: "ダフのデバイス(Duff's Device)"とは。 A: とんでもなく曲がりくねって展開されたバイトコピーの関数である。 これはTome DuffがLucasfilmに在籍中に考え出した。"古典的"な形式 では、以下のようになる。 register n = (count + 7) / 8; /* count > 0 であると仮定する */ switch (count % 8) { case 0: do { *to = *from++; case 7: *to = *from++; case 6: *to = *from++; case 5: *to = *from++; case 4: *to = *from++; case 3: *to = *from++; case 2: *to = *from++; case 1: *to = *from++; } while (--n > 0); } countバイトだけfromによって指された配列からtoで指された場所ま でコピーする(toが指している先はメモリマップされたデバイスの出 力レジスタである。だからincrementされない)。残りのバイトの処 理する問題を(countが8の倍数でないときに)、swtich文に一度に8バ イトコピーするループを差し込むことで解決している。驚くなかれ、 swtich文に入れ子になっているブロックの中にcaseのラベルを埋めこ むことは合法である。この技のCで開発する人達や世の中への彼の声 明によれば、このCのswtichの構文は、特に"下に落ちる(fall through)"動きが、昔から物議をかもしてきた。「このコードはそう いう議論に論拠を出すことになる。ただし、賛成意見なのか反対意見 なのかはよくわからない。」 20.36: 次の国際難解Cコードコンテスト:International Obfuscated C Code Contest(IOCCC)はいつ開かれるのか。どうすれば今回や昔の授賞作を 手に入れることができるのか。 A: コンテストのスケジュールは、授賞者の発表されるUSENIXの会議の日 付に縛られている。このFAQを書いている時点では、この年に一度行 われるコンテストは10月に開かれる。 最新のルールやその外の情報 を得るにはSubject:に「send rules」と書いて {apple,pyramid,sun,uunet}!hoptoad!judges or judges@toad.com に送ればよい(これらのアドレスは出展の申し込み用ではない)。 コンテストの受賞者は1月に行われる冬のUSENIXの会議で最初に発表 され、その後ネットに投稿される。昔の(1984年以降の)受賞作品は uunetの~/pub/iocccというディレクトリから手に入れることができる (質問18.16を参照)。http://reality.sgi.com/csp/ioccc/も参照のこと。 どうやっても手に入れられなければ、過去の授賞作は上記のアドレス にSubject:として「send YEAR」を付けた電子メールを送ることによっ て得られる。ここでYEARは4けたの年か、年度の範囲指定、あるいは 「all」である。 20.37: K&R1で述べられていたentryというキーワードは。 A: このキーワードはFORTRANのように、関数が複数の異なる名前のエント リーポイントを持つことを許すことを考えて予約されていた。この機 能が実現された例を知っている人は誰もいない(このキーワードを使っ てどういう構文が考えられていたのかもだれも知らない)。この機能 は撤回され、ANSI Cのキーワードにはならなかった。(質問1.12も参 照のこと。) References: K&R2 p. 259 Appendix C. 20.38: そもそも、「C」という名前の由来は。 A: CはKen Thompsonの実験的なプログラミング言語Bから派生したもので ある。BはMartin RichardsのBCPL(Basic Combined Programming Language)から着想を得ている。BCPLはCPL(Cambridge Programming Language)の仕様を記述したものであった。しばらく、Cの後継言語は Dではなく、P(BCPLの3番目の文字)という名前になるのではないかと 噂されていた。しかしもちろん今日一番目につく後継言語はC++であ る。 20.39: 「char」というのはどう発音するのか。 A: C言語のキーワードの「char」の発音の仕方は少なくとも3通りはある。 「char(チャー)」、「care(ケア)」、「car(カー)」のように発音す る。("キャラクター"でもよい)どれを使ってもいい。 (訳注:カタカナで発音を表記した『UNIX用語由来/読み方辞書』が http://www.meitetsu.co.jp/docs/dic/unix-term-dic.htmlから 入手可能である。) 20.40: このリストをもう一部欲しいがどうしたらよいか。過去の版はどうか。 A: 最新版はftp.eskimo.comのディレクトリu/s/scs/C-faq/から入手する ことができる。ネットからも引っ張ってくることができる。通常、ニュー スグループcomp.lang.cに月の始めに投稿される。Expires:行が付い ているので一ヶ月丸々消えないで残っている。並行して、簡易版も入 手可能である(投稿もされている)。大きく更新した版には変更の一覧 も用意している。 このFAQはさまざまな形でニュースグループcomp.answersや news.answersに投稿されている。いくつものサイトがこのFAQも含め て、news.answersへの投稿やその他のFAQを保管している。サイト rtfm.mit.edu(ディレクトリpub/usenet/news.answers/C-faq/と pub/usenet/comp.lang.c/)やサイトftp.uu.net (ディレクトリ usenet/news.answers/C-faq/) は、そのようなサイトのうちの2つで ある。archieサーバー(質問18.16参照)はそのようなサイトを見つけ る助けになる。archieに"prog C-faq"と聞いてみろ。ftpが使えない 環境なら、rtfm.mit.eduにあるメールサーバーを使ってFAQを手にい れることができる。まずは、本文に「help」とだけ書いて mail-server@rtfm.mit.eduに送る。その他の情報はnews.answersの meta-FAQ listから得られる。 このFAQの別の形態のものがWorld-Wide Webで読むことができる。3つ のURLは(それぞれ形態は異なる)、 http://www.lysator.liu.se/c/c-faq/index.htmlと http://www.hut.fi/~jkorpela/CFAQ.htmlとhttp://www.cis.ohio- state.edu/hypertext/faq/usenet/C-faq/top.htmlである。(以下で紹 介する本に対応する)拡大版はもうすぐ、たぶん1995年の9月半ばに使 えるようになる。すべてのFAQを指すURL(話題による検索ができるも の)はhttp://www.cis.ohio- state.edu/hypertext/faq/usenet/FAQ-List.htmlと http://www.luth.se/wais/にある。 このFAQの拡張した版はAddison-Wesleyより『C Programming FAQs: Frequently Asked Questions』(ISBN 0-201-84519-9)として1995年11 月に出版されている。 (訳注: 日本語版はトッパンより『CプログラミングFAQ Cプログラミ ングのよく尋ねられる質問』(ISBN 4-8101-8097-2)として出版されて いる。) このリストは発展し続ける資料である。単に今月の興味深い質問への 回答の寄せ集めではない。昔のリストは時代遅れで大した情報を持っ ていない。例外は、ときどきある入力ミスで、今回のリストに含まれ ていないものである。 参考文献 Americal National Standards Institute, _American National Standard for Information Systems -- Programming Language -- C_, ANSI X3.159-1989 (質問11.2参照). [ANSI] Americal National Standards Institute, _Rationale for American National Standard for Information Systems -- Programming Language -- C_ (質問11.2参照). [Rationale] Jon Bentley, _Writing Efficient Programs_, Prentice-Hall, 1982, ISBN 0- 13-970244-X. (邦訳:プログラム改良学 近代科学社 ISBN4-7649-0159-5) G.E.P. Box and Mervin E. Muller, "A Note on the Generation of Random Normal Deviates," _Annals of Mathematical Statistics_, Vol. 29 #2, June, 1958, pp. 610-611. David Burki, "Date Conversions," _The C Users Journal_, February 1993, pp. 29-34. Ian F. Darwin, _Checking C Programs with lint_, O'Reilly, 1988, ISBN 0- 937175-30-7. David Goldberg, "What Every Computer Scientist Should Know about Floating-Point Arithmetic," _ACM Computing Surveys_, Vol. 23 #1, March, 1991, pp. 5-48. Samuel P. Harbison and Guy L. Steele, Jr., _C: A Reference Manual_, Fourth Edition, Prentice-Hall, 1995, ISBN 0-13-326224-3. [H&S] (邦訳:詳細C言語:H&Sリファレンス 日本ソフトバンク ISBN4-89052-050-3) Mark R. Horton, _Portable C Software_, Prentice Hall, 1990, ISBN 0-13- 868050-7. [PCS] (邦訳:ポータブルCプログラミング トッパン) Institute of Electrical and Electronics Engineers, _Portable Operating System Interface (POSIX) -- Part 1: System Application Program Interface (API) [C Language_, IEEE Std. 1003.1, ISO/IEC 9945-1. International Organization for Standardization, ISO 9899:1990 (see question 11.2). [ISO] Brian W. Kernighan and P.J. Plauger, _The Elements of Programming Style_, Second Edition, McGraw-Hill, 1978, ISBN 0-07-034207-5. (邦訳:プログラム書法第2版 共立出版) Brian W. Kernighan and Dennis M. Ritchie, _The C Programming Language_, Prentice-Hall, 1978, ISBN 0-13-110163-3. [K&R1] (邦訳:プログラミング言語C 共立出版) Brian W. Kernighan and Dennis M. Ritchie, _The C Programming Language_, Second Edition, Prentice Hall, 1988, ISBN 0-13-110362-8, 0-13-110370-9. [K&R2] (邦訳:プログラミング言語C 第2版 共立出版) Donald E. Knuth, _The Art of Computer Programming_. Volume 1: _Fundamental Algorithms_, Second Edition, Addison-Wesley, 1973, ISBN 0- 201-03809-9. Volume 2: _Seminumerical Algorithms_, Second Edition, Addison-Wesley, 1981, ISBN 0-201-03822-6. Volume 3: _Sorting and Searching_, Addison-Wesley, 1973, ISBN 0-201-03803-X. [Knuth] (邦訳:基本算法) Andrew Koenig, _C Traps and Pitfalls_, Addison-Wesley, 1989, ISBN 0-201- 17928-8. [CT&P] (邦訳:Cの落とし穴 トッパン) Stephen K. Park and Keith W. Miller, "Random Number Generators: Good Ones are Hard to Find," _Communications of the ACM_, Vol. 31 #10, October, 1988, pp. 1192-1201 (also technical correspondence August, 1989, pp. 1020-1024, and July, 1993, pp. 108-110). P.J. Plauger, _The Standard C Library_, Prentice Hall, 1992, ISBN 0-13- 131509-9. Thomas Plum, _C Programming Guidelines_, Second Edition, Plum Hall, 1989, ISBN 0-911537-07-4. William H. Press, Saul A. Teukolsky, William T. Vetterling, and Brian P. Flannery, _Numerical Recipes in C_, Second Edition, Cambridge University Press, 1992, ISBN 0-521-43108-5. Dale Schumacher, Ed., _Software Solutions in C_, AP Professional, 1994, ISBN 0-12-632360-7. Robert Sedgewick, _Algorithms in C_, Addison-Wesley, 1990, ISBN 0-201- 51425-7. Charles Simonyi and Martin Heller, "The Hungarian Revolution," _Byte_, August, 1991, pp.131-138. David Straker, _C Style: Standards and Guidelines_, Prentice Hall, ISBN 0-13-116898-3. Steve Summit, _C Programming FAQs: Frequently Asked Questions_, Addison- Wesley, 1995, ISBN 0-201-84519-9. [The book version of this FAQ list.] Sun Wu and Udi Manber, "AGREP -- A Fast Approximate Pattern-Matching Tool," USENIX Conference Proceedings, Winter, 1992, pp. 153-162. 改訂されたIndian Hillsスタイルガイド(質問17.9参照)にも参考文献の一 覧が載っている。質問18.10も参照のこと。 謝辞 直接にあるいは間接的にこのFAQに貢献してくれた以下の皆さんに感謝する。 Jamshid Afshar, David Anderson, Tanner Andrews, Sudheer Apte, Joseph Arceneaux, Randall Atkinson, Rick Beem, Peter Bennett, Wayne Berke, Dan Bernstein, Tanmoy Bhattacharya, John Bickers, Gary Blaine, Yuan Bo, Mark J. Bobak, Dave Boutcher, Alan Bowler, Michael Bresnahan, Walter Briscoe, Vincent Broman, Stan Brown, John R. Buchan, Joe Buehler, Kimberley Burchett, Gordon Burditt, Scott Burkett, Burkhard Burow, Conor P. Cahill, D'Arcy J.M. Cain, Christopher Calabrese, Ian Cargill, Vinit Carpenter, Paul Carter, Mike Chambers, Billy Chambless, C. Ron Charlton, Franklin Chen, Jonathan Chen, Raymond Chen, Richard Cheung, Steve Clamage, Ken Corbin, Ian Cottam, Russ Cox, Jonathan Coxhead, Lee Crawford, Nick Cropper, Steve Dahmer, Andrew Daviel, James Davies, John E. Davis, Ken Delong, Norm Diamond, Bob Dinse, Jeff Dunlop, Ray Dunn, Stephen M. Dunn, Michael J. Eager, Scott Ehrlich, Arno Eigenwillig, Yoav Eilat, Dave Eisen, Joe English, Bjorn Engsig, David Evans, Clive D.W. Feather, Dominic Feeley, Simao Ferraz, Chris Flatters, Rod Flores, Alexander Forst, Steve Fosdick, Jeff Francis, Ken Fuchs, Tom Gambill, Dave Gillespie, Samuel Goldstein, Tim Goodwin, Alasdair Grant, Ron Guilmette, Doug Gwyn, Michael Hafner, Darrel Hankerson, Tony Hansen, Elliotte Rusty Harold, Joe Harrington, Des Herriott, Guy Harris, John Hascall, Ger Hobbelt, Jos Horsmeier, Syed Zaeem Hosain, Blair Houghton, James C. Hu, Chin Huang, David Hurt, Einar Indridason, Vladimir Ivanovic, Jon Jagger, Ke Jin, Kirk Johnson, Larry Jones, Arjan Kenter, Bhaktha Keshavachar, James Kew, Darrell Kindred, Lawrence Kirby, 北野 欽一, Peter Klausler, Andrew Koenig, Tom Koenig, Adam Kolawa, Jukka Korpela, Ajoy Krishnan T, Jon Krom, Markus Kuhn, Deepak Kulkarni, Oliver Laumann, John Lauro, Felix Lee, Mike Lee, Timothy J. Lee, Tony Lee, Marty Leisner, Don Libes, Brian Liedtke, Philip Lijnzaad, Keith Lindsay, Yen-Wei Liu, Paul Long, Christopher Lott, Tim Love, Tim McDaniel, J. Scott McKellar, Kevin McMahon, Stuart MacMartin, John R. MacMillan, Andrew Main, Bob Makowski, Evan Manning, Barry Margolin, George Matas, Brad Mears, Wayne Mery, De Mickey, Rich Miller, Roger Miller, Bill Mitchell, Mark Moraes, Darren Morby, Bernhard Muenzer, David Murphy, Walter Murray, Ralf Muschall, Ken Nakata, Todd Nathan, Taed Nelson, Landon Curt Noll, Tim Norman, Paul Nulsen, David O'Brien, Richard A. O'Keefe, Adam Kolawa, Keith Edward O'hara, James Ojaste, Hans Olsson, Bob Peck, Andrew Phillips, Christopher Phillips, Francois Pinard, Nick Pitfield, Wayne Pollock, Polver@aol.com, Dan Pop, Claudio Potenza, Lutz Prechelt, Lynn Pye, Kevin D. Quitt, Pat Rankin, Arjun Ray, Eric S. Raymond, Peter W. Richards, James Robinson, Eric Roode, Manfred Rosenboom, J. M. Rosenstock, Rick Rowe, Erkki Ruohtula, John Rushford, Kadda Sahnine, 坂本 智彦, Matthew Saltzman, Rich Salz, Chip Salzenberg, Matthew Sams, Paul Sand, DaviD W. Sanderson, Frank Sandy, Christopher Sawtell, Jonas Schlein, Paul Schlyter, Doug Schmidt, Rene Schmit, Russell Schulz, Dean Schulze, Chris Sears, Peter Seebach, Patricia Shanahan, Aaron Sherman, Raymond Shwake, Peter da Silva, Joshua Simons, Ross Smith, Henri Socha, Leslie J. Somos, Henry Spencer, David Spuler, Frederic Stark, James Stern, Zalman Stern, Michael Sternberg, Alan Stokes, Bob Stout, Steve Sullivan, Melanie Summit, Erik Talvola, Dave Taylor, Clarke Thatcher, Wayne Throop, Chris Torek, Steve Traugott, Ilya Tsindlekht, Andrew Tucker, Goran Uddeborg, Rodrigo Vanegas, Jim Van Zandt, Wietse Venema, Tom Verhoeff, Ed Vielmetti, Larry Virden, Chris Volpe, Mark Warren, Alan Watson, Kurt Watzka, Larry Weiss, Martin Weitzel, Howard West, Tom White, Freek Wiedijk, Tim Wilson, Dik T. Winter, Lars Wirzenius, Dave Wolverton, Mitch Wright, Conway Yee, Ozan S. Yigit, and Zhuo Zang。 , who have contributed, directly or indirectly, to this article. Thanks to the reviewers of the book-length version: Mark Brader, Vinit Carpenter, Stephen Clamage, Jutta Degener, Doug Gwyn, Karl Heuer, and Joseph Kent。 書籍版のreviewerのMark Brader, Vinit Carpenter, Stephen Clamage, Jutta Degener, Doug Gwyn, Karl Heuer, and Joseph Kentに感謝する。 特にKarl HeuerとJutta DegenerとMark Braderに、よりよいFAQのリストを作 るという終わりのない目的追求のために私の好みを物ともせず、ときには私の 我慢を越えた水先案内をしてくれたことに感謝する。 Special thanks to Karl Heuer, Jutta Degener, and particularly to Mark Brader, who (to borrow a line from Steve Johnson) have goaded me beyond my inclination, and occasionally beyond my endurance, in relentless pursuit of a better FAQ list. Steve Summit scs@eskimo.com この記事の著作権は1990年から1996年に渡ってSteve Summitに帰属す る。書籍『C Programming FAQs: Frequently Asked Questions』の内容を著者 と出版社の許可の元、社会への貢献のために使用している。このFAQは書籍版 を補足することを意図としている。内容は各国の著作権法によって守られる。 (訳注:日本語版の著作権は北野 欽一に帰属する。) The content is made available here and may be accessed freely for personal use but may not be republished without permission. Except as noted otherwise, the C code in this article is public domain and may be used without restriction. このFAQ内のCのコード(vstrcat(), error()など)はパブリックドメインで、何 の制限もなく使用してよい。 This article is Copyright 1990-1996 by Steve Summit. Content from the book _C Programming FAQs: Frequently Asked Questions_ is made available here by permission of the author and the publisher as a service to the community. It is intended to complement the use of the published text and is protected by international copyright laws. The content is made available here and may be accessed freely for personal use but may not be republished without permission. Except as noted otherwise, the C code in this article is public domain and may be used without restriction. 日本語版の謝辞 日本語版を出すにあたって以下の方に、様々な形で助けてもらいました。感 謝してます。 voidさん、川下 敬之さん、野口 恭子さん、遥夢さん、飯間 昇さん、星野 浩 志さん、結城 義敬さん、田辺 良則さん、坂本 智彦さん、フィンローダーさ ん、青木 和麻呂さん、太田 純さん、熊谷 典大さん(順不同) 最後に原著者のSteve Summitさんに、私の細かい質問に一々丁寧に答えても らったことを感謝します。