付録A 用語集

C言語で作るCGI入門で登場した言葉を独断と偏見を交えて解説しています。 なお、個々の言葉の解説は、敬体ではなく、常体で書かれています。

この用語集は、 数字、英字、特殊文字、ア段、カ段、サ段、タ段、‥‥‥、ワ段という順に、 並んでいます。


C

configure一発〔こんふぃぎゅあいっぱつ〕(configure one shot)
./configure一発

F

fork〔ふぉーく〕(fork)

forkとは食器のフォークであり、 knife and fork 、すなわち、 ナイフとフォークのフォークである。 ちなみに英語では fork and knife の順番では言わないらしい。 利き手でナイフを握るからだろうか。

さて、 電子計算機の世界で言うフォークとは、 無論、 食器のフォークではなく、 プロセスの複製を作ることである。 フォークは先っちょがふたまた(あるいはそれ以上)分かれていると言うことで、 かつては1本だった処理が2本に増える訳である。 新たに増えたプロセスは「子プロセス」と呼ばれ、 元のプロセスは「親プロセス」と呼ばれる。 すなわち、 親プロセスは fork で子プロセスを「産む」のである。 ただし、 生まれた子供は赤ん坊どころか、 親と同じプログラムを、 親と同じ状態から実行してくれる一人前である。 と言っても、 子プロセスはすぐに execve システムコールで変身して、 親プロセスとは異なるプログラムを実行する場合が多い。 生まれた瞬間だけ親とそっくりさんだけれども、 その直後は親と違うという、 全くの親不孝者であることは間違いない。

こうして生まれたばかりのプロセスの状態は元のプロセスと同じである。 ただし、 fork というシステムコールの返り値は異なり、 元のプロセスの返値は複製したプロセス(子プロセス)の識別番号、 子プロセス側は0が返る。 それ以外は、 実行している位置(すなわち fork が終わった場所)も、 グローバル変数も、 ローカル変数も同じである。 ただし、 以後はグローバル変数もローカル変数もプロセスそれぞれが操作するため、 別々に操作したら別々の値になる。 もし、 プロセス同士で変数の受け渡しが必要な場合、 セマフォア、シグナル、パイプ、共有メモリといったプロセス間通信が必要である。

ちなみに、 子プロセスは親プロセスより先に天命を全うすることが多い。 と言うのも、 親プロセス側が wait というシステムコールを実行した場合、 親プロセスは子プロセスが終了するまで待つからである。 まさに「我が子の死を見届けん内は死なん」である。 すなわち、 子プロセスは親よりも早く死ななければならないという悲しい運命を背負っており、 たとえスーパーマンに変身していても逃れられないのである。 親プロセスを待たせなくするには、 既に待っている親プロセスにシグナルを送って強引に止めさせるか、 共有メモリやパイプを用いて親が待たなくてもいいことを伝えなければならい。

なお、 fork() の振る舞いを確認する場合、 プログラム fork_test.c と その出力 fork_test_output.txt を参照。

(2003.5.24)

G

goto〔ごうとぅ〕(goto)

「gotoを使うな!」 プログラムの教育現場で教官が言ったり、 普通に教科書でその趣旨の記述を目にしたことがあると思う。 確かに何も考えずに goto を使うのは良くない。 しかし、 プログラムの実行中で例外を処理する場合だけは、 むしろ goto を使うべきである。 longjmp でもいいけど。

例えば、 以下の手順で動作するプログラムを書く場合、 goto の効果がある。

  1. ファイルを開く
  2. (1)で開いたファイルから文字を読む
  3. (2)で読んだ文字を加工する
  4. (3)で加工した文字を(1)で開いたファイルに書き戻す
  5. (1)で開いたファイルを閉じる
  6. 「成功したよ」というメッセージを表示する

プログラムがいつでも100%正常に動作するとして設計した場合、 実装に goto の出番はない。 しかし、 実際は、 ファイルが開けなかったり、 読んだ文字が加工できなかったりする可能性が付き物である。 何らかの原因で失敗する可能性がある場合、 その失敗したときの処理を書くときに、 goto は威力を発揮する。

まずは、 gotoを使わない場合のプログラムを書くと以下になる。

ファイルを開く
IF ファイルが開けない THEN
    「失敗でした」を表示する
    終わり 
ENDIF
ファイルから文字を読む
IF ファイルから文字が読めない THEN
    ファイルを閉じる
    「失敗でした」を表示する
    終わり 
ENDIF
文字を加工する
IF 文字の加工ができなかった THEN
    ファイルを閉じる
    「失敗でした」を表示する
    終わり 
ENDIF
文字をファイルに書き出す
IF 書き出しに失敗 THEN
    ファイルを閉じる
    「失敗でした」を表示する
    終わり
ENDIF
ファイルを閉じる
「成功でした」を表示する
終わり

さて、 一方、 goto を使った場合は、 前に挙げたプログラムよりぐっとスッキリする。

ファイルを開く
IF ファイルが開けない THEN
    goto 例外発生
ENDIF
ファイルから文字を読む
IF ファイルから文字が読めない THEN
    goto 例外発生
ENDIF
文字を加工する
IF 文字の加工ができなかった THEN
    goto 例外発生
ENDIF
文字をファイルに書き出す
IF 書き出しに失敗 THEN
    goto 例外発生
ENDIF
「成功でした」を表示する
goto 最終処理
《---例外発生---》
「失敗でした」を表示する
goto 最終処理
《---最終処理---》
IF ファイルを開いている THEN
    ファイルを閉じる
ENDIF
終わり

以上の2個のプログラムの違いは、 何かに失敗したときにその処理を行う場所がまとまっていることである。 goto を使わない場合は失敗時の対応がその都度書かれていたのに対し、 goto を使った場合は失敗時の対応が後ろにまとめて書かれている。

goto のご利益は失敗時に行う同じ処理を複数の場所に書かなくて済むことである。 同一の知識を複数の場所に書いてしまうと、 後で変更する必要が生じた場合、 とてつもなく大変なことになる。 例で挙げた前者の場合は4ヶ所も修正しなければならないのに対し、 goto を使った後者の場合はたった1ヶ所である。 特に、 忘れた頃になって、 プログラムを修正しなければならない場合、 必要な4ヶ所をすべて見落とさずに書き直す必要がある。 「いざ修正!」と言うときに、 4発命中させなければ正常に動作しない。 コンバット越前と言えどもこれは至難の技である。 goto を使ってまとめた場合は、 その1ヶ所を注意深く撃つだけで済む。 しかし4ヶ所もあったら標的がソースに埋もれて発見できないかもしれない。 この種の撃ち落としが極めて深刻で、後に、致命的になる場合がある。 下手をすると「コンティニューした瞬間に斬られて即死」という事態になりかねない。 せっかくだから goto を使おうぜぇ!

ところで、 何か失敗したときに goto を使うことは、 オブジェクト指向で例外を処理することと良く似ている。 メソッドの途中で何か間違いが生じたとき、 例外オブジェクトを生成して、 それを捕捉できる場所に一気に飛ぶ振る舞いは、 まさに goto のそれと同じである。

逆に例外処理で無い場合で goto を用いると、 プログラムがスパゲティの如くこんがらがるだけである。 そうなると、 BASICと変らなくなり、 構造化された言語が何が何やら解らなくなる。

同じ理由で、 C言語の while 文や for 文で break や continue もみだりに使用するのは危険である。 ループの最後に飛んだり、 いきなりループから抜け出したりするのは、 ほとんど悪い goto と同じ振る舞いをするからである。

(2002.3.7)

I

ISP 〔いんたーねっと さーびす ぷろばいだ〕(internet service provider)

ISPと言うと、 Internet Service Providerのことである。 みんなはプロバイダとか呼んでいる、 いわゆる「接続業者」というのが一般的な使われ方だ。

しかし、 ISPを字面通りに読めば、 「インターネットでサービスを提供者」のことであるから、 決して電話回線とかISDNとかでの接続サービスを行わないプロバイダもある。

例えば、 ウェブページ用のディスクスペースを提供するプロバイダを、 コンテンツプロバイダと呼び、 一般的に接続サービスを行うプロバイダと「明確に」区別している。 普通のISPで持てるウェブコンテンツの容量はせいぜい10Mなので、 50Mとか100Mとか貸すことで他のプロバイダと差別化を図っているわけである。

ただ、 面白いことに、 サーバなどの設備を持っておきながら、 コンテンツを提供するのが主な業者はプロバイダとはあまり認識されていない。 例えば、infoseekとかyahooとか。

さて、 そんな能書きは置いといて、 ここはCGI入門なので、 そのプロバイダでCGIが動くかどうかが最終的に重要だろう。 CGIが動くかどうかは、 プロバイダのHTTPサーバの走っているところに、 telnetかftp何なりでログインしてhttpd.confを見ると良く解る。 けれども、そんなことをしなくても、プロバイダに直接聞いてみた方が手っ取り早い。 CGIは動くけど拡張子は「.cgi」にしないとだめだとか、 CGIは動くけどプロバイダが用意したCGIプログラムしかだめだとか。

(2000.9.22)

J

Java言語〔ぢゃばげんご〕(Java Language)

1995年にオブジェクト指向言語としてデビュー。 C++を押し退けて一気にスターダムへ。 世の中をして「陽(Sun Microsystems)は沈めどコーヒー(Java)の香りは残る」 と言わしめたプログラミング言語。 それがJava言語である。 良かったね Oak にならなくて。 ぜろ"0"とアルファベットのオー"O"は間違えやすいので、 言語の名前がJavaで本当に良かったと思う。

Java言語はオブジェクト指向の他に、 スレッド、 GUI、 そして、 ネットワーク機能を言語レベルでサポートしているのが特徴である。 ついでにガーベジコレクションも重要な特徴である。 更に、 豊富なAPIも特徴で、 C言語での扱いが面倒な文字列、 コレクション、 リモートメソッド呼び出し(RMI)、 データベースに対するアクセスAPI(JDBC)と、 普通にビジネスアプリケーションを作る上で必要な機能はほとんど揃っている。

「Java言語は簡単か?」と問われると 「C++に比べたら容易かも知れない」としか答えられない。 テンプレートとか 関数アダプタとか バインダとか マニピュレータとか奇妙奇天烈な概念は全く出てこないので、 確かに敷居は低いと思う。 しかし、 オブジェクト指向そのものの難しさは、 Java言語でもC++言語でもpythonでもrubyでも全く同じである。 すなわち、 要求分析をして再利用性を考慮したクラスの設計をすることは、 どの言語でやっても難しさは変わらないのである。

(2003.8.7)

O

OS〔おーえす〕(OS : Operating System)
オペレーティングシステム

P

POSIX〔ぽしっくす〕(Portable Operation System Interface for uniX)

POSIXとはPortable Operation System Interface for uniXの略のことで、 字面通り読んでみると、 「UNIX系の基本ソフトにおける移植可能なインターフェイス」 となる。 ぶっちゃけた話、 国際標準の仕様を満したコマンドやシステムコールで操作するためのインターフェイスがPOSIXということになる。

具体的に言うと、 ディレクトリの中にあるファイルやサブディレクトリを表示するコマンドである ls 、 ファイルを開いたりするopen()やclose()といったシステムコールは、 みんなPOSIXというインターフェイスに準拠している。

POSIX互換になると何が嬉しいかと言うと、 LinuxでもSolarisでもFreeBSDでも、 ほとんど同じ感覚で操作することができるのである。 それはコマンドでも然り、 C言語のプログラムにしても然りである。 プログラム、 とりわけ、 システムコールの場合は、 違う環境に持っていっても、 全くソースコードを変えずにそのままコンパイルして実行できてしまうことが多々ある。

逆に、 プログラムを書く側は、 POSIX互換の部分と特定のOSに依存する部分をきちっと分離する必要がある。 特に、 C言語においては、 ANSIという規格(あんまり守られていないけれども)だけで書ける部分、 POSIXに準拠しているシステムコールを使用している部分、 特定の環境に依存する部分を意識しながらコードを作る。 こうしないと、 プログラムを他の計算機に持っていったとき、 どこで悪さをしているか解らないからね。

(2000.10.16)

S

snprintf 〔すとりんぐ えぬ ぷりんとえふ〕(String N PRINT Formatted)

snprintfはBSD 4.4で加わり、 ISO/IEC9899に準拠した標準ライブラリ関数である。 ANSIには規定されていないため、 使用できない処理系がある。

データを書き出す長さの上限を指定できる以外は、 snprintfの機能はsprintfと同等である。 書式は int snprintf(char *str,size_t size,char* format,...) で、 終端文字('\0')も含めて書き出した文字数を返す。 従って、 長さを指定する2番目の引数を1に指定すると、 終端文字だけ書き込まれて終了し、0を返す。 使用の際は終端文字も含めて長さを指定する必要がある。

snprintf は strcpy と sprintf の バッファオーバーフローを 防ぐために設計されているはずである。 C言語は文字列処理ライブラリが充実しておらず、 文字列処理が多いCGIには実は向かなかったりする。 それでもC言語でするのは、 性能のためではなく、 perlでは見えにくい内部処理を明らかにし、 CGIプログラムの基本を理解するためである。

(2003.8.14)
strtol 〔すとりんぐ とぅ ろんぐ〕(STRing TO Long integer)

アスキー文字の文字列で表現された数を、 演算を行うために数値に変換しなければならないことがある。 例えば文字列 "1234" を 数字の 1234 として加減算を行いたい場合はそうである。

文字列と数字の書式変換を行う関数は、 atoi (Ascii TO Integer) をはじめとして、 sscanf (String SCAN Formatted)、 そして、 strtol (STRing TO Long integer) がある。 内部プログラムが生成して正しいとしているデータには atoi を、 複数の数値に変換する場合は sscanf を、 ユーザから入力されたのデータで信用してはならない場合は strtol を用いる。 いずれも stdlib.h に定義されており、 ANSIに準拠している関数である。

strtol の書式は long int strtol(const char* str,char** endptr,int radix) である。 エラーを見付けず、しかも、10進数以外は変換できない atoi より引数が多い。 その分、 strtol は エラーの起こった文字を保存し、 2進数から36進数まで変換できる。 できることが多い分、 strtol は使いこなすのが atoi に比べて容易ではない関数である。 処理を停止した文字へのポインタをそのポインタとして渡す必要があるため、 ポインタへのポインタを知っていないと使いこなせないからである。

なお、 strtol の逆、すなわち、数字を文字列の書式に書き出す関数は、snprintf である。 処理系によっては itoa が用意されている場合がある。

(2003.8.14)

特殊文字

./configure一発 〔こんふぃぎゅあ いっぱつ〕(configure one shot)

「./configure一発」とは、 UNIX系のアプリケーションのごく典型的なインストールの方法である。 実際は autoconf とか automake いうプログラムが、 環境に依存する部分をできる限り吸収している。 だから、 「./configure」と撃ち込んだだけで、 キミのPCとOSに合わせてプログラムを作ってくれるという訳だ。

でも、 実際のインストールは、 ./configure コマンドだけ撃ち込めば良いと言うことではない。 ファイルの取得、 アーカイブの展開、 パッチ当て、 プログラム生成、 コンパイル、 インストール、 その後の環境設定が必要だ。 これでもかなり楽なので、 ./configure一発とか呼んでいるのである。 はじめは戸惑うけれども、一発ヤッてしまえば、あとはお決まりの作業行程である。 その「お決まり」であるが故に、「./configure一発」なのである。

なお、 筆者は現在、 CGIプログラムを「./configure一発」でインストールできるようにするため、 autoconfとかautomakeとかを勉強中。 自分が作ったプログラムを./configure一発で導入できるようにすると、 何発もmakeを撃たなくていいから楽なのだ。 それにしても、 autoconfに対応させると、 GNUに貢献できるとかどっかのウェブページで書いてあったけど、 なぜ?

(2000.9.22)

加筆修正。 autoconf や automake は GCC と同様に GNU のツールの1つである。 実は、 autoconf 、 automake 、libtool そして、GCC が動く環境では、 OSの違いをほとんど気にすることなく、 プログラムを動かすことができる。 本当に必要なものは、 エディタ、シェル、コンパイラ、その他のツールであり、 OSそのものではない。 そういうこともあって、 GNUのツールは基本的にOSに対する依存性を無くす思想で作られており、 「GNU is not UNIX.」と言うのも、 決してワケわからん洒落ではないことが何となく解る。

自分の作ったプログラムをGNU autoconf に対応させて GPL で配布することで、 人類共通の財産に貢献することになる。 また、 こうした知識や思想を解りやすく伝えることも、 オープンソースに貢献していることになる。 一歩間違えれば宗教に近いけれども、 決して悪いことではないし、 むしろ日常的にその恩恵に授かっているのならば、 入信してもばちが当ることはない。

と言うことで、 近々わたしのCGIプログラムを「./configure 一発」のこと GNU autoconf に対応させ、 その解説を書こうと考えている。 Makefile.in と configure.in の作成と各種ヘッダを修正する作業が待っているので、 それが処理でき次第、 掲載する予定である。

(2002.3.7)

あ〜お

ウォーターフォールモデル〔うぉーたーふぉーるもでる〕(water fall model)

ウォーターフォールモデルとはシステム開発の手法の1つである。 ユーザの要求と問題点を取り上げる基本設計、 アプリケーションの外部インターフェイス(ユーザや外部のシステムへ提供するインターフェイス)の決定を行う外部設計、 プログラム内部のデータ構造やモジュールとモジュール間同士の連携方法を決める内部設計、 コードを実装するプログラミング‥‥‥、 と、 滝から水が落ちるが如く、 トップダウンに開発を行うことから由来している。

わたしは一応、 情報処理の資格を持っているものの、 そんなことを意識しながらウェブアプリケーション(掲示板など)を作った覚えはない。 自分なりに自然な方法でプログラムを書くと、 やっぱりそう言う風になるのだろうと言うことが何となくわかる。 つまり、 「そう言うのをウォーターフォールモデルって言うのか。だから、何だ?」 である。 資格って無意味だなぁ(笑)

(2000.9.27)
オペレーティングシステム〔おぺれーてぃんぐしすてむ〕(operating system)

オペレーティングシステム(operating system:OS)とは、 コンピュータ自体の操作を行う基本的なソフトウェアである。 OSはコンピュータの資源(演算装置・記憶装置・入出力装置)を操作・管理し、 ユーザプログラムを作るための土台を提供している。 主にユーザプログラムにはワープロや表計算やCADやゲームがあり、 「アプリケーション・プログラム」あるいは単に「アプリケーション」と呼ばれている。 「アプリケーション」とは読んで字の如く「応用」で、 OSが「基本ソフト」と呼ばれるのはそのゆえんである。 何事も基本と応用が大事だ。

「生命とは何か?」という問題よりは容易ではあっても、 「OSとは何か?」という問題には正確に答えられない。 ただ、 OSの役割については、 現在稼働中のOSでも将来現れるはずのOSでも変わることは無いはずである。 その役割は以下の2つ。

  1. アプリケーションプログラムから個々のハードウェア(演算装置・記憶装置・入出力装置)の複雑な制御を隠蔽する
  2. コンピュータ資源を管理する

OSは個々のハードウェアの複雑で制御を隠蔽し、 ユーザに対して抽象的で単純で扱いやすい操作機能を提供している。 アプリケーションプログラムを作成するとき、 余程特殊でない限りは、 そのコンピュータに搭載されているハードウェアの操作を意識することは無い。 例えば、 単に"hello , world"を端末に表示させるだけのプログラム1つ取っても、 画面を直接制御することは無い。 ブラウン管の中に入っている電子銃も、 モニタに接続している線を通っている電気信号も何も操作しないはずである。 同様の例として、 フロッピーディスクにファイルを保存する場合、 フロッピーディスクのヘッドやモーターを制御することも無い。 場合によってはフロッピーディスク上のファイルを書き出す位置も探したりはしないはずである。

逆に言うと、 コンピュータで何かを行う場合は、 複雑で詳細なハードウェアの制御という現実と付き合わなければならないことを意味する。 そこで、 単純で抽象的な操作方法を提供する層を設け、 うんざりするような制御は見ないことにしよう考えたのである。 現在はディスクにファイルを書く際、 ディスクのモーターを直接制御しなくても、 「開く」「読み込む」「書き出す」「閉じる」といった程度の操作だけで、 簡単に目的を達成することができる。

OSにはコンピュータの資源を管理する役割もある。 CPUやメモリやディスクといった多種多様な装置を 計算機資源として扱い、 それらをプロセスへの割り当ても行う。 プロセスに演算資源を割り当てると、 プロセスの実行を進行させることができる。 シングルタスクOSは管理できるプロセスは1つだけで、 その仕組みも小さくて単純である。 一方で、 マルチタスクOSは複数のプロセスを管理する機能を提供している。 ただし、 その仕組みは巨大で複雑を呈している。 複数のプロセスを扱うには、 実行を進行させるプロセスを切り替える機能と、 プロセス同士による資源への競合を整理する機能が必要だからである。

実行しているプロセスを切り替えるには、 そのプロセスが保持している実行手順(プログラム)と 実行状態(プログラムの中の実行している位置や計算の途中結果)を記憶装置に退避し、 再開させるプロセスの実行手順と実行状態を復元する必要がある。 また、 複数のプロセスが同時に実行できるため、 ある資源をめぐって複数のプロセスが競合することもある。 例えば、 あるプロセスがファイルを書き込んでいる間、 別のプロセスがそのファイルに読みに行くと、 書き込んでいる途中の一貫性の無い内容を読むことになる。 そのため、 OSは排他制御を行う機能、 すなわち、 あるプロセスが資源を獲得している間は他のプロセスに絶対に奪われないようにする仕組み、 を提供し、 安全性を保証する必要がある。 これらのプロセスの切り替え機能と競合の整理は、 高々1つのプロセスしかないシングルタスクOSには必要のない機能である。 複数であるが故に起こる問題だからである。

なお、 プロセスを同時に実行できるマルチタスクOSは、 逐次実行のシングルタスクOSより良いとは限らない。 複数同時に実行するには、 複数のプロセスを管理しなければならない。 マルチタスクOSはプロセスを管理するプログラムと、 プロセスを切り替えるための計算機資源も必要である。 それに加えて、 本来の実行するプログラムも複数個あるため、 それらを保持するにはより多くの計算機資源を要する。 組み込み機器といった演算資源と記憶領域が極めて限られている場合や、 複数のプロセスを実行する必要が無い場合は、 小さくて機構が単純なシングルタスクOSの方が適している。 適材適所。 戦略に応じて戦術を変えるのはどの時代でも同じことである。

(2003.5.11)

か〜こ

拡張子〔かくちょうし〕(suffix)

拡張子。 Windowsでは関連しているアプリケーションがあって特別な振る舞いをするけれども、 UNIXでは単なるファイル名の一部だ。 ファイル名の最後の文字から後ろ向きに数えて、 初めて現れた「.」までの文字列が拡張子ということになる。 ただ、 それが「そのファイルが何なのか」が、 ファイル名から解るようにしているしているだけである。

UNIXのアプリケーションの一部でも、 扱うファイル名の拡張子を要求しているものがある。 例えば、gcc とか。 拡張子が「.c」 、「.C」、「.cpp」、「.cc」、「.cxx」とかしかコンパイルしてくれないのだ。 これならアプリケーションが扱うファイルについてちょっと駄々をこねているだけなので、 まだ許せる。

しかし! 何なんだ! あのWindowsの扱いは。 ちょっと拡張子を変えただけで、 OSがファイルの扱い方を変えているではないか。 しかも、 「.」から始まるファイルを作ろうとしても、 小細工をしないとできないし。 バイナリファイルだったらELFとかmachintoshみたいに、 ちゃんとヘッダをつけて(OSが)検証しろっつーの。 プンプン。 プンプンと言えば、にこにこぷん。 あらあらー ほらほらー それからどんどこしょー。 空しくなってきたので、次。

(2000.9.22)
計算機資源〔けいさんき しげん〕(machine resource)

コンピュータに携わっている人種が、 「資源の無駄遣いだ!」とか叫ぶことがある。 ここで言う資源とは、 石油とか石炭ではなく、 コンピュータ上のCPU時間とかプログラムが占有するメモリとかを言及するものである。 従って、 週末にト○タに行ってエコカーを買ったり、 生ごみを減らす献立にしても、 決して計算機資源の節約に貢献したことにはならない。

計算機資源とは、大きく、次に挙げる事柄に分類される。

  1. 計算時間
  2. 使用記憶領域

計算時間とはプログラムの実行に要するCPUサイクルである。 言い換えれば、 そのプログラムがCPUを働かせた時間ということになる。 繰り返し処理や再帰処理が多くなるほどCPUを働かせた時間が大きくなる。 すなわち、CPU時間が長い、ということである。

一方、 使用記憶領域とは、 そのプログラムが使ったメモリの量を表す。 広義の記憶領域では、 ディスクといった不揮発性の記憶領域も含むけれども、 現在はディスクはたっぷりあるので、 しばしば問題となるのがメモリの量である。 プログラムを実行する前に、 一旦、 プログラム自体をメモリに置かなければならない。 また、 基本的に、 データはCPUからアクセスできる場所、 すなわち、 メモリに置かなければならない。 従って、 再帰処理が多かったり、 プログラムが扱うデータの量が多かったりする場合、 多大なメモリを消費してしまう。 すなわち、計算機資源を大量に消費してしまうわけである。

では、 計算機資源を節約するにはどうするか。 結論を先に言うと、 計算時間と使用記憶領域は一定のトレードオフがある、 である。 例えば、 何度も同じ計算する場合を考えてみる。 処理手順を決める側は、 同じ計算を実際に何回も行うか、 一旦計算した結果をメモリに保存して二回目以降はサボるという選択肢がある。 この場合、大抵は、後者の方が良い。 しかし、 プログラムの実行時間が極めて長く、 同じ計算を何度もするけれども結果を参照する頻度が極めて少ない場合、 長時間無駄にメモリを占有することになる。 と、言うことで、 「計算機資源を節約するには?」と言う問いに対する答えは、 一概に言えないのが現実である。

(2000.10.12)

さ〜そ

資源〔しげん〕(resource)

計算機資源

システムコール〔しすてむ こーる〕(system call)

システムコールとは オペレーションシステム が提供している拡張命令である。 命令と言っても、 UNIXのためにC言語は開発されただけあって、 そのシステムコールはC言語の関数という形で提供されている。 アプリケーションプログラムの開発者がOSの機能を使いたい場合、 これらの特殊な関数群を利用することになる。

システムコールで提供されているOSの機能として、 主に、 ファイルの操作(open,close,read,write,lseek,fcntl)、 パイプや共有メモリやセマフォといったプロセス間通信(pipe,shmget,semget)、 ネットワークで用いるソケット (socket,bind,connect,listen,accept,send,receive)がある。 無論、 マニュアルも存在しており、 お馴染の man コマンドがそれである。 システムコールのマニュアルはセクション2に存在し、 例えば read システムコールのマニュアルを見る場合、 キャラクタ端末から man 2 read と打鍵する。 セクションを指定せずに read のマニュアルを見ようとすると、 シェルの内部コマンドの read のマニュアルがしか現れないので注意。

システムコールは、 ハードウェアに近いと言う意味で、 「低級関数」と言われることがある。 これらは操作そのものが原始的で、 ライブラリ関数で行っていた処理をシステムコールで行うと、 いくつものシステムコールを呼ばなければならない。 ちょっとしたテキストファイルの処理を行うだけでも、 システムコールだけ用いるとプログラムが多くなるはずである。 不幸はソースコードが長くなるだけでなく、 システムコールはOS固有の機能なので、 異なるOSではプログラムが動作しないことが多い。

そこで、 OS固有の機能を直接用いずに済ませるために、 printfをはじめとするC言語のライブラリ関数がANSIで規格化されている。 一方で、 UNIXにはPOSIXというOSのインタフェイスの規格があり、 多くのシステムコールはそれに則っている。 こうして、 C言語のプログラムは標準的なライブラリ関数や、 POSIXに準拠したシステムコールを用いることで、 C言語のプログラムの高い移植性が実現されている。

また、 システムコールには、 OSに資源を使うための正式な手続きという側面もある。 そもそもOSは全ての計算機資源を管理しているので、 アプリケーションの開発者に勝手にハードウェアを使わせるわけには行かないのである。 そこで、 ユーザプログラムはOSに対してお伺いをする、 すなわち、 OS(システム)を呼んで(コール)、 資源をお願いするのである。 こうして、 借りた資源を使って仕事を済ませたら、 返す必要もあり、 そのためのシステムコールも用意されている。 開いたファイルを閉じたり、 借りたメモリを返すのである。

なお、 システムコールの「C言語の関数で提供された特殊な関数群」は、 しばしばAPI(appliation user interface)と呼ばれる。 読んで字の如く、 アプリケーション(application)を開発するときに、 何か特殊な機能を利用する人(user)に提供されている、 接点(interface)である。 APIはC言語で提供されたシステムコールの他にも存在し、 代表的なAPIとして、 C言語でスレッドの操作を行うPOSIXスレッドライブラリや、 Sunが提供しているJava言語の広汎なクラスライブラリがある。 ただ、 JavaはOSに依存しない言語と言っても、 実は、 プレーンソケットをはじめとする一部の機能は ネイティブメソッドからシステムコールを使っていたりする。 すなわち、 UNIXのAPIにJavaの皮を被せて、 Java言語としてのAPIを提供しているだけのことである。

(2003.5.26)
仕様〔しよう〕(specification)

岩波書店の広辞苑で「仕様」の意味を調べると、 「仕方」とか「方法」とか書かれている。 すなわち、 手に負えない意味での「しょうがない」を漢字で書くと「仕様が無い」である。 すると、 「この仕様では仕様が無い問題もあります」といった小もない駄洒落で、 周囲の白けさせ、落胆の底に突き落し、反感を集めることが可能だ。 でも、 そんな猫の糞を踏んで死ぬに値することよりも、 もっと前向きで楽しくて幸せなことが世の中にはあると思う。

さて、 電子計算機の世界で言うところの「仕様」とは、 「仕様が無い」の「仕様」(英語では means)ではなく、 「構造や性能や能力」としての「仕様」(英語では specification)である。 パソコンのスペックというのは、 構成としてのスペックと、 性能としてのスペックという2つの属性で表現されることがある。 前者の構成としてのスペックとは、 諸元、 すなわち、 構成する要素の集まりを意味している。 CPUのコアの名前、 メモリの規格、 マザーボードの上に付いているチップセット、 ハードディスクのメーカーと型番云々を列挙して構成を表現する。 一方で、 後者の性能としてのスペックは、 単純なCPUの動作クロックや、 メモリのアクセス速度から、 ベンチマークプログラムの測定値で表現することができる。 この性能としてのスペックは、 必ず、 測定され、 評価された後に、 能力があると認められるのである。

ソフトウェアないしはプログラムの「仕様」は、 専ら、 それが実現できる機能と性能を表してる。 ソフトウェアの機能は様々で、 ユーザから入力したデータを受け取ったり、 データを加工したり、 ファイルを操作したりと、 目的の数だけ仕様がある。 機能は同じだけれども、 性能が違うプログラムとして、 ライブラリで使用している、 特定のアルゴリズムの実装がある。 例えば、 整列アルゴリズムには、 挿入ソート、 バブルソート、 クイックソート、 ヒープソート、 マージソートは全て要素を一定の順序で整列するアルゴリズムである。 違いは整列に要する演算時間(比較回数)や記憶領域である。

実は、 講義やセミナーの演習と違って、 一般的なソフトウェアの開発においては、 その仕様を決めること自体が難しかったりする。 と言うのも、 人間自体がソフトウェアで何をしたいかが、 はっきりと決められず、 複雑多岐で、 その上、 生き物のように変わるからである。 従って、 ソフトウェアを作る際、 ソフトウェアで達成するべき「要求」を分析し、 その「要求」に基づいて「仕様(機能)」を定義するといった、 要求分析という工程と、 仕様定義という工程を経る。 何をしたいかが解らないうちは、 何をすべきかが解らないからである。 オブジェクト指向言語が流行っている現在は、 要求と仕様との間のギャップを確認するために、 ユースケースを用いることが多い。 と言っても、 ユースケースの表記法やシナリオの内容の深さに関しては絶対無二の指針は定められておらず、 その場その場の人間の経験と勘に任せているのが現状である。

(2003.7.19)

た〜と

単体テスト〔たんたいてすと〕(unit test)

単体テストとはコーディングしたモジュールが仕様通りに動くかどうかのテストである。

モジュールとは手続き指向の言語ならば関数であるし、 オブジェクト指向の言語ならばひとつのメソッドである。 これらは極めて小規模なので、 机上デバッグが終わったら直ぐにでもコンパイルしてテストした方が後々良い。

テストの方法はふたつある。 ひとつは、 モジュールのインターフェイスが仕様を満しているかどうかだけ調べる、 ブラックボックステスト。 ひとつは、 モジュール内部のロジックを調べ上げてテストを行うホワイトボックステストである。

わたしは基本的にホワイトボックスで、 ロジックの分岐を全て調べる条件網羅をお勧めする。 時間はかかるけれども、 自分が書いたプログラムが何かもっと理解できるし、 モジュールの組み合わせのテストでぐっと楽になる。

(2000.10.11)

な〜の

長いソースプログラム 〔ながいそーすぷろぐらむ〕 (too long source program)

長いソースプログラムを書くと、 「うっかり」が原因でコンパイルできなかったり、 うまく動かないことが多々ある。 と言うのも、 長いテキストファイルを読んでいると集中力が散漫になるし、 書いている側もちょこっといじるのにわざわざ画面をスクロールしなければならないので大変だ。 すなわち、 長いプログラムはミスをしやすい、 と言うことである。

そういった「うっかり」を未然に防ぐために、 わたしは、 以下の基準でプログラムを書いている。

具体的には、以下の長さを目安にしている。

この長さはテキストファイルの印刷環境に依存する。 わたしの場合は a2ps の出力を psnup で半分にしてA4サイズで印刷しているため、 1ページがおおよそ66文字となる。 余白や改ページも考えると、 およそ120行が適当な目安となる。

実はC言語のプログラムは関数単位でソースプログラムを分割できるため、 こういったことが容易である。 しかし、 Java言語の場合は、 1クラス1ファイルである。 従って、 どうしてもソースファイルが長くなってしまいがちになり、 それが書き手と読み手の注意を散漫にしてしまい、 生産性を落としているかも知れないと思うけど、 どう?

(2002.3.7)

は〜ほ

ファイル〔ふぁいる〕(file)

「ファイル」と言えば、 最も一般的に使われている狭義の概念と、 UNIXの世界で良く使われている広義の概念がある。

狭義の概念と言えば、 ハードディスクに何気なく置いてある、 テキストファイルだとか実行ファイルだとかである。 ちょっと固めの言葉で言うと、 「不揮発性の記憶装置に保存されているプログラムないしはデータ」 である。 不揮発性とは、 電源を切っても保持しているデータが失われないことを意味する。 これが狭義で言われている「ファイル」である。 実は、 第2次対戦中は「鬼畜米英」とか叫ばれてて、 英語とか横文字とかそういうもんはもっての他だったため、 「ファイル」も強引に「算帳」なんてジャパニーズなネーミングをしてたそうな。 だから、 C言語の標準関数のこと fflush() は、 「file のバッファを flush する」と言えばそれなりに意味は通じるのに、 強引にジャパナイズするから「算帳噴出」何て逆に分かりにくくなっちゃうんだなぁ。 「噴出」ですよ旦那(あるいは姉〔あね〕さん)? どうします? バッファの内容が噴出でもしてくれたら、 中身がどっかへ飛んで消えて行きそうで大変ですよ!

さて、 一般ではあんまり知られてはいないけれども、 UNIXの世界では算帳とは別の概念の「ファイル」が存在する。 ひとつがデバイスファイルと言う、 コンピュータの周辺機器を制御するためのハンドラがある。 他には、 名前付きパイプとかソケットとか、 普通のユーザではなくて、 プロセス間通信で扱うファイルも存在していたりする。 ちなみに、 ディレクトリも「ファイル」だったりする。 中身が「ファイル」の「ファイル」だと考えたら容易に想像がつくかなぁ。

狭義にせよ広義にせよ、 ファイルには必ず次に挙げる特徴がある。

と言う感じにまとめて、 締めくくると言い感じがしたので、 この辺で終わり。

(2000.12.4)
バッファオーバーフロー 〔ばっふぁおーばーふろー〕(buffer overflow)

バッファオーバーフローは規定の範囲外にデータを書き込むことで、 その原因は100%プログラムの誤りである。 この種の間違いはC言語の文字列操作で起こしやすい。 と言うのも、 C言語の文字列は単なる文字の配列であるため、 気を抜くと確保した領域の外にデータを書き込むからである。

それではプログラマが決めた範囲外にデータを書き込むと、 最悪、 プログラム(データを処理する手順が書かれた領域)や、 スタック(現在実行中の関数)の戻り先が書き換えられてしまう。 クラッカーはわざとデータをあふれさせ、 あふれた領域に悪意のあるプログラムを書き込む。 それはシェル(Windowsならば cmd.exe で UNIX ならば /bin/sh)であることが殆んどで、 実行者の権限 (元のプログラムの実行権限が一般ユーザならば一般ユーザ、 ルートならば恐ろしいことにルート) で他のプログラムを実行させられたりする。 よく「ルート権限が奪われる」と言うのはルート権限で実行しているプログラムで、 この手のセキュリティホールはシステム全体だけには留まらず、 他のホストにとっても致命的である。 と言うのも、 乗っ取られたホストは、 他のホストへの攻撃の踏み台にされるからである。

C言語で文字列の操作を行うライブラリは非常に原始的で、 その上、 境界の検証を行わないため、 誤りの原因となることが多い。 従って安全なライブラリを作るか、 メモリリークを検出するツールを用いて、 プログラムのテストを行う必要がある。 この手の不具合は生じやすい上に、 クラッカーに見つけられたら極めて危険だからである。

(2003.8.7)
フリーフォーマット〔ふりーふぉーまっと〕(free format)

フリーフォーマット(free format)とは、 ソースプログラムそのものの書き方で、 書き方が行数に依存しないという形式である。 つまり、 何行目に何を書かなければいけないという決まりがあるわけではなく、 実行する順序さえ守っていればどこに何を書いてもマルである。

では、フリーフォーマットではない言語とは何か。 最も有名なのが、BASICである。 この言語は、どの処理が何行目に書いてあるかを明記する必要がある。 例えば下のプログラムである。 IFのジャンプ先が行で指定されているところに注目してほしい。

10 REM (以上略)
20 INPUT A
30 IF A=0 THEN 50
40 PRINT "A IS NOT 0"
50 REM (以下略)

何と言っても、 フリーフォーマットでない言語は、 プログラムの書き手の負担が増える。 プログラムというのは頻繁に修正する必要があるため、 いちいち行なんか構っている暇がないわけである。 しかも、 挿入しなければならない命令が、 例えば11行目と12行目の間にあった場合、 行をいちいち書き直すため、 悲惨な目に遇う。

一方、 フリーフォーマットな言語は、 プログラムの構造さえきちんとしていれば、 どこに何が入ってもすぐに修正できるので負担が少なくなる。 そこがフリーフォーマットの良さであり、 そして、今ではフリーフォーマットが最も用いられている訳でもある。

(2000.10.11)
プロセス〔ぷろせす〕(process)

プロセスとは「実際に動いている状態のプログラム」を指す。 計算機資源 を使いながら計算をしたり、 データを加工したり、 他のプログラムからの通信を待ったりといろいろな仕事を行う。 その意味で「タスク」や「スレッド」と同義である。

UNIXでは新しいプログラムを動かす際、 まず、 プログラムを動かす元のプロセスのそっくりさんを作り (forkするという)、 そっくりさんにプログラムを実行させる。 実際にC言語で記述する際、 そっくりさんはシステムコール fork を用いて生成し、 できたばかりのそっくりさんはプログラムをシステムコール execve で実行する。

WWWサーバがCGIを実行する際、 接続を受けていたプロセスがそっくりさんを生成し、 そのそっくりさんがCGIプログラムを実行する。 元のプロセスは他のクライアントからの接続を待つ。 すなわち、 サーバはCGIを実行しつつ、 他の接続を待っていることになる。 この「何かをしながら別の何かをする」機能はOSが提供している。 以降、 OSから見たプロセスについて考えてみる。

OSはプロセスに対して計算機資源の割り振りを行う。 大抵の計算機にはCPUが1つしか搭載されていないものの、 複数のプロセスを扱うことができる。 実はOSは各プロセスに対して少しずつCPU資源を与えることで、 見掛け上、 複数の仕事を同時にこなすことができる。 すなわち、 ある仕事をちょこっとし、 別の仕事をちょこっとし、 また別の仕事をちょこっとし‥‥‥と、 目まぐるしい早さで仕事を切り替えて複数のプロセスを実行している。

ここで問題となるのが、 プロセスが多くなるほど、 CPUやメモリの割り振りと、 プロセスの切り替えをするための管理が大変になることである。 大抵のOSでは仕事の優先度に応じてプロセスの管理を行っている。 そのため、 プロセスに対するCPU資源の割り振りを決める作業はかなり複雑である。 すると、 プロセスを作るほど、 管理に要する手間が多くなり、 仕事を片付けるのに時間がかかってしまう。 プロセスを管理する仕事は本来の作業ではないのに、 その管理だけに追われて、 結局何をしているか解らなくなるからである。 この種の余計な仕事による負荷をオーバヘッドと言う。 各プロセスを動かすのが本来の仕事、 それらのプロセスを管理する余計な負荷がプロセス管理によるオーバヘッドである。

すなわち、 CGIを実行するサーバに寄って集ってアクセスされると、 それだけ余計なプロセスが作られ、 サーバの負荷が大きくなる。 従って、 システムの負荷を小さくしたい場合、 余計なプロセスは作らないべきである。 アクセス集中による余計な負荷の増大は無視できない問題である。 そのため、 実用的なサーバには余計なプロセスを作らない工夫が凝らされている。

ちなみに、 プログラムの本体を含めてメモリ資源を共有し、 CPU資源の割り振りを単純化したプロセスがスレッドである。 OSによる管理もプロセスに比べて楽なので、 そういった意味で、 LWP(light weight process)、すなわち、軽いプロセスと呼ばれている。

(2002.3.8)

ま〜も

マニュアル〔まにゅある〕(manual)

マニュアルと言うと、 どうしても、 電話帳みたいな巨大な書物を連想してしまう。 ただ、 このコーナーで言う「マニュアル」は、 十中八九UNIXのマニュアル、 すなわち man コマンドで見られるあれ、 である。 日頃からUNIXにたしなんでいる方々は、 man のついでに whatis や apropos を考えたり、 GNUのツールの場合は texinfo を思い浮かべるかもしれない。 いずれにせよ、 UNIXはソフトウェアなので、 そのマニュアルもソフトだ。 理解できるまではハードだけど。

実はC言語でCGIを作る場合、 遅かれ早かれ、 システムコールが必要となる。 ファイルをロックしたり状態を調べたりと言ったことは、 標準ライブラリ関数ではできないからである。 同じUNIX系のOSでもシステムコールが全然違ったりする場合もあるため、 その都度、 各処理系のマニュアルを調べる必要がある。 幸い、 世の中にあるHTTPサーバのほとんどは Linux をはじめとする POSIXに準拠したOSであるため、 その差異はヘッダファイル程度で済んでいるのが現状。 システムコールのマニュアルはUNIXのマニュアルのセクション2に分類されている。 read や write のマニュアルが出ない場合は、 セクションの指定も忘れずに。 2番だけあって通好みだ。

各UNIXのマニュアルを理解しようとすると、 相当の前提知識が必要になる。 と言うのも、 マニュアルを書いた本人はその分野を知り尽くしている人なので、 そういった方々が要領よく把握できるように書かれているからである。 例えば、 yacc のマニュアルにはBNFやLALRといった基礎知識も解説されていなければ、 ruby のマニュアルにはオブジェクト指向と手続き指向の違いも解説されていない。 良く解っている人向けの要領の良い文書である。 一方で、 sh(普通のBourneシェル) や awk や perl といったマニュアルは、 それぞれの得意な処理だけでも知っていれば十分読むことができる。 と言っても、 マニュアルを読む前に、 どこかで別の解説書を手に練習したと思う。

なお、 sh(ついでに sed や grep や expr)も awk も perl も習熟し、 状況に応じてある程度自由に使えるようにしたい。 確かに sh や awk でできることは perl でも可能である。 しかし、 目的を達成するまでの手間(スクリプトの長さ)は 、 sh や awk よりも多くなっているはずである。 ちょっとしたテキストの操作作業も要領良く。 そうするには前提知識を学習し、 各種ツールに習熟しなければならない。 熟練度が上がるにつれ、 ちょっとマニュアルを読んで仕事を要領よくやっつけることができ、 本質的なことを考える時間が増える。 情報系で本当に凄い人は、 普通の人100人分以上の仕事をしてしまう。 この圧倒的な差は聖闘士〔セイント〕と聖域〔サンクチュアリ〕にいる雑兵のそれと良く似ており、 毎日戦ったり修行したりして小宇宙〔コスモ〕を燃やさなければならないのである。 キミはソースコードを感じているか(第1感)! アーキテクチャを感じているか(第2感)! システムを感じているか(第3感)! 顧客を感じているか(第4感)! 顧客が法人だったら顧客の顧客も感じているか(第5感)! 技術動向を感じているか(第6感)! システムとかソフトウェアとかで戦おうと思うと、 他のこともいっぱい感じなければならないのである。 それでも、セブンセンシズは‥‥‥いらないと思う(笑)

(2003.6.10)

ら〜ろ

ライブラリ関数〔らいぶらり かんすう〕(library funciton)

種々の目的で、種々の局面で使うことがあるプログラムは、 個々のプログラムとは別にまとめられることが多い。 こういったプログラムはライブラリとか言われている。 C言語は手続き指向プログラミング言語なので、 この種のプログラムは関数単位になり、 ライブラリ関数と呼んでいる。 ちなみに、 Java言語はオブジェクト指向言語であるため、 汎用的で使用頻度の高いプログラムはクラス単位になり、 クラスライブラリと呼ばれている。 もうちょっと脱線すると、 C++言語はテンプレート、型に透過的なクラスや関数、があり、 可変長配列や整列アルゴリズムは標準テンプレートライブラリ (standard template library : STL)として標準化されている。 結局は、 道具箱の道具と同様に、 様々な用途に何度も繰り返して利用するために設計したプログラムがライブラリである。

さて、 C言語の話題に戻すと、 使用頻度の高い printf や strtol や strncmp は標準ライブラリ関数である。 それぞれ、stdio.h 、stdlib.h 、string.h に定義されており、 #include< stdio.h > と、 #include< stdlib.h > と、 #include< string.h > という「おまじない」を書くだけで使える関数である。 stdio は STanDard Input/Output の頭文字で標準入出力、 stdlib は STanDard LIBrary で標準ライブラリ、 string はストリングと呼ばれる文字列(C言語の文字の配列)である。 初心者の頃は #include 以外の「おまじない」がいっぱいのコードを見て たじたじになったけれども、 何が何を意味しているのか知ってしまえばへっちゃらになり、 もっと熟練すると自分専用のライブラリのためにヘッダを書くことになるはずである。 呪術もその理屈が理解され、自在に再現できれば科学である。 理屈に翻弄される側から使役する側へ。 人間と道具という本来の関係に戻るのだ。

なお、 学習ではなくて実務が目的のプログラムを作成する際、 必要な機能を実現するコードを書く前に、 ライブラリ関数として既に提供されているか調べるべきである。 ライブラリ関数自体、 あるいは、 それをちょっと組み合わせるだけで済んでしまうからである。 例えば、 配列の要素を一定の順序で整列するアルゴリズムは、 標準ライブラリで用意されている。 ただし、 整列アルゴリズム自体の学習が目的の場合、 ライブラリ関数を使って省力化してしまっては無意味である。

(2002.6.8)
C言語で作るCGI入門の目次へ
ホームページへ