* 『C言語によるプログラミングの基礎』の疑問点 [#w138a2a8] 授業の教科書『C言語によるプログラミングの基礎』(田中敏幸著、コロナ社)の非公式で勝手な正誤表です。第1版第2刷(2004年1月15日発行)の記述に基づいています。 このページの内容については、まったく保証しません。 ** 2章 [#n2209313] *** 2.3 [#ee49849f] - p.11 「#include <stdio.h> これはプリプロセッサといいプログラムの中で使う関数を指定する部分である。 … プリプロセッサとしてどのようなものを用いるかは」 -- プリプロセッサ(Preprocess''or'')は、プログラムの一部のことではなくて前処理を行うソフトウェアのことである。よって、この部分は「#include <stdio.h> これはプリプロセッサ''指令''といい … ''ヘッダファイル''としてどのようなものを用いるかは」とするべきだろう。 - 同 「どのような場合でもstdio.hは必ず宣言しておかなければならない」 -- 初心者向けの説明とは思うが、そのソースファイルでprintfなどの標準入出力関数を用いないときはstdio.hは不要である。 - p.12 「関数の最後には必ず戻り値を返す関数が必要となる。この戻り値を返す関数がreturnである」 -- returnは関数ではなく制御文である。 *** 2.4 [#v168df92] - p.16 「C++が開発されたときに、改行までを一つの単位としてコメント文にする方法も付け加えられた」 -- 誤りとはいえないが、「//」型のコメントはC言語の先祖にあたるBCPLから復活したものである。 ** 3章 [#u4064e54] *** 3.8 [#x5a872fa] - p.36 リスト3-9 -- 12行目に「;」が抜けている。 ** 4章 [#n6b92a75] *** 4.2 [#b787ab9a] - p.42 「Visual C++ではプログラム中に実数値を書くとすべて倍精度とみなされる」 -- Visual C++だけでなく、倍精度(double)になるのが標準規格である。 - p.44 「条件が満たされたとき(条件の値が1のとき)」 -- 初心者向けの説明とは思うが、真と判定されるのは条件の値が0以外(非0)のときである。 *** 4.3 [#be8e1b7b] - p.56 「インクリメント演算子(++)とデクリメント演算子(--)は、浮動小数点(実数)の変数に対して用いることはできない」 -- できる。1.0を加減する。 *** 4.4 [#g3865b00] - p.68 「最近では、プログラミングの際に、特殊な場合を除いてgoto文は使わないというのが一般的になっている」 -- これを「最近」というのはどうだろうか…。(有名な論文 [[Go to statement considered harmful>http://portal.acm.org/citation.cfm?id=362929.362947]]) ** 5章 [#yc1a28b7] *** 5.2 [#a5668739] - p.82 「これはPascalなどのプログラミング言語で用いられた書法だが、最近では、C言語でもこのようにmain関数を最後に置くプログラムがよく書かれるようになった」 -- Pascalについては言語仕様で定められている。CについてはANSI Cの制定(1989年)以来、増えたのではないかと思われるが、これも「最近」や「よく」という表現がどの程度のことを言っているのだろうか…。 - p.86 「階乗など一部のアルゴリズムに使われているだけのようである」 -- 一部かもしれないが、重要なアルゴリズムに再帰を使うものは少なくない。よく使われるものにフォルダ(ディレクトリ)の探索がある。さらに、ほとんどループを使わず、代わりに再帰を使うプログラミング言語もある(すべてのループは再帰でも表現可能である)。 *** 5.3 [#yd3a6f00] - p.89 「5.3.2 外部変数(extern)」の説明 -- 教科書では、「各関数の中でもexternを用いた使用宣言をするのが正式の使用方法である」と書いてあるが、そういうことをしている人は(それが正式だと認識している人も)、皆無に等しいと思う。 -- これに関し、以前ここに、externは外部リンケージ(external linkage)の変数の使用を宣言するもので、教科書のような使い方は本来の使い方ではないと書いた。しかし、『プログラミング言語C』(K&R)の第1章(1.10)にも教科書と同じような使用例があったのでそれは訂正する。 -- 教科書の記述通り、Cにおける「外部変数」(external variable)はグローバル変数と同義であり、その使用宣言として(外部リンケージでなくても)externを使ってよい。 -- ただし、K&R(第2版)でも、すぐに「普通のやり方は、ソース・ファイルの最初ですべての外部変数を定義してしまい、''extern宣言をすべて省く''」と書いてある。やはり、(正式かどうかはともかく)普通は、externはほとんど外部リンケージの変数の使用を宣言するためだけに用いられているはずである。 --ちなみに、外部リンケージとextern宣言は関係ない概念だということで、面白いことに下記のようにexternを内部リンケージの変数に使っても正しいらしい。 #include <stdio.h> int main(void) { extern int e; /* 前方参照のためのextern */ extern int i; /* 内部リンケージでもextern */ printf("%d %d\n", e, i); return 0; } int e = 1; /* 外部リンケージの変数 */ static int i = 2; /* 内部リンケージの変数 */ *** 5.4 [#wbae2815] - p.95 「stdio.hはプログラムの最初に必ずインクルードしなければならない」 -- そのソースファイルのなかで、printfなどの標準入出力関数を用いないときはstdio.hは不要である。 - p.96 「#define PISQR PI*PI」 -- なぜか等幅フォントになっていない。 *** 5.5 [#pf1d912a] - p.102 リスト5-16 -- 変数宣言に int i が抜けている。 -p.104 「理由についてははっきりしないが、2次元以上の配列で(1)の方法を用いると、プログラムが正常に実行されない場合がある」 -- もし本当ならコンパイラのバグである。たとえば、Visual C++のどれかのバージョンでは、これが正常に実行されないのだろうか? ** 6章 [#d2f600da] *** 6.1 [#wa1e55f0] - p.129 「読み込み更新"r+"は、まずファイルの読み込みを行って、その後で書き出しを行う」 -- "r+"はファイルの内容を保持したまま、読み書き可能でオープンするものである。いったんオープンすれば、読み込みと書き出しの順番は自由なはずである。 - p.131 「オープンしたファイルの操作が終了したら、必ずクローズしなければならない」 -- これは補足になるが、規格上、オープンされたファイルはプログラム終了時にすべて自動的にクローズされる。よって、あえてクローズしなくてもプログラムは正常に動作する。 -- ただし、一度にオープンできるファイル数はOSや環境によって制限があるので、いちいちクローズしたほうが安全であることには変わりない。 - p.133 実行結果6-2 -- 「a=」「b=」「c=」が抜けている。 *** 6.2 [#n6b9aca9] - p.144 リスト6-6 -- 「/* 丸め誤差を考慮 */」としているが、浮動小数点数をループカウンタにして誤差が累積するような場合、比較で等号を含めるべきでない(「<=」の代わりに「<」を使うべき)。 -- さらに言えば、ループの中では、小数がきっちり2進数で表せずに誤差が累積するような計算はしないほうがよい。特に正確性が必要な計算では、浮動小数点数をループカウンタに使うべきでない。 ** 7章 [#ab20accd] *** 7.2 [#x5cf555e] - p.151 「コンピュータの処理能力が低く、… プログラムの高速化のために*(a+i)のようなポインタ演算が好まれていた。初期のCプログラミングの本では、できるだけポインタ表現を使うよう心がけていたものが多く見られる。現在では、配列a[i]という書式を用いても …」 -- *(a+i)とa[i]についていえば、今のコンパイラでも昔のコンパイラでも翻訳結果に違いはないだろう(アドレスレジスタ+インデックスレジスタ)。*p++とa[i++]のことならばそういうこともあるかもしれないが、Cを使い慣れていれば特にポインタ表記のほうが分かりにくいということはないと思う。 - p.154 「sizeof()演算子」 -- 「()」は演算子の一部ではないので、「sizeof演算子」でよい。 - p.165 リスト7-13 -- 「#include <stdlib.h>」がない。 -- 関数keisanで、スタック上のアドレス(自動変数bのアドレス)を呼び出し元の関数にreturnしている。関数を抜けるとbへのアクセスは保証されない。対策として、keisanの中のbの宣言にstaticをつけるか、bはmallocによってヒープに確保しなければならない。 *** 7.3 [#o855492c] - p.172 「この場合にはポインタと配列はまったく同じことを意味する」 -- 配列名はその先頭要素へのポインタに適宜変換されるが、ポインタとまったく同じではない。具体的には、sizeof演算子での扱いが違う。sizeof(array)は配列全体のサイズであるが、sizeof(ptr)はポインタ変数1個のサイズになる。また、&arrayと&ptrも違う意味である。 *** 7.5 [#i483034c] - p.181 「s0.rank → s0->rank」 -- 「s0.rank → s->rank」の誤りではないか。