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


はじめてのCプログラム

 プログラミング言語C++の設計者であるBjarne Stroustrup氏は、"現状のC++プログラミング教育には問題が多い"と繰り返し指摘しています(Stroustrup氏自ら書き下ろした2008年8月出版C++入門書)。この指摘は、プログラミング言語Cの教育にも当てはまるのでしょうか。Stroustrup氏の「C」に関する見解は、この連載記事に詳述されています。大変興味深い見解です。

 次のサンプルプログラムをご覧ください。

main()
{
   printf ("I don't know anything about programming!\n");
}
 この3行の小さなサンプルプログラムを一見し、皆さんの多くは、次のようなことをお感じになるのではないでしょうか。

・Hello Worldプログラムではない。
・stdio.hというファイルの姿がない。
・returnというコードがない。
・mainの前にintがない。
・このコードはそもそもコンパイルできるのか。
・サンプルコードとしてはありふれている。
・printf関数の使い方が初歩的すぎる。
・豊田が何を考えているのかよく分からない。
・豊田はいったい何を説明しようとしているのかまったく分からない。
・プログラミング知識がまったくないので今は何も感じない。

 結論を言えば、このコードはVisual Studio.NET 2003で無事にビルドすることができます(参考資料(赤字は豊田強調))。

 完成したプログラムを実行すると、"I don't know anything about programming!"という文字列が画面に表示されます。しかし、Visual Studio.NET 2003環境でこのソースコードを正常にコンパイル・リンクするためには、いろいろなビルドオプションを有効にしておく必要があります(つまり、MicrosoftのIDEツールの操作にある程度習熟する必要がある)。有効にすべきオプションを詳しく説明していると、おそらく、ほとんどの皆さんは次回以降の連載を読みたくなくなってしまうでしょう。このサンプルコードはきわめて単純なものではあっても、プログラミング言語Cの歴史をはっきり語ってくれています(MicrosoftのC++開発チームとC言語仕様)。

 プログラミング言語C++の設計者であるBjarne Strousrup氏は、この論文の中で、このコードに関して次のようなことを述べています。

・このコードをC89標準仕様から見た場合、一つのエラーがある。
・このコードをC++98標準仕様から見た場合、二つのエラーがある。
・このコードをC99標準仕様から見た場合、二つのエラーがある。
・二つのC99仕様エラーを修正しても、C++98仕様準拠C++コードと完全に同じになるわけではない。

 このリストを一目見た皆さんは、ほぼ例外なく、"自分はプログラミング言語学者になりたいわけじゃない!"と、叫びたい心境になるでしょう。筆者もまったく同感です。ただ、次のような点は覚えておいても決して損にはなりません。

・プログラミング言語Cには歴史がある。
・プログラミング言語C++には歴史がある。
・2つのプログラミング言語は相互に影響しあっている。
・コンパイラとリンカは2つの言語の歴史を表現するアプリケーションである。
・2つの言語の歴史に精通すると、人のできないことができてしまう。
・2つの言語の歴史に精通すると、コンパイラとリンカを自由に使えるようになる。
・2つの言語の歴史に精通すると、高度な機能を誇る統合開発環境を自由に使えるようになる。
・2つの言語の歴史に精通すると、危険なコードの使用を避けることができるようになる。
・2つの言語の歴史に精通すると、ソフトウェア業界の動きを楽しく眺めることができるようになる。

 上に紹介したサンプルCコードから学べることがあるのでしょうか。Stroustrup氏は、本「IT談話館」のこの連載記事内で次のようなことを述べています。

"Cの関数は、プログラムを抽象化するものである"

 抽象化というのは、物事を分かりやすく表現する思考過程(ビジネス的には、効率的な分析手段)を指しています(参考記事)。上のサンプルCコード内では、printfという関数が使われています。初歩的な英語の知識のある人は、その英語名から、printf関数の実装機能を理解し、サンプルコード全体の機能概要を把握できてしまうはずです。それでは今度は、ほとんどの開発環境でそのまま正常にコンパイル・リンクできるサンプルコードを紹介しましょう。
#include <stdio.h>
int main()
{
   printf ("I don't know anything about programming!\n");
   return 0;
}
 このコードは、Cコンパイラでコンパイルすれば正式なCプログラムであり、と同時に、C++コンパイラでコンパイルすれば、正式なC++プログラムです。この表現の意味はこの後の連載回ではっきりしていきます。現時点では、CとC++という2つのプログラミング言語は、相互に影響を受け合いながら発展してきたため、文法上の相違点が少なくなってきている、ということを覚えておきましょう。

 最初のサンプルCコードと比較すると、このコードにはいろいろな情報が追加されています。これはコードの複雑化、あるいは巨大化というものですが、その最大の原因は、私たちの社会の進歩です。社会が進歩すればするほど、コンピュータの使われ方はそれを反映するように複雑化し、完成するシステムは必然的に巨大化します。ここでは、「stdio.h」というファイルが第1行目に挿入されている点に注目しておきましょう。

 「stdio.h」ファイルには、printf関数以外のいろいろな(標準)関数が定義されています。たとえば、「stdio.h」ファイル内の他の関数を使用すれば、次のようなコードも書けてしまいます。
#include <stdio.h>
int main()
{
   puts ("I don't know anything about programming!");
   return 0;
}
 同じ文字列を表示するために、「printf」と「puts」という2つの関数が使えます。これは選択肢が増えたことであり、歓迎すべき歴史の進歩ではありますが、私たちはいったいどちらの関数を使用してよいのか混乱してしまいます。筆者はこのような場合、プログラムの抽象化(分かりやすさ)という視点を重視することにしています。この視点に立つと、おそらく、printf関数を使用する人が多いのではないでしょうか。ちなみに、printfの「f」はformatを、putsの「s]はstringをそれぞれ示しています。

 これまでの説明内容を整理すると、次のようになります。

・プログラミング言語には歴史がある。
・コンパイラとリンカの仕様は実質的に標準化委員会が決める。
・標準化委員会の活動は尊重されている。
・プログラムの抽象化とは、分かりやすいプログラムを書くことである。
・プログラミング言語Cでは、関数がプログラミングの抽象化を担っている。

 Cプログラミングでは、関数の役割が大きいことが分かります。

 関数というのは、一般には、「ある値を受け取り、要求された処理を行ったうえで、その結果を返す」といわれています。この解釈に沿って、本日のサンプルプログラムを書き換えれば、次のようになります。
#include <stdio.h>
int main()
{
   int retVal; // 結果を受け取るメモリを用意する
   retVal = puts ("I don't know anything about programming!"); // 結果を返す
   return 0;
}
 ご覧のように、「int retVal」というコードが追加されています。intというのは、Integer(整数)の略語で、機能的には、「型」と呼ばれています。ここでは「型」の詳しい説明は行いませんが、「型」の概念をさらに拡張すると、「構造体」や「クラス」という概念が誕生します。ちなみに、プログラミング言語Cは、BCPLとBという言語を参考にして作られたといわれます。しかしこの2つの参考言語は、「型」という概念を持っていなかったようなのです。こうした歴史の流れを知ると、ソフトウェア発展の歴史の中で、「型」というものの位置付け、基礎解釈、そしてその応用がいかに大きいな意味を持っているかが分かります。後の連載回で紹介するC++サンプルコードは、さらに発展した「型」の上に成立しています。「型」とは何か。たいへん気になるところですが、今回は説明いたしません。後の連載回に譲ります。

 「int retVal」のretValは、変数と呼ばれています。変数というのは、数値が(勝手に)変化する、内容が(勝手に)変化する、という意味を持っていますから、変数を使用する人は、その時々の値(内容)を注意して見守る必要があります。プログラミング言語Cは、開発者は自分の行っていることをきちんと理解しているはず、つまり、プログラマを信頼する、という設計思想を打ち出している言語といわれています。

 上のコードでは、puts関数が動作を完了すると、retVal変数に何らかの値が設定されます(このため、変数の内容が変化する)。Visual Studio.NET 2003のCコンパイラは、次のようなコードを内部で作成しています。

mov dword ptr [retVal],eax

 これはアセンブリコードといわれものですが、「eaxの内容をretValに移動(mov)せよ!」、と述べています。eaxというのは、32ビットWindowsシステムが動作するIntel CPUでは、32ビットの汎用レジスタの一つとなっています。ここでは、CPUの内部レジスタであるeaxから変数retVal(メモリ)へのデータ転送が行われている、ということを覚えておきましょう。ちなみに、retValを一切使用していない以前のサンプルコードの場合では、レジスタからメモリへのデータ転送は行われません(Cコンパイラは上のようなアセンブリコードを作成しません)。処理スピードを考えた場合、retValを(必要以上に)使用しないほうがよい、ということになります。一般に、レジスタ間のデータ転送と比較すれば、メモリ操作を伴うデータ転送はプログラムのパフォーマンスを低下させます。このようなことを考えながらプログラミングすることは結構楽しいものなのですが、平均的なソフトウェア開発者やプログラミング学習者にとっては、"難しい!"、ですね。また、関数、メモリ、あるいは、CPUの奴隷になっているようで、どことなく、息苦しささえ感じます。

前へ | 次へ



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


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