「C言語 ポインタ完全制覇」正誤表

このページは、拙書「C言語 ポインタ完全制覇」技術評論社 ISBN4-7741-1142-2の正誤表です。

日付が2001年2/2以前の誤りは、第2刷で修正されています。

日付が2001年3/28以前の誤りは、第3刷で修正されています。

日付が2001年5/29以前の誤りは、第4刷で修正されています。

日付が2001年12/1以前の誤りは、第5刷で修正されています。

日付が2002年11/30以前の誤りは、第6刷で修正されています。

日付順のインデックス


p.13(2刷で修正)

int *a; と int a[]; は,どのような場合に置換可能なのか?

int *a と int a[] は,どのような場合に置換可能なのか?

間違いというわけでもないですが、セミコロンが付いていると、 (新しい関数定義の形式を使う限り)置換可能なケースがなくなってしまうので...


p.25

また、JISハンドブックとしても入手できます。 『情報処理 プログラミング言語編』に含まれています。 書店で注文もできますし、大型書店なら店頭にあるかもしれません。

正しくは「プログラム言語編」であるというのもありますが、 それ以前に、JISハンドブックの2001年度の全面改訂に伴い、 「プログラム言語編」に相当する部分はハンドブックから外されたようです。

同じだけの情報を全部規格書で取り寄せようとすると、 いくらかかることやら。とほほ。


p.26(2刷で修正)

(5)移植性を犠牲にしない範囲で、高速にしよう(Make it fast, even if it is not guaranteed to be portable)

誤訳です。正しくは、

(5)たとえ移植性が保証されなくても、高速にしよう。(Make it fast, even if it is not guaranteed to be portable)

です。

これはかなり情けないポカですね。 Rationaleでは、続きにこれに関する説明が書いてありまして、 そこも読んでたはずなのですが...


p.28(2刷で修正)

たとえば、Pascalでは、Cのprintf()相当のことを行なうのに writeという命令を使います。これはPascalのキーワードの1つです。

write()は、Pascalの標準手続きであって、 キーワード(予約語)ではありません。

確かに、write()は可変個の引数を取ります (Pascalにおける通常の手続きは可変個の引数が許されないにもかかわらず)。

JIS X3008 6.6.4.1の備考によれば、

標準手続き及び標準関数は、手続き又は関数の一般規則には必ずしも従わない。

とされています。

また、「参考1 構文規則」の中の「手続き呼び出し文」の中に 「write パラメタ並び」というのが入っていたりします。 つまり、構文上、特別扱いをしているわけです。

しかし、やっぱりキーワード(予約語)ではありません。


p.35(2刷で修正)

ということは、hogeとpiyoとhoge_pがどんな順で並んでいるのかを意識しても しかたがないわけですから、Fig.1-2は、Fig.1-3のように 表現することもできます。

(前略)Fig.1-3のように表現することもできます。

p.40

「ほげを考えるページ」は、現在オリジナルが公開されていませんので、 作者の吉田さんの許可を得まして、私のページに転載させていただきました。

URLは、
http://member.nifty.ne.jp/maebashi/programmer/hoge.html(移転済み)
http://kmaebashi.com/programmer/hoge.html(移転後)
です。


p.43 8行目

今回のサンプルプログラムでは、hoge_pは配列を指していませんから、 これに加算を行なうことは、それだけで規格に反しています。

一応規格では、

配列でないオブジェクトへのポインタは、 要素型としてそのオブジェクトの型をもつ長さ 1の配列の最初の要素へのポインタと同じ動きをする

と書いてありますので、 ふたつ超えた所に向けない限り問題なさそうです。


p.43(2刷で修正)

List1-2の9行目

hoge_p = hoge;

hoge_p = &hoge;

p.67 List1-5

#include <stdio.h>

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>

ctype.hとstdlib.hが抜けていました。


p.67 List1-5(その2)

int len = 0;

別にバグではありませんが、 後の方でlen = 0と書いているのでこの初期化は不要です。


p.78 下から2行目

scanf()は、戻り値として、変換に
成功した変換指定子の数を返しますが、

scanf()は、戻り値として、代入に
成功した変換指定子の数を返しますが、

代入抑止文字*を使用した場合、変換の数と代入の数は食い違います。


p.80(2刷で修正)

C言語の変数には、段階的なスコープ(有効範囲)があります。

C言語では「scope」と「linkage」という言葉は別々に定義されていて、 ブロックで囲むのはscope、staticとexternはlinkage(結合)を制御します。

プログラマの感覚としては、「staticを付けるとスコープが ファイル内に限定される」ように感じるわけですが、 規格上用語は明確に分けられています。

いわゆるグローバル変数は、scopeがfile scopeで、 linkage(結合)がexternal linkage(外部結合)ということになります。


p.81 下から8行目

Cでは、malloc()関数を使用して

Cでは、malloc()関数を使用して

p.83 List2-2

 7: void func1()

 7: void func1(void)

同様に、

16: void func2()

16: void func2(void)

p.96(2刷で修正)

誤:

1から4までに伸張した分のスタックが,その関数が参照する(後略)

読者の方から、この1と4がどの図の番号かわからないという質問がありました。 これは、この箇条書き自身の番号です。

増刷時には、箇条書きと表記を合わせ、黒の丸数字に変更します。


p.102 図の中

"&d, %s"へのポインタ

"%d, %s\n"へのポインタ

p.104 List2-5の23行目

  va_end();

  va_end(ap);

p.110(2刷で修正)

List2-6 12行目

12:          for ( ; data[right_index] < pivot; right_index--)

12:          for ( ; data[right_index] > pivot; right_index--)

不等号の向きが逆です。

ベンチマークも同一条件でやりなおしました。同一ページより。

私の環境で,5万件のランダムな整数データをソートしたところ, バブルソートでは92秒かかりましたが,クイックソートでは51ミリ秒 (1ミリ秒は1000分の1秒)で済みました.

私の環境で,5万件のランダムな整数データをソートしたところ, バブルソートでは117秒かかりましたが,クイックソートでは65ミリ秒 (1ミリ秒は1000分の1秒)で済みました.

p.116(2刷で修正)

struct BookData_tag *next;
} BookData;

    struct BookData_tag *next;
} BookData;

インデントが変でした。


p.156(2刷で修正)

いままで、
void func(int hoge[2][3]);
とか、
void func(int hoge[][3]);
のように書いていた方も多いかもしれませんが、これらはすべて

いままで、
void func(int hoge[3][2]); ←2と3が逆
とか、
void func(int hoge[][2]); ←3ではなくて2
のように書いていた方も多いかもしれませんが、これらはすべて

p.159 注(2刷で修正)

*細かいことをいうと,規格では「基本型」という言葉は定義して いないようなのですが(後略)

規格ではちゃんと定義しているので、注ごと削除してください。

次のページの列挙型に関する注と思いっきり矛盾してます。


p.160〜161(2刷で修正)

符合(7箇所)

符号ですね(とほほ)。


p.162(2刷で修正)

          :
      struct Man_tag *husband; /* 夫 */
  };

          :
      struct Man_tag *husband; /* 夫 */
          :
  };

ここだけタテの中略記号がありません。


p.163(2刷で修正)

  struct Woman_tag; ←タグだけ先に宣言

  struct Man_tag {
      ...
      struct Woman_tag *wife;  /* 妻 */
      ...
  }

  struct Woman_tag {
      ...
      struct Man_tag *husband; /* 夫 */
      ...
  }

(中略)

  struct Woman_tag {
      ...
      Man *husband; /* 夫 */
      ...
  }

  struct Woman_tag; ←タグだけ先に宣言

  struct Man_tag {
      ...
      struct Woman_tag *wife;  /* 妻 */
      ...
  };

  struct Woman_tag {
      ...
      struct Man_tag *husband; /* 夫 */
      ...
  };

(中略)

  struct Woman_tag {
      ...
      Man *husband; /* 夫 */
      ...
  };

セミコロンが抜けています。


p.176 下から11行目

これは、言語に比べて極端に多いため

これは、言語に比べて極端に多いため

p.177 Table3-5

演算子の優先順位表の?:の結合規則

左から右

右から左

p.183 下から2行目(2刷で修正)

const指定指定した識別子は、

const指定した識別子は、

p.189 11行目

さらに、早期の移行を用意 にするべくデザインされたいくつかの規則は、

さらに、早期の移行を容易 にするべくデザインされたいくつかの規則は、

p.189 注(2刷で修正)

*この宣言は,ANSI C以前の古い形式の宣言ですね.

*これは,ANSI C以前の古い形式ですね.

原文で「in the function declaration」になっていますから、 「関数宣言」と訳すしかないと思うのですが,これは関数「定義」 なので、注の中で「宣言」と連発するのはどうも...


p.191 補足

int a; のようなものは正確には仮定義(tentative definition)であり、 初期化子がつかないと「外部定義」にはなりません。

K&Rのp.39〜41にも同様の説明がありますし、 「仮定義」は「定義」かどうかという問題のような気もしますが、 一応補足しておきます。


p.194 補足

ただし、式の中では、配列はポインタに読み替えらるので

ただし、式の中では、配列はポインタに読み替えられるので


p.201 下から4行目

/* Free BSDのman pageから抜粋 */

/* FreeBSDのman pageから抜粋 */

スペースが余計です。


p.204

間違いになるかどうかはわかりませんが、 式の中で配列名に代入できない理由について議論がありました。 こちらの補足を参照してください。


p.216 List4-5

#include <stdio.h>
#include <stdlib.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

string.hが抜けていました。


p.218 List4-6

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

string.hが抜けていました。


p.223 List4-8

 9: char *read_line(FILE *fp);

7行目で#includeしているヘッダに含まれているので、 不要です(あっても害はないですが)。


p.226 List4-9

#include <stdio.h>

#include <stdio.h>
#include <stdlib.h>

stdlib.hが抜けていました。


p.228 List4-11

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "read_line.h"

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include "read_line.h"

string.hが抜けていました。


p.230 List4-11 73行目

fpから1行読みこむ。ファイルの末尾に来たら、NULLを返す。

fpから1行読みこむ。

List4-6からの直し忘れでした


p.241(3刷で修正)

Fig 4-11の中の一番上の箱の中。

point_num

npoints

p.249 List5-1

#ifndef GET_WORD_H_INCLUDED
#define GET_WORD_H_INCLUDED

#ifndef GET_WORD_H_INCLUDED
#define GET_WORD_H_INCLUDED
#include <stdio.h>

ポリシーに従えば、stdio.hを#includeしなければなりません。


p.253(2刷で修正)

「POINT ヘッダファイルを書く際に必ず守ること!」は、 内容的には補足の一部なので、掛け網部分の中にあるべきです。


p.257(3刷で修正)

List5-7 9行目

9:     printf("%-20s%5d\n",

9:     fprintf(fp, "%-20s%5d\n",

元のままでは、fpを渡している意味がありません。

3刷以降のための追記:

誤:

9:     fprintf(fp. "%-20s%5d\n",

正:

9:     fprintf(fp, "%-20s%5d\n",

3刷で修正を入れる際、コンマの代わりに 誤ってピリオドを入れてしまったようです。


p.261 List5-11 15行目

15:     dest = malloc(sizeof(char) * strlen(src) + 1);

15:     dest = malloc(sizeof(char) * (strlen(src) + 1));

sizeof(char)が1であることは規格で保証されていますから 結果は別に変わりませんが、 わざわざsizeof(char)と書くからにはこうなってないと変ですね。


p.267 List5-14

#include <stdio.h>

#include <stdio.h>
#include <string.h>

p.268(3刷で修正)

List5-14 14行目

14:     while (left < right) {

14:     while (left <= right) {

20行目

20:         right = mid;

20:         right = mid - 1;

元のままだと、場合によっては left == rightになった瞬間returnしないでループを抜けます。

ソースのミスは罪が重いですね。申し訳ありません(_o_)


p.282 注(2刷で修正)

voidを使うという方法もありますが、 それでは「どんな型を指す可能性があるのか?」がさっぱりわかりませんから、 ソースの可読性を考えればポ<ぷっつり>

この後に、

ソースの可読性を考えればポインタの共用体にすべきです。

と続きます。


p.283(2刷で修正)

  typedef struct {
      Shape shape;
      Shape *prev;
      Shape *next;
  } LinkableShape;

  typedef struct LinkableShape_tag {
      Shape shape;
      struct LinkableShape_tag *prev;
      struct LinkableShape_tag *next;
  } LinkableShape;

p.284(2刷で修正)

  typedef struct {
      Shape *shape;
      Shape *prev;
      Shape *next;
  } LinkableShape;

  typedef struct LinkableShape_tag {
      Shape *shape;
      struct LinkableShape_tag *prev;
      struct LinkableShape_tag *next;
  } LinkableShape;

p.285

  typedef struct Linkable_tag {
      void  *object;
      Shape *prev;
      Shape *next;
  } Linkable;

  typedef struct Linkable_tag {
      void *object;
      struct Linkable_tag *prev;
      struct Linkable_tag *next;
  } Linkable;

p.300

(strncat()も同様です)

削除

JIS X3010 7.11.3.2には、 「文字列の終端を示すナル文字を、常に結果に付加する」 とはっきり書いてありました。

strncpy()とは仕様が異なります。確認不足でした。


p.300 下から6行目

srcからlenに、最大でlen文字だけコピーします。

srcからdestに、最大でlen文字だけコピーします。

p.300(3刷で修正)

strncpy()は、
strcpy(dest, src, len); のように使い、(後略)

strncpy()は、
strncpy(dest, src, len); のように使い、(後略)

p.301(2刷で修正)

「あるプログラマがこんなコーディングをしたそうな.」の次の行。

strncpy(dest, src, strlen(src))

strncpy(dest, src, strlen(src) + 1)

p.304(2刷で修正)

実は、printf()の書式指定子では、%fと書こうが%lfと書こうが、 まったく同じものとして扱われます。 よって、double型も%fで表示可能です。それどころか、 私の使っているコンパイラ(gcc)では、 %lfを使うとコンパイラが警告メッセージを出してくださりやがります。

申し訳ありません。完全に間違ってます。

規格では、printf()の%f指定子は、

double型の実引数を[-]ddd.ddd形式の10進表記に変換する。

とはっきり書いてあります。

また、

l(エル)は、それにd, i, o, u, x又はX変換指定子が続く場合...

と書いてありまして、さらに、

h, l, 又はLを他の変換指定子とともに指定した場合の動作は、未定義とする。

とあります。

つまり、GCCが親切にも警告してくださる%lfは、 やはり未定義なのであって、 警告を出す方が正しいことになります。

暴言を撤回するとともに、GCC関係者にお詫びいたします。


p.310(2刷で修正)

  struct Woman_tag {
      ...
      Man *husband; /* 夫 */
      ...
  }

  struct Woman_tag {
      ...
      Man *husband; /* 夫 */
      ...
  };

p.314(2刷で修正)

  char str[3] = "abc"; ← 間違い.'\0'の分を忘れている

  char str[3] = "abc"; ← '\0'の分を忘れている

char str[] = "abc"; とは意味が違うという点では確かに間違いなのですが、 規格上は間違ってはいないので、 「間違い」と断定すると要らぬ誤解を招きそうです。


p.315 下から4行目(2刷で修正)

すから、"red", "green" "blueyellow"からなる要素数3の配列に

すから、"red", "green", "blueyellow"からなる要素数3の配列に

コンマが抜けていました。


p.316(2刷で修正)

と書くことで,構造体の内容を初期化できます.

以下のように書くことで,構造体の内容を初期化できます.

p.316(その2)(2刷で修正)

と、きちんと対応がとれるように初期化子を書けば,初期化可能です.

以下のように、きちんと対応がとれるように初期化子を書けば,初期化可能です.

「C言語 ポインタ完全制覇」のページに戻る | トップページに戻る