プログラミング実習II情報 教科書の正誤表†
『C言語によるプログラミング 基礎編 第3版』(1刷)の明らかな誤植だけでなく、初学者には混乱の元になる無駄な記述を以下に指摘します。
- p.61 上から2行目
- 『最初の31文字までが有効』→『少なくとも最初の31文字までが有効』
環境依存で、もっと長い変数名を区別することもあります。
- p.61 上から8--9行目
- 『同じ変数として扱われる』→『同じ変数として扱われる可能性のある』
環境によっては区別されます。
- p.64 表3.3
- 「使用ビット数(最低保証)」について。浮動小数点数の使用ビットの規定は、言語仕様にはないはずです。(10進数での有効精度の桁数 ???_DIG の規定はあります。)特に long double は、精度が double 以上であることは保証されていますが、少なくとも使用ビット数の保証値は『80』ではなく、VisualStudioのあるバージョンのように『64』(つまりdoubleと同一)である環境も見受けられます。
- p.64 表3.4
- signedで表せる値の最小値が、-32768のように -2^n になるのは、マイナスを「2の補数」で実現した場合に限ります。C99では他に「1の補数」と「符号と絶対値」の2通りの実現方法が想定されており*1、この場合は最小値が -(2^n-1)(つまり最大値の符号違い)になります。
- p.65 図3.3の4行上
- 『(この表現方法はコンピュータによって異なります)』→『(この表現方法もコンピュータによって異なります)』
あたかも整数型と浮動小数点で状況が異なるかのような言い回しですが、整数値のマイナス表現にも3通りのバリエーションがあります。(p.68 の Coffe Break 3.4 で、「2の補数」であることが決まっているかのような説明も間違いです。)
- p.65 図3.3
- 浮動小数点の内部表現には、「符号部」を含める必要がありますが、この図では抜けています。
- p.65 表3.5
- double の正の小さな値の 2.2251e-308 は正規化数に限った場合で、精度が落ちてよければ4.9e-324まで表せます。負の値や、float, long double でも同様に、より0に近い値まで表せます。
- p.66 3行目
- 『精度がよい』→『精度が同じかよい』
- p.66--67 coffee break 3.2 内
- 「最小値」という単語が、文脈によって2通りの意味で用いられています。「doubleの最小値」には0より大きい正規化数という前提がついているのに対して、「intの最小値」にはその前提がありません。なお、前提なしの「doubleの最小値」は -DBL_MAX です。
- P.67 coffee break 3.3
- intのビット数は、「アーキテクチャで提供される自然の大きさ」(K&R第2版日本語版)として選ばれます。演算が高速であることは、(考慮すべきですが)絶対的な条件ではありません。
- p.78 上から4行目
- 『%lf』→『%Lf』
printf() の %f と %lf の動作は同じです。long double には %Lf を使います。
- p.78 下から1行目
- 『全体で15桁、小数点以下3桁』→『全体で少なくとも15文字、小数点以下がちょうど3桁』
15文字で足りない場合は、必要な文字数まで自動的に増やして表示します。また、全体の文字数には、空白やピリオドも含めるので、数値の「桁」数とは違います。
- p.79 上から2行目
- 『全体で15桁』→『全体で少なくとも15文字』
- p.110 リスト4.1の18行目
- 『Taro』→『Jiro』
- p.150 リスト4.11の07行目
- 『double sine, cosine;』→『(削除)』
13行目と14行目の変数定義だけで十分です。
- p.152 リスト4.12の05行目
- 『当たりの回数』→『当たりの数値』
- p.184 リスト5.6の直前の段落からリスト5.7まで
- 『また、関数の定義では、その多くを省略することができます...』→関数の定義では、C言語の旧規格との互換性のために、省略できるキーワードがいくつかありましたが、省略するメリットはありませんのできちんと記述するようにしましょう。
- p.184 脚注13
- 『引数を省略すると、void型の引数となります。』→『引数を省略すると、引数の型チェックがされなくなり、どんな引数で呼び出してもエラーになりません。』
voidの省略が「引数なし」の意味になるのは、C++やJavaなど、他の言語の場合です。
- p.195 リスト5.12
- 5行目で定義された変数a,b,cが、初期化されないまま9行目で使用されています。『int a=1, b=2, c=3;』のように初期化しておきましょう。
- p.195 リスト5.12 と p.196 2段落目
- プロトタイプ宣言は、関数の一覧の役目もあるので、7行目のようにローカル変数と一緒に宣言して埋もれさせるようなことはせず、#include の直後の行(例えば2行目)に書き並べるのが標準手法です。
- p.196 5.2.3節 1行目
- 『maz(24,12)』→『max(24,12)』
- p.199 下から3行目
- 『総省選択』→『総称選択』
- p.200 リスト5.16の6行目
- 『_Generic((X, Y), ...』→『_Generic((Y), ...』
_Generic() の総称選択に使える値は1個だけです。(X, Y) としても、コンマ演算子(逐次評価)によって X が無視されて Y のみが使われるので、コンパイラによっては警告されます。X,Y のペアで判定されているかと誤解させそうな表記 (X,Y) はやめておくべきでしょう。
- p.211 中央あたりの太字部分
- 『(global valiable)』→『(global variable)』
- p.233 リスト6.6の06行目
- 『int i;』→『(削除)』
もう使われてないようです。
- p.236 下から7行目
- 『オプション(-lm)をつけてコンパイルする必要があります。』→『オプション(-lm)の必要な場合があります。』
Cygwin のように、gcc でも -lm の不要な環境があります。
- p.238 中央の数式
- 分散の式の最後から2番目の項がおかしいようです。(式変形を追いかけきれません。)また最後の項は(間違いではありませんが)、カッコの位置を次のように移動したほうが、次のページのプログラムの動作に近くなります。(あるいは、カッコをなくすのもよいでしょう。)
- p.249 上から2行目
- 『動的に配列サイズを変更することができるのです。』→『条件が揃えば動的に配列サイズを変更することができるのです。』
可変長配列は、C99準拠のコンパイラで、しかもローカル変数のみサポートされます。C11ではオプション機能に格下げになりましたし、例えばVisual C++ではサポートされていません。配列の要素数はマクロ定数にしておくのが基本だと理解してください。
- p.259 リスト6.14の14行目
- 『int max_dis_seat[2];』→『int max_dis_seat[2] = {-1, -1};』
定義と同時に(何らかの値で)初期化するのが標準的な作法です。この初期化をしないと、コンパイラによっては42行目で初期化されない可能性があると警告されます。
- p.259 リスト6.14の26行目
- 『j = 0;』→『j = i + 1;』(forの初期化式)
本文の「実はバブルソートのときに使ったforループの回り方とまったく同じ」を信じるなら、p.248のリスト6.10の20行目は j = i + 1 になっていますし、図6.17もi以前は比較が不要である説明にも見えるので、j = i + 1が正解でしょう。さらに、この書籍の第2版では、 j = i + 1 になっていました。ただし、同じ変化が p.335 のリスト9.5の27行目にもあるので、j = 0 にして無駄な動作をさせたのは、意図的にも思えるのですが、その理由までは推し量れません。
- p.263 上から(ソースコードを含めて)10行目
- 『/* Case2 */ int a[3][2] = {1,2,3,4,5,6}; と定義することもできます。』→『int a[3][2] = {1,2,3,4,5,6}; という記述はエラーにはなりませんが、「初期化子の周りに中括弧がありません」といった警告が出ることがあります。』
- p.273 リスト7.5の08行目
- 『2261』→『2265』(2ヶ所)
ハートマークの線画(♡)か塗りつぶし(♥)かの違いですが、すぐ下の実行結果と食い違っています。
- p.274 リスト7.6の07行目
- 『unsigned int len;』→『size_t len;』
strlen()は size_t 型です。(もっとも、len を int 型にして、12行目の代入を(int)でキャストするほうが、修正箇所が少なくなります。)
- p.274 リスト7.6の13行目
- 『%d』→『%zu』
上記の修正をしていないとしても、len は unsigned int でしたから、少なくとも %u にする必要がありました。(len を int にしていれば、この修正は不要です。)
- p.274 本文の2行目
- 『1行といっても、その行の最後の改行(\n)は \0 に置き換えられて message に代入されます。』→『(削除)』
この置換えをするのは gets です。fgets は置き換えを行いません。
- p.274 リスト7.7の18行目
- 『unsigned int』→『int』
str_length() は int 型です。(標準関数のstrlen()とは異なります。)
- p.275 本文の最後の行
- 『txt1とtxt2が同じなら0を返す関数です』→『txt1とtxt2を辞書順で大小比較して、txt1が大きければ正、小さければ負、同じなら0を返す関数です』
比較関数が正・負・0の3通りの値を返すのは、よくある設計手法です。ただ、自前の str_cmp() の実装を単純化するために、0・0以外の2通りに限定する意図も感じられます。
- p.275 リスト7.8の08行目
- 『unsigned int len;』→『(削除)』
もう使われてないようです。
- p.277 リスト7.9の10行目
- 『str1[i] - str2[i]』→『(unsigned char)str1[i] - (unsigned char)str2[i]』
char型の実体がsigned charの環境では、本文5行めの「このとき、str1の文字が大きければ正の数、...」の符号がおかしくなる場合があります。このようにキャストすれば、環境に依存せず正常になります。もっとも、(標準関数とは違って)str_cmp() が同一判定のみ行うとするならば、この符号の違いは問題になりません。
- p.275 リスト7.9の27行目
- 『unsigned int len;』→『(削除)』
もう使われてないようです。
- p.283 Coffe Break 7.2
- strcpy_s() や strcat_s() は C11 で導入された関数ですが、オプショナル機能と位置づけられているため、例えば gcc では使えません。
- p.279 リスト7.11の03行目
- 『int str_cpy(...)』→『void str_cpy(...)』
str_cpy()関数が常に0を返すのは無駄ですから、void型にして、11行目を「return;」にしたほうがよいでしょう。p.281のリスト7.13にも、同じことが言えます。意味もなく0を返す、という関数設計は、void型のなかった時代の古い習慣です。
- p.295~297 リスト8.3, 8.4, 8.5
- 『%d』→『%zd』
sizeof(int) などの値を printf() で表示するためには "%zd" の書式指定子を使うか、値を(int)でキャストする必要があります。
- p.298 リスト8.6の11行目から13行目
- 『%d』→『%td』
ポインタどうしの減算の演算結果は ptrdiff_t 型になり、この値を表示するためには "%td" の書式指定子を使うか、あるいは値を(int)でキャストする必要があります。
- p.301 リスト8.9の6行目
- 『int i;』は不要です。
- p.309 上から8行目
- 『int i;』→『int i = 0;』
リスト7.11を再掲載しているはずですが、このように初期化も抜けてますし、他にもコメントやスペーシングが異なっています。関数名も _cpy と _copy が違います。
- p.309 リスト8.14の7行目
- 『 %s\n" 』→『 "%s\n" 』
最初のダブルコーテーション(")が抜けています。
- p.317 2段落目からp.318 2行目
- 『また、構造体タグの省略について...警告を出すコンパイラも存在します。』→構造体タグを省略すると、その構造体を他の場面で利用できなくなるので、単独では(ほぼ)省略しません。後述の9.5.1節(p.339)のように、typedef と組合せて型名を付ける場合に(ほぼ)省略します。
- p.320 リスト9.1
- 構造体がmain関数内で宣言されていますが(しかも変数定義を同時に行なっていますが)、こうするとこの構造体は通用範囲がmain関数に限定されてしまい、他の関数で用いることができませんので、このような使い方はしません。通常は複数の関数で扱えるように、構造体の宣言は関数の外側(特にプロトタイプ宣言の直前)に記述します。そして変数定義は分離することになります。
- p.335 リスト9.5の17行目
- 『int max_dis_seat[2];』→『int max_dis_seat[2] = {-1, -1};』
p.259 リスト6.14の14行目と同じです。定義と同時に(何らかの値で)初期化するのが標準的な作法です。この初期化をしないと、コンパイラによっては41行目で初期化されない可能性があると警告されます。
- p.335 リスト9.5の27行目
- 『j = 0;』→『j = i + 1;』(forの初期化式)
p.259 リスト6.14の26行目と同じです。
- p.338 ソースコード内の下から3行め(print_address()関数内)
- printf(" zip:%l\n", addr.zip);→printf(" zip:%d\n", addr.zip);
- p.340--345
- 論理型の変数名には、is_selected のように is + 形容詞(過去分詞)にする習慣があり、さらに is を省略する場合もあります。しかし、ここでは selected が laguage_t 型になっていて、紛らわしいです。この用例では受け身にする必要性は見出だせないので select のほうがよいでしょう。
10章†
- p.356 リスト10.3の13行目
- このプログラムは、コマンドライン引数を付け忘れた際のエラー処理が抜けています。引数の付け忘れはよくやることですし、その際は argc = 1 となって、13行目の argv[1] の参照は不正メモリアクセスになります。main() の最初では、argc でコマンドライン引数の個数を検査して、足りなければコマンドの使い方(ヘルプメッセージ)を表示して、プログラムを中断するのが作法です。
- p.364 リスト10.5の18行目
- 『ファイルが見つかりません』→『ファイルが作成できません』
ファイルの書き込みオープンの失敗ですから、見つからないはずがありません。ここではリスト10.10と同じメッセージにしてみましたが、単純に「オープン失敗」のメッセージにして、読み書きの区別をせずに共通化することも多くあります。
- p.371 リスト10.7の25行目
- 『while (fgets(string, STRING_SIZE, fp))』→『while (fgets(string, STRING_SIZE, fp) != NULL)』
動作は正しいですが、表記が一貫していません。18行目では「if ( (fp=fopen()) == NULL)」とNULLとの比較を記述しています。370ページの5行目に書かれている「ファイルの終端に達すると、fgets関数はNULLを返します。」の仕様に合わせて、ここでもNULLとの比較を明示するのがよいでしょう。
- p.371 リスト10.7の29行目
- 『cut+1』→『cnt+1』
- p.373 リスト10.8
- 『if (fp... 』→『if ((fp... 』
3ヶ所のカッコ始まりが抜けています。
- p.374 リスト10.9
- 『if (fp... 』→『if ((fp... 』
3ヶ所のカッコ始まりが抜けています。
- p.376 リスト10.11
- #include <stdio.h> が省略されているのはともかくとして、gets() はC11で廃止されました。セキュリティホールがあるので、使ってはいけません。
11章†
変数名や関数名の命名規則に、C言語では「すべて小文字、単語は_をはさんで連結する」というスネークケースが用いられるのが一般的ですが、「単語の先頭を大文字にして隙間なくつなげる」というキャメルケースや、大文字小文字の使い分けルールの読み取れない部分が残っています。(第2版よりは減りました。)スネークケースで統一されるよう、変数名は以下のように読み替えるのがよいでしょう。
- 383ページ
- 『MonthlyRecord_t』→『monthly_record_t』
『DailyRecord_t』→『daily_record_t』
また、このように typedef と組み合わせる場合、構造体にタグ名(MonthlyRecord_T や DailyRecord_T)をつける必要はありません。
- 387ページ
- 『MonthlyRecord_t』→『monthly_record_t』
『dayCount』→『day_count』
『dataFile』→『data_file』
他にも多数あります。
- 389ページ リスト11.2の03行, 394ページ リスト11.5の107行
- 『char dateString[strlen("yyyy/mm/dd") + 1];』→『char dateString[sizeof("yyyy/mm/dd")];』
関数の戻り値は定数ではないので、可変長配列のサポートされてない環境(例えばVisual C++)ではエラーになります。sizeof演算子は定数ですし、ヌル文字分の+1を含んでいるので、このほうがよいでしょう。定数にできるものを、わざわざ変数にするメリットは見つかりません。
もっと言うと、読み込むデータの日付部分がもっと長い可能性もあるので、通常は100のような余裕をもった値にしておき、リスト11.2の07行やリスト11.5の111行のsscanf() で読み込む際には、長さの上限を "%99[^,],%lf,%lf" のような書式で指定します。