このページは、拙著「C言語 ポインタ完全制覇(第2版)」のサポートページです。
- 書名…「新・標準プログラマーズライブラリ C言語 ポインタ完全制覇」
- 出版社…技術評論社
- ISBN…978-4-7741-9381-6
- 定価…本体2,480円 + 税
技術評論社さんによる紹介ページ
書籍版:
Kindle版:
17年ぶりの改訂版となります
この本は、2001年の発売以来たいへんご好評をいただいた『C言語 ポインタ完全制覇』の改訂版です。
旧版は、最終的に第18刷を数えるロングセラーとなりました。読んでくださる皆様のおかげです。
今回の版では、時代に合わせたり、旧版では不足していたと思われる記述を補ったりという加筆・修正を行いました。
本の内容について
「Cのポインタがよくわからない」人に
※以下は、本書の第0章および「はじめに」から抜粋して組み立てたものです。
Cを学ぶ上で、最大の難関と言われるのが、ポインタです。
そのポインタを学習するにあたって、よく以下のようなことがいわれます。
- 「コンピュータの、メモリとアドレスの概念がわかってしまえば、ポインタなんて簡単だ」
- 「C は低級言語だから、先にアセンブラを勉強したほうがよい」
確かに、Cのポインタを理解するには、先にメモリとアドレスの概念を理解するのが手っ取り早いと思います(アセンブラまで勉強する必要があるかは疑問ですが)。ただし、メモリとアドレスの概念がわかっただけでは、ポインタをマスターすることはできません。メモリとアドレスの概念は、ポインタを理解するうえにおいて、必要条件かもしれませんが、十分条件ではなく、それから始まる長い長い「どはまりの道」の、最初の一歩でしかないのです。
実際に、初心者がCのポインタではまるパターンを見ていると、以下のようなものが多いようです。
- int *a;とポインタ変数を宣言する……まではよいが、このポインタ変数を、ポインタとして使うときにも*aと書いてしまう。
- int &a;のような宣言を書いてしまう(← C++ の話ではない)。
- 「『int へのポインタ』って何なんだろう? ポインタってアドレスなんだろ? int へのポインタだろうが、char へのポインタだろうが、いっしょなんじゃないのか?」
- ポインタに1 足したら、2 バイトとか4 バイトとか進む、ということを習って……「ポインタってアドレスなんだろ? そんなもん1 進むに決まってるんじゃないのか?」
- 「scanf()の"%d"では、変数に& を付けて渡すのに、なんで"%s"では、& を付けなくていいんだろう?」
- 配列名をポインタに代入することを習ったあたりで、配列とポインタを混同してしまい、「領域を確保してもいないポインタを、配列としてアクセスする」とか、「配列名にポインタを代入しようとする」とかいう過ちを犯す。
こういった混乱の原因は、「ポインタはアドレスだ」ということを理解していないところにあるのではありません。本当の理由は、
- C の「奇怪な」宣言の構文
- 配列とポインタの間の「妙な」交換性
にあるのです。
Cの宣言の構文が「奇怪」であるといわれても、ピンとこない方もいるかもしれません。でも、このような疑問を持ったことはないでしょうか?
- C の宣言では、[]は* よりも優先順位が高い。ゆえに、char *s[10];という宣言は、「charへのポインタの配列」を意味する―逆じゃないか?
- double (*p)[3];や、void (*func)(int a);といった宣言の読み方がわからない。
- int *a;は、a を「int へのポインタ」として宣言する。しかし、式の中での* は、ポインタをポインタでなくすほうに働く。同じ記号なのに、意味が逆じゃないか?
- int *aとint a[]は、どのような場合に置換可能なのか?
- 空の[]は、どのような場所で使用することができ、どのような意味を持つのか?
本書は、このような疑問に答えるべく書きました。
ちなみに、正直に告白しますと、私の場合、Cの宣言の構文をちゃんと理解できたのは、Cを使い始めてから、実に数年を経てからでした。
おれだけが人並はずれて頭が悪いんじゃなかろうか、なんて考えるのは嫌ですので、私は、「Cの宣言をきちんと理解しているCプログラマーって、実は意外に少ないんじゃないか?」と思っています。何しろ、私自身、Cの宣言をきちんと理解する前から、仕事でばりばりプログラムを書いていましたから。「Cなんて簡単だよ、おれはポインタもちゃんと理解しているよ」と思っているあなただって、意外と理解はあいまいかもしれません。
たとえば、以下の事実をご存じでしょうか?
- 配列の中身を参照するときには、a[i]のように[]を使うけど、この[]は、配列とはまったく無関係だ。
- C には多次元配列は存在しない。
書店で本書を手に取って、「なんだ? この本はトンデモ本だったのか?」と、そっと棚に戻そうとしたあなた、そういう人にこそ、本書を読んでいただきたいと思います。
――ネットで検索してみると、Cのポインタが難しいという声は今でもたくさん見つかります。
そのような、Cのポインタに苦しんでいる人に伝えたいことがあります。
Cのポインタがわからないのは、あなたが悪いわけじゃなく、単に、Cの文法がクソなだけだよ!!
Cにおける、特に宣言まわりの文法は、かなり奇ッ怪なものです。奇ッ怪である以上、奇ッ怪であることを前提に理解しなければなりません。しかし、書店にあふれるCの本は、ポインタを専門に解説しているような本でも、この点について正面から説明しているものはほとんどないように見受けられます。
私自身、昔は配列とポインタにまつわるCの文法でかなり悩んだものです。
「あの頃、こんな本に出会っていたら,悩まなくて済んだのに」という思いを込めて書いたのが、本書です。ポインタに悩む方にも、ポインタが「わかっているつもり」の人にも、ぜひどうぞ。
旧版との違い
旧版と大きく構成を変えたわけではないですが、以下のような点で加筆・修正を行っています。
- C99の可変長引数(VLA)やC11のライブラリに言及
- ASLRやDEPといった今時の機能にも言及
- サンプルプログラムの動作環境の64bit化
- 1章に、「メモリとアドレス」の項を追加。17年前にCプログラマを志す人ならある程度前提にできたかもしれませんが、今時はそうもいかないと思われるので。
- 「補足」を目次に載せました(これはご要望が多かった!)。
- その他、細かい修正。
何しろ旧版が323ページのところ、今回は367ページありますので、相応の書き足しを行ってます。旧版をお持ちの方も、ぜひどうぞ。
目次から抜粋
「わかっている人」は、このあたりでにやりとできるのではないでしょうか。
- 【補足】宣言にまつわる混乱―どうすれば自然に読めるか?
- 【補足】hogeって何だ?
- 【補足】実行時には,型の情報も変数名も,ない
- 【補足】NULLと0と'\0'と
- 1-4-5 ポインタ演算なんか使うのはやめてしまおう
- 1-4-8 C99の可変長配列―VLA
- 2-5-3 自動変数をどのように参照するのか
- 3-1-1 英語で読め
- 【補足】最近の言語だと,型は後置のものが多い
- 3-2-4 「配列へのポインタ」とは何か?
目次
- 第0章 本書の狙いと対象読者―イントロダクション
- 第1章 まずは基礎から―予備知識と復習
- 1-1 Cはどんな言語なのか
- 1-1-1 Cの生い立ち
- 【補足】アセンブリ言語? アセンブラ?
- 【補足】Bってどんな言語?
- 1-1-2 文法上の不備・不統一
- 1-1-3 Cのバイブル―K&R
- 1-1-4 ANSI C以前のC
- 1-1-5 ANSI C(C89/90)
- 1-1-6 C95
- 1-1-7 C99
- 1-1-8 C11
- 1-1-9 Cの理念
- 1-1-10 C言語の本体とは
- 1-1-11 Cは,スカラしか扱えない言語だった
- 1-2 メモリとアドレス
- 1-2-1 メモリとアドレス
- 1-2-2 メモリと変数
- 【補足】size_t型
- 1-2-3 メモリとプログラムの実行
- 1-3 ポインタについて
- 1-3-1 そもそも,悪名高いポインタとは何か
- 1-3-2 ポインタに触れてみよう
- 1-3-3 アドレス演算子,間接演算子,添字演算子
- 【補足】本書に載っているアドレスの値について―16進表記
- 【補足】宣言にまつわる混乱―どうすれば自然に読めるか?
- 【補足】hogeって何だ?
- 1-3-4 ポインタとアドレスの微妙な関係
- 【補足】実行時には,型の情報も変数名も,ない
- 1-3-5 ポインタ演算
- 1-3-6 ヌルポインタとは何か?
- 【補足】NULLと0と'\0'と
- 1-3-7 実践―関数から複数の値を返してもらう
- 1-4 配列について
- 1-4-1 配列を使う
- 【補足】Cの配列はゼロから始まる
- 1-4-2 配列とポインタの微妙な関係
- 1-4-3 添字演算子[]は,配列とは無関係だ!
- 【補足】シンタックスシュガー
- 1-4-4 ポインタ演算という妙な機能はなぜあるのか?
- 1-4-5 ポインタ演算なんか使うのはやめてしまおう
- 【補足】引数を変更してよいのか?
- 1-4-6 関数の引数として配列を渡す(つもり)
- 【補足】配列を値渡しするなら
- 1-4-7 関数の仮引数の宣言の書き方
- 【補足】なぜCは,配列の範囲チェックをしてくれないのか?
- 1-4-8 C99の可変長配列―VLA
- 第2章 実験してみよう―Cはメモリをどう使うのか
- 2-1 仮想アドレス
- 【補足】scanf()について
- 【補足】未定義,未既定,処理系定義
- 2-2 Cのメモリの使い方
- 2-2-1 Cにおける変数の種類
- 【補足】記憶域クラス指定子
- 2-2-2 アドレスを表示させてみよう
- 2-3 関数と文字列リテラル
- 2-3-1 書き込み禁止領域
- 2-3-2 関数へのポインタ
- 2-4 静的変数
- 2-4-1 静的変数とは
- 2-4-2 分割コンパイルとリンク
- 2-5 自動変数(スタック)
- 2-5-1 領域の「使い回し」
- 2-5-2 関数呼び出しで何が起きるか?
- 【補足】呼び出し規約―Calling Convention
- 2-5-3 自動変数をどのように参照するのか
- 【補足】自動変数の領域は,関数を抜けたら解放される!
- 2-5-4 典型的なセキュリティホール―バッファオーバーフロー脆弱性
- 【補足】OSによるバッファオーバーフロー脆弱性対策
- 2-5-5 可変長引数
- 【補足】assert()
- 【補足】デバッグライト用の関数を作ってみよう
- 2-5-6 再帰呼び出し
- 2-5-7 C99の可変長配列(VLA)におけるスタック
- 2-6 malloc( )による動的な領域確保(ヒープ)
- 2-6-1 malloc( )の基礎
- 【補足】malloc( )の戻り値をキャストするべきか
- 2-6-2 malloc( )は「システムコール」か?
- 2-6-3 malloc( )で何が起きるのか?
- 2-6-4 free( )したあと,その領域はどうなるのか?
- 【補足】Valgrind
- 2-6-5 フラグメンテーション
- 2-6-6 malloc( )以外の動的メモリ確保関数
- 【補足】サイズが0でmalloc( )
- 【補足】malloc( )の戻り値チェック
- 【補足】プログラムの終了時にもfree( )しなければいけないか?
- 2-7 アラインメント
- 2-8 バイトオーダー
- 2-9 言語仕様と実装について―ごめんなさい,ここまでの内容はかなりウソです
- 第3章 Cの文法を解き明かす―結局のところ,どういうことなのか?
- 3-1 Cの宣言を解読する
- 3-1-1 英語で読め
- 3-1-2 Cの宣言を解読する
- 【補足】最近の言語だと,型は後置のものが多い
- 3-1-3 型名
- 【補足】せめて,間接演算子*が後置になっていれば……
- 3-2 Cの型モデル
- 3-2-1 基本型と派生型
- 3-2-2 ポインタ型派生
- 3-2-3 配列型派生
- 3-2-4 「配列へのポインタ」とは何か?
- 3-2-5 C言語には,多次元配列は存在しない!
- 3-2-6 関数型派生
- 3-2-7 型のサイズを計算する
- 3-2-8 基本型
- 3-2-9 構造体と共用体
- 3-2-10 不完全型
- 3-3 式
- 3-3-1 式とデータ型
- 【補足】「式」に対するsizeof
- 3-3-2 左辺値とは何か―変数の2つの顔
- 【補足】左辺値という言葉の由来は?
- 3-3-3 配列→ポインタの読み替え
- 3-3-4 配列とポインタに関係する演算子
- 3-3-5 多次元配列
- 【補足】演算子の優先順位
- 3-4 続・Cの宣言を解読する
- 3-4-1 const修飾子
- 3-4-2 constをどう使うか?どこまで使えるか?
- 【補足】constは#defineの代わりになるか?
- 3-4-3 typedef
- 3-5 その他
- 3-5-1 関数の仮引数の宣言(ANSI C版)
- 【補足】関数の仮引数の宣言に関するK&Rでの説明
- 3-5-2 関数の仮引数の宣言(C99版)
- 3-5-3 空の[ ]について
- 【補足】定義と宣言
- 3-5-4 文字列リテラル
- 【補足】文字列リテラルは,charの「配列」だ
- 3-5-5 関数へのポインタにおける混乱
- 3-5-6 キャスト
- 3-5-7 練習―複雑な宣言を読んでみよう
- 3-6 頭に叩き込んでおくべきこと―配列とポインタは別物だ!!
- 3-6-1 なぜ混乱してしまうのか
- 3-6-2 式の中では
- 3-6-3 宣言では
- 第4章 定石集―配列とポインタのよくある使い方
- 4-1 基本的な使い方
- 4-1-1 戻り値以外の方法で値を返してもらう
- 4-1-2 配列を関数の引数として渡す
- 4-1-3 動的配列―malloc( )による可変長の配列
- 【補足】他言語の配列
- 4-2 組み合わせて使う
- 4-2-1 動的配列の配列
- 【補足】ワイド文字
- 4-2-2 動的配列の動的配列
- 4-2-3 コマンド行引数
- 4-2-4 引数経由でポインタを返してもらう
- 【補足】「ダブルポインタ」って何?
- 4-2-5 多次元配列を関数の引数として渡す
- 4-2-6 多次元配列を関数の引数として渡す(VLA版)
- 4-2-7 縦横可変の2次元配列をmalloc( )で確保する(C99)
- 【補足】Cの多次元配列は「行優先」だ
- 【補足】ANSI Cで縦横可変の2次元配列を実現する
- 【補足】JavaやC#の多次元配列
- 4-2-8 配列の動的配列
- 4-2-9 変に凝る前に,構造体の使用を考えよう
- 4-2-10 可変長構造体(ANSI C版)
- 【補足】可変長構造体確保時のサイズ指定について
- 4-2-11 フレキシブル配列メンバ(C99)
- 【補足】ポインタは,配列の最後の要素の次の要素まで向けられる
- 第5章 データ構造―ポインタの真の使い方
- 5-1 ケーススタディ1:単語の使用頻度を数える
- 5-1-1 例題の仕様について
- 【補足】各種言語における「ポインタ」の呼び方
- 【補足】参照渡し
- 5-1-2 設計
- 【補足】ヘッダファイルの書き方について
- 5-1-3 配列版
- 5-1-4 連結リスト版
- 【補足】ヘッダファイルのパブリックとプライベート
- 【補足】同時に複数のデータを扱えるようにするには
- 【補足】イテレータ
- 5-1-5 検索機能の追加
- 【補足】倍々ゲーム
- 5-1-6 その他のデータ構造
- 5-2 ケーススタディ2:ドローツールのデータ構造
- 5-2-1 例題の仕様について
- 5-2-2 各種の図形を表現する
- 【補足】座標系の話
- 5-2-3 Shape型
- 5-2-4 検討―他の方法は考えられないか
- 【補足】なんでも入る連結リスト
- 5-2-5 図形のグループ化
- 5-2-6 関数へのポインタの配列で処理を振り分ける
- 5-2-7 継承とポリモルフィズムへの道
- 【補足】本当に,draw( )をShapeに入れていいのか?
- 5-2-8 ポインタの怖さ
- 5-2-9 で,結局ポインタってのは何なのか?
- 第6章 その他―落ち穂拾い
- 6-1 新しい関数群
- 6-1-1 範囲チェックが追加された関数(C11)
- 【補足】restrictキーワード
- 6-1-2 静的な領域を使わないようにした関数(C11)
- 6-2 落とし穴
- 6-2-1 整数拡張
- 6-2-2 「古い」Cでfloat型の引数を使ったら
- 6-2-3 printf( )とscanf( )
- 6-2-4 プロトタイプ宣言の光と影
- 6-3 イディオム
- 6-3-1 構造体宣言
- 6-3-2 自己参照構造体
- 6-3-3 構造体の相互参照
- 6-3-4 構造体のネスティング
- 6-3-5 共用体
- 6-3-6 無名構造体/共用体(C11)
- 6-3-7 配列の初期化
- 6-3-8 charへのポインタの配列の初期化
- 6-3-9 構造体の初期化
- 6-3-10 共用体の初期化
- 6-3-11 要素指示子付きの初期化(C99)
- 6-3-12 複合リテラル(C99)
著者のWebページトップはこちら
ご意見、ご質問、不具合連絡等は掲示板にお願いいたします。