19章_システム依存



19.1:
RETURNキーが押されることを待つことなしにキーボードから1文字読 むのはどうすればいいのか。入力を受け付けてるあいだ文字が画面に エコーされないようにするにはどうすればいいのか。

A:
やれやれ、Cには上のようなことをおこなう標準的な方法も移植性の 高い方法もない。画面とかキーボードとはどういうものを指すのかさ え規格の中では出てこない。規格は簡単な、文字のI/Oストリームし か扱っていない。

どこかの層で、キーボードからの入力は通常いったん集められ一度に 一行ずつ、入力を要求しているプログラムに渡される。これによって オペレーティングシステムが入力行の編集機能(バックスペース/デリー ト/ruboutなど)を一貫した方法で、しかもプログラムごとに組み込む 必要なく用意するすることが可能となる。ユーザーが納得して改行キー (あるいは類似のキー)を叩いて初めて入力行は呼び出した側のプログ ラムから使えるようになる。プログラムが一度に一文字(getchar()の ようなルーチンを使って)入力を読んでいるように見えても、最初の 呼び出しはユーザーが行全体を入力するまでブロックされる。行全体 が読み込まれた時点では、多くの文字が利用可能になり、矢継ぎ早に 文字の取り出しも(例:getchar()の呼び出し)満たされる。

プログラムに文字が到着するとすぐに読ませたいなら、処理の流れは 入力ストリームのどこで入力を1行分集めているかと、1行分集めるの をやめるにはどうすればよいかに依存する。システムによっては(例: MS-DOS, あるモードでのVMS)、プログラムは異なった、または修正さ れたOSレベルの入力コールの一組を使うことで、一度に一行分丸々処 理することを迂回することができる。その他のシステムでは(例:Unix、 別のモードでのVMS)、オペレーティングシステムのシリアル入力を担 当している部分("ターミナルドライバー"と呼ばれることが多い)を、 一度に一行分丸々は処理しないモードに移す。移った後は、通常の入 力ルーチン(例:read(), getchar()など)は文字を即座に返す。いくつ かのシステムでは(特に古い、バッチ処理中心の汎用機では)入力の処 理は周辺のプロセッサーで行われ、一度に一行丸々処理させるかどう かしか命令することはできない。

よって、一度に一文字を入力することが必要なら(あるいはキーボー ドからの入力時の画面へのエコーをとめる必要があるなら。これは類 似の問題である)、使っているシステムに固有の技を使う必要がある。 comp.lang.cはCで手におえる話題が中心なので、上のような質問は comp.unix.questionsやcomp.os.msdos.programmerといった特定のシ ステムのニュースグループで聞くか、そういうグループのFAQを見る ほうが普通はよい解答が返って来る。システムが違っても解答は共通 であることが多いが、システム特有の質問に答えるときは、君のシス テムにあてはまる解答が他の人全部にあてはまる解答でないかもしれ ないことに注意すること。

しかしながら、これらの質問はあまりにも何度も聞かれるので、以下 によくある状況用の簡潔な解答をつける。

cursesの中には関数cbreak()を用意しているものもある。これが望み の機能を果たす物である。画面に表示することなくパスワードを読み たいというのであればgetpass()を試せばよい。Unixでは、ターミナ ルドライバーのモードをいじるのにioctlを使えばよい(「古典的な」 システムではCBREAKやRAW、SYSTEM VやPOSIX準拠のシステムでは ICANON、c_cc[VMIN]、c_cc[VTIME]、すべてのバージョンでECHO)。最 後の最後の方法としてはsystem()とsttyコマンドを使う。(もっと詳 しく知りたければ、古典的なバージョンでは<sgtty.h>とtty(4)、 System Vでは<termio.h>とtermio(4)、POSIXでは<termios.h>と termios(4)を参照せよ。) MS-DOSでは、getch()やgetche()を使うか、 対応するBIOSの割り込みを使う。VMSでは、スクリーン管理機能 (SMGZ$)やcurses、一度に一文字欲しいのであれば低レベルの$QIOを IO$_READVBLKを(たぶんIO$M_NOECHOも必要だろう)付けて試すこと (VMSの端末ドライバーで一度に一文字、すなわち"素通し"モードを設 定することも可能である)。そのほかのシステムでは、自分でやるし かない。

(ついでに、setbuf()やsetvbuf()を使ってstdioをバッファしない ようにするだけでは一般には一度に一文字の入力を実現することがで きないことに注意。)

移植性の高いプログラムを書くつもりなら、よい取り組みかたは(1) ターミナルドライバーや入力システムを(必要なら)一度に一文字ずつ のモードに移す関数、(2)文字を取得する関数、(3)関数の処理が終わっ たらターミナルドライバーを最初の状態に戻す関数の3つの関数を一 組にしたものを自分で定義ことである。(理想的には、このような関 数の集合は、いつかC規格の一部になるかもしれない。) このFAQの拡 張版には(質問20.40参照)、いくつかの普及しているシステム用の例 も載っている。

質問19.2も参照のこと。

References:
PCS Sec. 10 pp. 128-9, Sec. 10.1 pp. 130-1; POSIX Sec. 7.


19.2:
読み込むことができる文字が残っているかどうか(できるなら後いく つ残っているか)知ることができるか。できないとしたら、文字が入っ てこないときに処理が止まってしまわないような読み込みはどうやれ ばよいか。

A:
これも、まったくO/Sに依存した問題である。cursesの中には、その ような目的のために関数nodelay()を用意しているものもある。シス テムによっては、「ブロックしないI/O」や「select」とか「poll」 という名のシステムコール、FIONREAD ioctl、c_cc[YTIME]、kbhit()、 rdchk()、あるいはopen()やfcntl()のO_NDELAYオプションを用意して いるかもしれない。質問19.1も参照のこと。


19.3:
処理の何%まで終了したかを表示して、それをその場で更新させるに はどうすればよいか。また仕事の進んでいることを"バトンを回す"こ とで知らせるのはどうやればよいのか。

A:
こういう簡単なことであれば、結構移植性高く行うことができる。文 字'\r'を出力すると、普通は行送り(line feed)することなく復帰 (carriage return)する。それで現在行を上書きすることができる。 文字'\b'はバックスペースを表わし、普通はカーソルを左に1文字動 かす。

References:
ANSI Sec. 2.2.2; ISO Sec. 5.2.2.


19.4:
どうすれば画面に表示されている文字を一掃することができるか。ど うすれば反転文字で出力することができるか。どうすればカーソルを、 指定したx,y位置に動かすことができるか。

A:
この手の話は、端末の型(や表示装置)の種類に左右される。こういう 処理にはtermcapやcursesのようなライブラリー、あるいはシステム 固有のルーチンを使わなければいけない。

画面をクリアするのに多少でも移植性の高い方法は、用紙送り (form-feed)文字('\f')を印字することである。この文字を印字する ことで表示されている内容がクリアされるものもある。もっと移植性 の高い方法は、今表示されているものが全部見えなくなるだけ改行を 印字することである。最後の手段として、system() (質問19.27参照) を使ってオペレーティングシステムが用意している画面クリアの命令 を呼び出すこともできる。

References:
PCS Sec. 5.1.4 pp. 54-60, Sec. 5.1.5 pp. 60-62.


19.5:
矢印キーを読むにはどうしたらよいか。ファンクションキーは。

A:
terminfoやいくつかのtermcapやいくつかのcursesは、こういうASCII コードにないキーに対応している。よくあるのは、特殊なキーは複数 の文字を連続して送り出す(普通はESC, '\033'で始まる)。こういう 文字の並びの構文解析は慎重を要するものになる可能性がある (cursesなら、最初にkeypad()を呼べば、構文解析までやってくれる)。

MS-DOSではキーボードからの入力を読んでいて値0('0'ではない)を持 つ文字を受け取ったら、それは次に読む文字は特別なキーを表わす符 号であることを意味している。どんなDOSのプログラミングガイドでも いいからキーボードのコードを参照すること。(超簡単に説明する。 上、左、右、下矢印キーはそれぞれ72、75、77、80である。ファンク ションキーは59から68に対応する)

References:
PCS Sec. 5.1.4 pp. 56-7.


19.6:
どうすればマウスの出力を読むことができるか。

A:
対象とするシステムの資料を参照すること。あるいは適切なシステム 専門のニュースグループでたずねること(当然FAQを先に調べること)。 マウスの取り扱いは、XウインドウとMS-DOSとMacintoshではまったく 異なる。たぶん他のシステムでも異なるだろう。


19.7:
シリアル(COM)ポートを使ったI/Oはどうやればよいか。

A:
それはシステム次第である。Unixではよく、/devにあるデバイスをオー プンしたり(open)、読んだり(read)、書き込んだり(write)する。ま たデバイス ドライバーの機能を使って、I/Oの特性を調整する。 MS-DOSでは基本的なBIOSの割り込みを使うか、(かなりの性能を要求 するときは)世の中にたくさんある割り込み駆動のシリアルI/Oのパッ ケージを使う。ネットワーク上で何人かがJoe Campbellの書いた『C Programmer's Guide to Serial Communications』を推薦している。


19.8:
出力先をどうすればプリンターに切り替えることができるか。

A:
Unixではpopen()(質問19.30参照)を使ってlpやlprプログラムに書き 込むか、/dev/lpのようなスペシャルファイルをオープンする。 MS-DOSでは、(C規格の標準ではないが)あらかじめ定義されたstdioの ストリームstdprnに書き込むか、スペシャルファイルであるPRNや LPT1をオープンする。

References:
PCS Sec. 5.3 pp. 72-74.


19.9:
エスケープシーケンスを使って、端末やその他のデバイスを制御する のはどうすればいいのか。

A:
そもそもデバイスに文字を送り込むのはどうやればいいか分かってい れば(上の質問19.8参照)、エスケープシーケンスを送り込むのは何で もない。ASCIIでは、ESCのコードは033(10進では27)なので、

	fprintf(ofd, "\033[J");

のようなコードを書けばESC [ Jというシーケンスを送り出す。


19.10:
グラフィックするのは。

A:
むかしむかし、Unixにはデバイスに独立して図示する、plot(3)や plot(5)に記述された、小さくまとまった結構よくできたルーチンが 存在した。しかし、ほとんど使われなくなってしまった。

MS-DOSでプログラムするときは、VESAやBGIに準拠したライブラリー を使ったほうがいいだろう。

プロッターの種類がはっきりしてるときは、絵を描かせることは適切 なエスケープシーケンスを送る話になる。したがって質問19.9を参照 のこと。プロッターのベンダーがCから呼べるライブラリーを提供し てるかもしれないし、ネットから引っ張って来ることができるかもし れない。

特定のウインドウシステム(Macintosh、Xウインドウ、マイクロソフ トWindows)用のプログラムには、そのシステムが持つ機能を使う。関 連する資料やニュースグループやFAQを参照のこと。

References:
PCS Sec. 5.4 pp. 75-77.


19.11:
ファイルが存在するかどうかはどうやって調べればよいか。指定され た出力ファイルがすでに存在するときにはユーザーに了解を取りたい。

A:
この判定を信頼性が高く移植性も高く実行することは意外にも難しい。 どんなテストも、テストをしてからファイルを開こうとするまでの間 に(その他のプロセスによって)生成されたり削除されたりすれば、無 効になってしまう可能性がある。

解答として考えられる3つはstat()とaccess()とfopen()である。 (fopen()を使ってファイルの存在をざっとテストするには、ファイル を開いて即座に閉じればいい。)この3つの中でfopen()だけが広く移 植可能で、access()はもし存在しても、Unixのset-UID機能を使った プログラムでは注意深く使う必要がある。

ファイルを開くような作業がうまくいくか前もって予測しようとする より、開いて見て、戻り値を見て、失敗してたら文句をつけるほうが いい考えである。(すぐわかるように、これでは、ファイルが存在す るときは上書きしたくないときには、ファイルを開くときにO_EXCLオ プションのようなものがないとうまくいかない。O_EXCLオプションは この場合やって欲しいことをぴったりやってくれる)

References:
PCS Sec. 12 pp. 189,213; POSIX Sec. 5.3.1, Sec. 5.6.2, Sec. 5.6.3.


19.12:
どうすれば読む前にファイルの大きさを知ることができるか。

A:
もしも"ファイルの大きさ"が、C言語を使ってファイルから読むこと のできる文字の数ということであれば、一般にはこの数を前もって知 ることは不可能である。

Unixでは関数stat()が正確な答えを与えてくれる。他のシステムの中 にもUnixのstat()に類似の関数を用意しているものがあって、そうい う場合は近似値を与えてくれる。fseek()を呼んでファイルの最後ま で進んで、ftell()を使えばよい。しかしこれもstat()が抱えている のと同じ問題を抱えている。fstat()は移植性が高くないし、たいて いstat()が返すのと同じ情報を返す。ftell()はバイナリーファイル を除いては正しくバイト数を返すとは限らない。filesize()とか filelength()というルーチンを用意しているシステムもある。これら のルーチンも移植性が高いわけではない。

本当にファイルのサイズを前もって決めておかなければならないのか。 Cプログラムから見えるファイルのサイズを決定する最も正確な方法 は、ファイルを開いて読むことであるから、ファイルを読みながらサ イズを知るように書き換えたほうがいい。

References:
ANSI Sec. 4.9.9.4; ISO Sec. 7.9.9.4; H&S Sec. 15.5.1; PCS Sec. 12 p. 213; POSIX Sec. 5.6.2.


19.13:
まったく消し去ることや上書きすることなくファイルをその場で短く することはできるか。

A:
BSDシステムはftruncate()を、他のものはchsize()を、またいくつか は(ドキュメントには書かれていないだろうけれど)fcntlのオプショ ンとしてF_FREESPを用意している。MS-DOSではwrite(fd, "", 0)が使 えることがある。けれど頭のブロックを削除する、本当に移植性の高 い解決策も方法も存在しない。質問19.14も参照のこと。


19.14:
ファイルの真ん中から一行(あるいは1レコード)削除するのはどうや ればよいか。

A:
ファイルを書き直すのでなければ、たぶん不可能だろう。普通は、単 純にファイルを書き直す(レコードを削除するかわりに、削除したと 印を付けるだけにする手もある。こうすれば書き直しをしなくてもす む)。質問12.3019.13も参照のこと。


19.15:
オープンしたファイルのファイル識別子からファイル名を復活するこ とができるか。

A:
この問題は、一般に、解くことはできない。たとえばUnixではディス ク全体を端から端まで調べることが(たぶん特別な許可が必要となる だろうが)理屈の上では必要で、ファイル識別子がパイプや削除され た後のファイル(複数のリンクが張られているファイルに対しては誤 解をまねくような答えを返すだろう)を指していたら、この方法もう まくいかない。オープンしたファイルの名前を(たぶんfopen()に関数 を一段かぶせて)自分で覚えておくのが一番よい。


19.16:
どうやればファイルを削除することができるか。

A:
標準Cのライブラリー関数にremove()がある。(よってこれは、この章 の数少ない、解答が「システム依存」でない質問である。)古い、 ANSI制定以前のUnixシステムにはremove()はないので、その場合は unlink()を試してみること。

References:
K&R2 Sec. B1.1 p. 242; ANSI Sec. 4.9.4.1; ISO Sec. 7.9.4.1; H&S Sec. 15.15 p. 382; PCS Sec. 12 pp. 208,220- 221; POSIX Sec. 5.5.1, Sec. 8.2.4.


19.17:
どうして明示的にパスを指定してファイルが開けないのか。

	fopen("c:\newdir\file.dat", "r")

だとうまくいかない。

A:
ここで開こうとしているファイルは、つまりファイル名に\nや\fを含 んだファイルは、たぶん存在しないし、開こうとしてるつもりのファ イルでもない。

文字定数や文字列リテラルでは、バックスラッシュ\は拡張文字(エス ケープキャラクター)であり、次に続く文字に特別な意味を与える。 バックスラッシュそのものをパス名の一部としてfopen()(その他のルー チンでも)に正しく渡すには、バックスラッシュを2回続けて書かなけ ればいけない。一つめのバックスラッシュは二つめのバックスラッシュ をエスケープする。

	fopen("c:\\newdir\\file.dat", "r");

別のやりかたとしては、MS-DOSでは、スラッシュ'/'もディレクトリ の分離文字に使えることがわかっている。そこで以下のように書くこ とができる。

	fopen("c:/newdir/file.dat", "r");

(ところで、プリプロセッサーの#include前処理指令で出てくるヘッ ダーファイルの名前は文字列リテラルでないので、その中ではバック スラッシュのことを心配しなくてよい。)


19.18:
「ファイルを開きすぎ(Too many open files)」というエラーが出た。 同時に開けるファイルの数をどうすれば増やせるのか。

A:
同時に開くことのできるファイルの数には、実際には少なくとも二つ の資源の制限がある。下位の層の話である"ファイルディスクリプター "や"ファイルハンドル"をオペレーティングシステムがいくつ持って いるかと、stdioライブラリーの持つFILE構造体がいくつあるかであ る。両方とも十分な数でなければならない。MS-DOSでは、オペレーティ ングシステムのファイルハンドルの数をCONFIG.SYSに一行記述するこ とで制御することができる。コンパイラの中にはstdioのFILE構造体 の数を増やす命令(たぶんソースにも追加が必要)を持っているものも ある。


19.20:
ディレクトリの内容を読むにはどうすればよいか。

A:
opendir()やreaddir()が使えるかどうか調べること。これらのルーチ ンはPOSIXの一部で、たいていのUnixシステムに載っている。MS-DOS、 VMS、その他のシステム用の実装も存在する(MS-DOSには実質的に同じ 仕事をするFINDFIRST、FINDNEXTというルーチンも存在する)。 readdir()はファイルの名前しか返さない。ファイルについてもっと 情報が必要であれば、stat()を呼ぶ。ファイル名がワイルドカードと 一致するかどうかについては、質問13.7を参照のこと。


19.22:
どれだけメモリが使えるかはどうやればわかるか。

A:
オペレーティングシステムが、そういう情報を返すルーチンを用意し てるかもしれない。ただし、これはまったくシステム依存の話だ。


19.23:
どうやれば64Kより大きな配列や構造体を割り付けることができるのか。

A:
まっとうなコンピューターなら使用可能なすべてのメモリに、こっ ちが意識しないでアクセスできるのが当たり前である。運悪くまっと うでないコンピューターの相手をすることになったら、プログラム内 のメモリの使いかたを考え直すか、さまざまなシステム依存の技を 使うかする必要がある。

64Kというのは(それでも)けっこう大きなメモリの塊である。どん なにたくさんメモリがコンピューターに載っていても、大量のメモ リーを連続に切れ目なく確保しようとするのは欲張りすぎである。(C の規格は、一つのオブジェクトが32Kより大きくなることを保証して いない。)メモリ全体に切れ目がないことを必要としないようなデー タ構造を使うようにするほうが、いい考えであることが多い。動的に 割り当てた多次元の配列には、質問6.16のところで紹介したように、 ポインターへのポインターが使える。構造体の大きな配列を使うかわ りに、線形リストや構造体へのポインターの配列を使えばよい。

(8086ベースの)PC互換システムを使っていて640Kの壁にぶつかったら、 "ヒュージ(huge)"メモリモデルを使うか、拡張メモリ(訳注:日本 語ではexpanded memoryとextend memoryを区別する用語がない)を使 うか、halloc()やfarmalloc()のようなmallocの変形を使うか、セグ メントを意識させない32ビットの"平らな(flat)"コンパイラ(例: djgpp、質問18.3参照)を使うか、何かしらのDOSエクステンダーを使 うか、O/Sを取り替える。

References:
ANSI Sec. 2.2.4.1; ISO Sec. 5.2.4.1.


19.24:
「DGROUPデータの割り当てが64Kを越えた(DGROUP data allocation exceeds 64K)」というエラーメッセージは何を表わしているのか。ど うすればよいのか。ラージモデルを使えば64K以上のデータが使える ものだと思っていた。

A:
ラージモデルを使ってもMS-DOSのコンパイラは、ある種のデータ(文 字列、初期値付きのグローバル変数やスタティック変数の一部)をデ フォルトのデータセグメントに押し込む。そしてこのセグメントが溢 れる。グローバル変数の数を減らすか、すでにまあまあの量まで抑え ていたら(あるいは問題が文字列の数か何かなら)、コンパイラにデフォ ルトのデータセグメントをそんなにたくさん使わないようにさせるこ とができるかもしれない。"小さな"データオブジェクトだけをデフォ ルトのデータセグメントに置き、"小ささ"の敷居値を設定する手段 (たとえばマイクロソフト社のコンパイラでは/Gtオプション)を提供 しているコンパイラもある。


19.25:
あるアドレスに位置するメモリ(メモリマップされたデバイス、 あるいはグラフィックメモリ)にどうやってアクセスすればよいか。

A:
適切なデータ型のポインターを、望むアドレスの値に設定する。 (明示的にキャストをして、コンパイラにこちらが移植性のない変換 をする気があることを伝える)

	unsigned int *magicloc = (unsigned int *)0x12345678;

そうすれば*magiclocは望む場所を指す。(MS-DOSでは、セグメントと オフセットの相手をするのにMK_FP()のようなマクロが便利かもしれ ない。)

References:
K&R1 Sec. A14.4 p. 210; K&R2 Sec. A6.6 p. 199; ANSI Sec. 3.3.4; ISO Sec. 6.3.4; Rationale Sec. 3.3.4; H&S Sec. 6.2.7 pp. 171-2.


19.27:
Cプログラムの中から別のコマンド(単体の実行可能プログラム、O/S のコマンド)を起動したい。どうすればよいか。

A:
ライブラリー関数であるsystem()を使う。これはまさしく上記を実行 する。system()の戻り値はコマンドのexitのステータスで、コマンド の出力とは普通はぜんぜん関係ない。system()は起動すべきコマンド を表わす文字列を一つだけ引数として取る。複雑なコマンド行を組み 立てる必要があればsprintf()を使う。質問19.30を参照のこと。

References:
K\&R II Sec. B6 p. 253; ANSI Sec. 4.10.4.5; H\&S Sec. 21.2; PCS Sec. 11 p. 179;


19.30:
別のプログラムやコマンドを実行して、その出力を捕まえるのはどう すればよいのか。

A:
Unixや他のいくつかのシステムはpopen()ルーチンを用意している。 このルーチンはコマンドを走らせているプロセスにつなげたパイプへ のstdioのストリームを、出力を読み取れる(あるいは入力を用意する) ように設定するものである。

popen()が使えない環境なら、system()を使って、出力をファイルに 書き出し、後からファイルを開いて中身を読むことができるかもしれ ない。

Unixを使っていてpopen()では不足なら、pipe(), dup(), fork(), exec()といった関数を調べること。

(ところで、ひとつだけおそらく絶対うまくいかないのは、freopen() をつかうことである。)

References:
PCS Sec. 11 p. 169.


19.31:
どうすればプログラムが起動されたときの絶対パスを知ることができ るか。

A:
argv[0]に絶対パスやパスの一部が入っているかもしれないし、何も 入っていないかもしれない。argv[0]にパス名が入っているが完全で ないときは、シェルのようなコマンド行を解釈するソフトがコマンド を探す筋道を真似ればいい。しかしながら確実な方法は存在しない。

References:
K&R1 Sec. 5.11 p. 111; K&R2 Sec. 5.10 p. 115; ANSI Sec. 2.1.2.2.1; ISO Sec. 5.1.2.2.1; H&S Sec. 20.1 p. 416.


19.32:
実行可能プログラムと同じディレクトリにあるプログラムの設定ファ イルがどこにあるかを自動的に捜したい。どうやればよいか。

A:
これは難しい。上の質問19.31も参照のこと。使える方法を思い付い たとしてもプログラムの補助(ライブラリー用の)ディレクトリを、環 境変数かなにかを使って、変更可能にしたいと思うようになるかもし れない。(プログラムの設定ファイルを様々な場所に置けるようにし ておくのは、何人かでそのプログラムを使うときに、大事な話となる。 例:マルチユーザシステムで)


19.33:
どうすればプロセスが、起動した側の環境変数を変更することができ るか。

可能かもしれないし不可能かもしれない。それぞれのO/Sが、Unixに 類似の変数名と値を結び付ける機能を、それぞれの方法で実装してい る。"環境"が実行中のプログラムによって都合よく変更できるかどう かと、できるとすればその方法は、システムに依存する。

Unixのプロセスは自分の環境を変更することができる(setenv()や putenv()をこの目的のために用意しているシステムもある)。そして、 その環境はたいてい子プロセスに渡される。しかし親プロセスにまで 戻ることはない。


19.36:
どうすればオブジェクトファイルを読み込んで、その中にあるルーチ ンに飛び込むことができるか。

A:
ダイナミックリンカーやダイナミックローダーが必要である。領域を mallocして、その領域にオブジェクトファイルを読み込むことはでき る。しかしオブジェクトファイルのフォーマットやその配置などにつ いて、かなりの知識が必要となる。BSDではsystem()とld -Aを使って リンクさせることができる。SunOSやSystem Vの多くのバージョンに は- ldlというライブラリーが載っていて、これを使えばオブジェク トファイルを動的にロードすることができる。VMSでは LIB$FIND_IMAGE_SYMBOLを使う。GNUのパッケージに「dld」というの が存在する。質問15.13も参照のこと。


19.37:
1秒より細かい遅れや、ユーザの反応の1秒より細かい計測は、どう実 装すればよいか。

A:
残念ながら移植性の高い方法は存在しない。V7 Unixやそこから派生 したシステムは、かなり役に立つftime()を用意していた。この関数 はミリ秒単位の精度を持っていた。しかしSYSTEM VやPOSIXから消え てしまった。他に探すべき関数としてはclock()やdelay(), gettimeofday(), msleep(), nap(), napms(), setitimer(), sleep(), times(), usleep()がある(しかしながらwait()というルー チンは、少なくともUnixでは、望みのものではない)。select()や poll()は(もし手に入れば)、簡単な遅れを実装するのに使える。 MS-DOSマシンでは、システムタイマーやタイマーの割り込みをプログ ラムしなおすことで実現できる。

これらの関数の中でclock()だけがANSI規格の一部である。clock()を 2回呼んで、その差が経過時間を表わす。もしCLOCKS_PER_SECが1より 大きければ、差は秒以下の分解能を持つ。しかしながら、clock()が 返すのはclock()を呼び出したプログラムのプロセッサーの使用時間 で、マルチタスクのシステムでは実際に経過した時間とぜんぜん違う かもしれない。

遅れを実装するつもりだけれど、使えるのは時間を報告する関数しか ない場合でも、CPUパワーを一点に集中し処理待ちの状態を作り出し て遅れを実装することができる。けれどこれはシングルユーザーのシ ングルタスクのシステムでだけ許される選択肢である。というのも、 これは他のプロセスにとってはまったく迷惑な話であるからである。 マルチタスクのシステムでは、指定した時間だけプロセスを眠らせる 関数を使うこと。たとえばsleep()やselect()やpause()を、alarm() やsetitimer()と一緒に使う。

本当に短い遅れなら、なにも実行しないループを使いたい気がしてく る。

	long int i;
	for(i = 0; i < 1000000; i++);
けれど、どうやってでもこの誘惑を払いのけなければいけない。一つ には、注意深く計算したつもりの遅れを実現するループが、次の月に もっと速いプロセッサーが登場して動かなくなると相場が決まってい るからである。もっと悪いことに、賢いコンパイラならループは何も 仕事をしてないことに気が付いて最適化の際にまったく取り除いてし まうかもしれない。

References:
H&S Sec. 18.1 pp. 398-9; PCS Sec. 12 pp. 197-8,215- 6; POSIX Sec. 4.5.2.


19.38:
コントロールCのようなキーボード割り込みを捕まえたり無視するの はどうすればよいか。

A:
基本的な手順は、signal()を呼んで、

	#include <signal.h>
	signal(SIGINT, SIG_IGN);
のように割り込みシグナルを無視するとか、

	extern void func(int);
	signal(SIGINT, func);
として割り込みシグナルを受け取ったら関数func()に制御が移るよう にする。

Unixのようなマルチタスクのシステムでは、もう少し手の込んだ以下 のような技法を使うほうがよい。

	extern void func(int);
	if(signal(SIGINT, SIG_IGN) != SIG_IGN)
		signal(SIGINT, func);

テストと余分にsignal()の呼び出しを用意することでフォアグランド で打ち込まれて発生したキーボード割り込みがうっかりバックグラン ドで走っているプログラムに割り込みがかからないようにしている (どんなシステムで動かすにしても、このようにsignal()を呼ぶよう にコーディングしても悪いことにはならない)。

システムによっては、キーボードの割り込みの処理は端末の入力を処 理するサブシステムの特定のモードの機能である。

システムによってはキーボードの割り込みを調べるのは、プログラム が入力を読み込んでいるときにだけ実行され、どの入力ルーチンが呼 ばれているかによってキーボード割り込みの処理が変わってしまう (そもそも入力ルーチンが動いているかどうかによる)。MS-DOSシステ ムではsetcbrk()またはctrlbrk()が必要となるかもしれない。

References:
ANSI Secs. 4.7,4.7.1; ISO Secs. 7.7,7.7.1; H&S Sec. 19.6 pp. 411-3; PCS Sec. 12 pp. 210-2; POSIX Secs. 3.3.1,3.3.4.


19.39:
浮動小数点の例外を扱う気の利いた方法は。

A:
多くのシステムで、matherr()というルーチンを定義することができ る。このルーチンは<math.h>の中にある数学ルーチンで起きたエラー のような、ある種の浮動小数点のエラーが発生すると呼ばれる。また、 signal()(上の質問19.38を参照)を使ってSIGFPEを捕まえることがで きるだろう。質問14.9を参照のこと。

References:
Rationale Sec. 4.5.1.


19.40:
えーっと、どうやってソケットを使えばいいのか。ネットワークは。 クライアント/サーバーのアプリケーションの書き方は。

A:
上のすべての質問はこのFAQの範囲外で、C言語よりは使用するネット ワークの設備との関係のほうが強い。この件に関するよい本としては Douglas Comerの三部作であるInternetworking with TCP/IPや W. R. StevensのUNIX Network Programminが挙げられる。(ネットワー クそのものの上にもたくさんの情報が流れている)


19.40b: BIOSコールをどうやって使えばいいのか。どうすれば常駐ソフトが書 けるのか

A:
こういうのは特定のシステムに特有な話である(十中八九、MS-DOSが 走っているPC互換機だろう)。

特定のシステムの話を扱うcomp.os.msdos.programmerのようなニュー スグループやそういうグループのFAQからのほうがもっと情報が得ら れるだろう。その他の素晴らしい情報源としてはRalf Brownの割り込 みの一覧表がある。


19.41:
そんなこといっても、そんな標準でないシステム依存の関数を使うわ けにはいかない。私のプログラムはANSI規格に適合してなければいけ ない。

A:
ついてないね。君が要求事項を誤解してるか、そういう要求を満たす のは不可能なのかのどちらかだ。ANSI/ISO標準Cは上のような事柄を どうやって実行するのかを定義するものではまったくない。(POSIXはいく つか定義している。) システム依存の機能を、移植するシステムごと に書き直す少数のファイルの少数のルーチンに任せて、プログラムの ほとんどの部分をANSI規格適合にすることは可能であるし、そうする ことが望ましい。

目次へ戻る