ビジネス WinDbg入門 Windowsカーネル分析ノート


C関数のまとめ2

 今回は、Cの「最大の売り」であり、しかし、習得がもっとも困難といわれている「ポインタ」を取り上げます。ただし、本記事では、「ポインタ」の上手な使い方を説明しているのではなく、「ポインタを理解することはたいへん難しい!」、ということを証明しています。プログラミング言語の選択や開発生産性などを考える上で参考になれば幸いです。

 すでに公開してあるこの記事では、次のようなprintf関数の宣言を紹介しています。この関数宣言は、C標準化委員会が定めたものであり、「stdio.h」というファイル内に含められています。

int printf(const char *, ...);

 このprintf関数宣言を素直に解釈すると、次のような文章が出来上がります。

 "このprintf関数は、C標準化委員会が定めた関数である。この関数は、(複数の)文字へのポインタを受け取り、それを画面に表示する。表示後は、表示した文字数をint型の整数として返す"。

 ご覧のように、かなり難しい文章です。私たちは以前、printf関数を次のように使用しました。
#include <stdio.h>
int main()
{
  const char ch1 [] = "I don't know anything about programming!\n";
  const char* ch2 = "I don't know anything about programming!\n";

  printf (&ch1[0+3]); // アドレスを意識した表現(保守性は低い)
  printf (&ch2[5]);   // アドレスを意識した表現(保守性は低い)
}
 ポインタは一般に次のように記述されます。

int *p;

 プログラミング言語Cの設計者であるDennis Ritchie氏は、書籍「The C Programming Language」(第2版)の第5章で、ポインタを次のように説明しています。

 "ポインタとは、ある変数のアドレスを保持する変数である"

 この説明を単純化すると、"ポインタはアドレスである"、"ポインタは変数である"、ということになります。前者はメモリの直接操作を、そして、後者はメモリの変数経由での間接操作を、それぞれ示しています。Ritchie氏の説明は、これら2つの可能性を主張するとともに、後者(つまり、ポインタは変数であるという考え方)の利便性を強調しています。

 "ポインタとは、ある変数のアドレスを保持する変数である"(変数経由でのメモリ操作)というRitchie氏定義に忠実にコーディングすると、先のサンプルコードは次のようになります(出力結果は同じです)。
#include <stdio.h>
int main()
{
  const char ch1 [] = "I don't know anything about programming!\n";
  const char* ch2 = "I don't know anything about programming!\n";

  printf (ch1+3); // アドレスではなく、変数を意識した表現(誰でも保守可能)
  printf (ch2+5); // アドレスではなく、変数を意識した表現(誰でも保守可能)

}
 このコードから、次の3点を学ぶことができます。

・ポインタ変数はアドレスを保持している。
・ポインタ変数はアドレスのように利用できる。
・ポインタ変数の操作はアドレスを操作することである。

 以上の2つのサンプルプログラムを比較すると、この2番目のプログラムは、メモリ操作(メモリ制御、システムプログラミング)を変数操作(ユーザ問題の解決)に置き換えているため、「表現力が向上している」(このため、コードが分かりやすくなり、開発と保守の生産性も向上する)といえます。しかし、[「メモリアドレス」というハードウェア概念から私たちを完全に解放してくれているわけではありません。友人宅を訪問する際、私たちは番地(アドレス)や信号機からの(正確な)距離を意識したりしません。意識した場合、それは、友人宅の訪問ではなく、探偵などが行う特殊な行為です。

 ところで皆さん、次のようなポインタ宣言にお目にかかったことはありませんか。先に紹介したポインタ宣言「int *p;」の「*」印の位置を少し左側にずらし、変数名「p」ではなく、「int型」に密着させています。

int* p;

 多くのプログラミング入門書は、星印の位置の意味をほとんど説明してくれません。中には、「int * p;」という具合に、型と変数名の中間に星印を記述する例をなんの断りもなく紹介している書籍さえあります。おそらく、星印の位置に関係なく、ソースコードが(なんとなく)コンパイル・リンクされてしまうからでしょう。これは、コンパイラライタが星印の位置の相違を吸収してくれていることを示しています。Cコンパイラライタたちは、「*」印の位置の違いをどのように解釈したのでしょう。"実装上困るから統一してくれ!"、と叫ばなかったのでしょうか。

 プログラミング言語C++の設計者であるBjarne Stroustrup氏は、(「int *p」ではなく)「int* p」の意味を次のように説明しています。筆者は、この説明を高く評価している一人です。「型」という用語が使われている点に注目してください。

"「int* p」のpは、int型のオブジェクトのアドレスを保持する「int*型」の変数である"

 この説明には、「ポインタ」という用語が見当たりません。Stroustrup氏は、Cプログラマは「int *p」と書く傾向が強く、C++プログラマは「int* p」と書く傾向が強い、と述べています。

 ポインタの解釈は、CからC++への時代の流れを感じさせてくれます。Cにはsizeofという演算子があります。皆さん、このsizeofは"なんの大きさ"を求めてくれるのでしょう。たとえば、次のようにポインタを宣言したとしましょう。

int* a;

 この場合、sizeof(a)とsizeof(int*)は、同じになります。ということは、「a」と「int*」の間には"is-a"関係が成立していることになります。この視点から見ると、「int*」は型であり、「a」はそのオブジェクト(あるいは、インスタンス)である、ということができます。

 C的なポインタ解釈は、メモリ(ハードウェア)の知識を要求しています。「int* p」の「int*」は、1つの型である、というC++的発想はぜひ印象に残しておきたいところです。C++特徴の一つは、「型」とその安全性を保障するところにあります(静的な型チェック)。なお、後続のこの連載回では、ポインターをアセンブラーレベルで説明することになっています。

前へ | 次へ




 WinDbgアプリケーション開発独習講座  ホーム


Copyright©豊田孝 2004- 2008
本日は2008-11-22です。