ANSI - みる会図書館


検索対象: 月刊 C MAGAZINE 1991年6月号
33件見つかりました。

1. 月刊 C MAGAZINE 1991年6月号

の「比較」をどのように行えばいいかはデー タによって異なるのて、ある。 たとえば , 固定長の文字列データをソー トするようなケースだけを考えてみよう。 半角文字だけのデータだとしても , 単純に 文字コード順にソートすれば十分て、ある場 合もあれば , いわゆる辞書式配列 ( A と B の間 には a がこなければならない ) の場合もあ る。さらに半角カナ文字が含まれる場合に は濁点 , 半濁点 , 拗音 , 促音 , 長音などを 考慮して大小を判断しなければならす , 単 純に文字コード順にソートする場合とはま ったく異なる処理になる。さらに , 全角文 字のことを考慮しなければならない場合に はもっと複雑な処理が必要になるだろう。 したがって , これらの関数て、は「比較」処 理を外からえられる点が重要て、ある。ユ ーザがどのような大小比較処理を用いたい かをあらかじめすべてのパターンにわたっ て用意しておくことなどて、きないからて、あ 要するに , これらのライプラリ関数に渡 したいものは本質的には「ある処理手順」な のて、ある。「値」を渡したい場合には , 通常 の引数て、間に合うが , 「処理手順」を渡す場 合には通常の引数て、はうまくない。それは C て、は処理手順は関数によってのみ表現され るからて、ある。つまり本当は「関数」そのも のを引数として渡すことがて、きればそれが 望ましい。しかし残念なことに C て、は「引数 として関数を渡す」ことはて、きない。そこ て、 , 次善の策として「関数へのポインタ」を 引数として渡すのて、ある。 関数呼び出しにおける関数 グへのポインタの役割 最初に述べたように , ANSI 標準て、は関数 を呼び出す場合のセマンティクスに変更が 加えられている。 K & R の文法て、は , 関数呼 び出しは次のような形式を必要とした。 112 C MAGAZINE 1991 6 「・・・・・・を返す関数」型の一次式 ( 代表的 には関数名 ) の後に , 0 個以上の実引数 をカンマで区切ったものをカッコでく くった実引数リストが置かれた形式が 関数呼び出しである このような形式を要求していたために K & R て、は関数へのポインタを用いて関数を 呼び出す場合には , 「・・・・・・を返す関数へのポ インタ」 funcp に対して必ずインディレクシ ョンを行って「・・・・・・を返す関数」という型の 式 *funcp とし , 結合順位の関係て、それの両 側をカッコて、くくり ( * funcp) としたうえ て、 , 引数のカッコを指定して ( * funcp)( ) と いう呼び出しとする必要があったのて、ある。 ところが , ANSI 標準て、は関数呼び出しの 形式は実質的に次のものに改められた。 「・・・・・・を返す関数へのポインタ」型の後 置式 (postfix-expression) の後に , 0 個以 上の実引数をカンマで区切ったものを カッコでくくった実引数リストが置か れた形式が関数呼び出しである すなわち , 実引数のカッコの左側にくる 式に対する制約が「「・・・・・・を返す関数」型の一 次式」から「「・・・・・・を返す関数へのポインタ」 型の後置式」へと修正されたのて、ある。これ はさりげないが重要な変更て、ある。とくに 「関数」て、あったものが「関数へのポインタ」 と変更されたために , 従来必ず ( * funcp)( ) と書かねばならなかった関数へのポインタ を用いた間接的な関数呼び出しを , 通常の 関数呼び出しと同様の funcp ( ) と書くことが て、きるようになったのて、ある [ 2 ] 。 このことは , 次のような通常の関数呼び 出しが K&R と ANSI て、は異なった解釈がな されるということを意味している。 puts( ” HellO WO 目 d ” ) ; この関数呼び出しにおいて , puts という関 数名をどのように解釈するかて、ある。 K& R て、はこれは「関数」を指し示す一次式と解釈 される。ところが ANSI て、は「関数」を指し示 す式が , 前述のポインタ生成によって「関数 へのポインタ」型へ型変換された前置式て、あ るとみなされ , それが関数呼び出しに用い られると解釈されるのだ。 この違いは「関数へのポインタ」を関数呼 び出しに用いる場合に顕著になる。 ANSI て、 は「関数へのポインタ」に実引数リストを付 加したものが関数呼び出して、あるため , 関 数へのポインタ変数 funcp があったとした場 合 , 次のものがれつきとした関数呼び出し になる。 funcp( ) ; 一方 , K & R が要求した次の形式は , ANSI て、はどのように解釈されるか ? 解釈はこうて、ある。 ( * funcp ) は「関数への ポインタ」にインディレクションを施した「関 数」を表す式て、ある。したがって , ポインタ 生成によってそれは「関数へのポインタ」へ と型変換され , それに実引数リストを付加 したものて、あるため , これは関数呼び出し となる。つまり , 驚いたことに結局この形 式て、、、 * 〃をつけることは実質的に意味がな いのて、ある。 また , 「関数」を表す式からポインタ生成 によって「関数へのポインタ」を表す式への 型変換がなされることは , 関数へのポイン タ funcp に関しては次の二つの表現は同じ値 をもっということて、ある。 funcp と *funcp さらに , ANSI によって「・・・・・・を返す関数」 型の式に対して , 単項の「 & 」 , すなわちオペ ランドのアドレスを取り出してポインタを 生成するための演算子の適用が認められた ために , &*funcp の値も funcp に等しい とが保証される。結局次の三つの表現は値 も型もまったく同じて、あるという一見奇妙 な事態が生じることになった。 funcp と *funcp と &*funcp この「関数呼び出し」のセマンティクスに

2. 月刊 C MAGAZINE 1991年6月号

関数へのポインタ N mo 化 第 7 回 きだあきら してきた。今回は関数へのポインタについて解説する。 前回までは ( テータ ) オプジェクトへのポインタについてお話 関数へのポインタ くわからないとかいう場合が少なくないよ か関数へのポインタは苦手てあるとか , よ 通りポインタをマスターした人ても , なぜ タは難解て、あるという話をよく聞く。ひと い。それにもかかわらず , 関数へのポイン 扱うことがて、きるのは不思議なことてはな らないのだから , 関数へのポインタを取り する型の情報を備えたアドレス値にほかな してポインタは , 端的にいえば対象物に関 して操作することは可能なはずて、ある。そ 数と同様に , 関数もそのアドレスを取り出 て、メモリ上に記憶されるのて、あるから , 変 を除けば , 変数も関数もすべて何らかの形 考えてみれば , 記憶クラスが register の変数 になる。 C 言語のインプリメンテーションを 「関数」という実体を間接的に指し示すこと ている。関数へのポインタは , 当然ながら さて , C には関数へのポインタが用意され て、ある。 制限 ( 自由度と考えるべきか ? ) がっくだけ オプジェクトの型は何て、あるか不明」という 変わりはない。ただ void * は「ポイント先の ェクトを指し示すためのものて、あることに が違ってくるが , それて、も何らかのオプジ のものて、ある。なお , void * の場合少し話 体 ( オプジェクト ) を間接的に指し示すため C におけるポインタは , 一般に具体的な実 関数へのポインタは , 関数を間接的に呼 び出すための機能て、あるとも解釈て、きる。 目的とする関数名を指定して呼び出すのが , 一般的な直接呼び出して、ある。それに対し て , あらかじめ目的とする関数へのポイン タを , 「関数へのポインタ」型の変数に格納 し , そのポインタ変数の値を利用して呼び 出すのが「間接呼び出し」て、ある。「間接呼び 出し」の機能をもつ言語は C だけて、はない 有名なところて、は ,PascaI や FORTRAN に も存在する。さらに Lisp 系の言語て、は間接 呼び出しは日常茶飯に用いられている。し かし , それらの言語て、は , 間接呼び出しを 行うことが困難な作業て、あるとは認識され ていないようだ。て、はなぜ , C における関数 へのポインタは , 苦手意識をもって受け止 められるのだろうか。 関数へのポインタを難解なものにしてし まっている理由として , ーっはその構文的 な記法の問題があると思われる。関数への ポインタ funcp を宣言する場合は , 以下のよ うに書かなくてはならない ( int を返す関数へ のポインタ funcp , 引数の型情報は省略して いる ) 。 int (*funcp)( ) ; また funcp を通じて関数を呼び出す際に ANSI 以前のコンパイラて、は次のように宣言 と類似した形式て、書く必要があった ( 引数は ないものとする。また , 呼び出した関数が 返してきた値は無視している ) 。 これらの記法は , 見慣れた普通のポイン タ変数宣言や , 関数呼び出しとかなり異な っており , 一見して難解て、あると感じさせ てくれる。大げさにいえば , これらの記法 は , それを見慣れていない人にとっては何 のことか , わけのわからない呪文に等しい このことが , 関数へのポインタに対する最 初の , そして最大の問題て、あろう。しかし ながら , 幸いなことに ANSI 標準とそれ以前 のいくつかの処理系て、は , 以下のように記 述することが可能て、ある。 funcp( ) ; これならば , 通常の関数の直接呼び出し とまったく変わらない。この記述を可能に するために , ANSI て、は関数呼び出しに関す るセマンティクス ( 意味的解釈 ) を K & R のも のとは変更している。これは非常に興味深 ことなのて、後て、詳しく述べる。 ただ , 関数へのポインタを通して関数を 呼び出す場合には上述のように直接呼び出 し的な記述が可能になったが , 残念ながら funcp を変数として ( 関数の仮引数以外の場 所て、 ) 宣言する場合には , ANSI ても最初に あげたとおりの int ( * funcp)( ) ; という記述 を強いられる。 関数へのポインタが難解て、あるもうーっ の理由は , その必要性・必然性が理解され こともあるのてはないかと考えてい る。多くの教科書が適切な使用例を示して ことが , 関数へのポインタに対する 理解を阻んているのてはないだろうか。 ANSI C ー more 105

3. 月刊 C MAGAZINE 1991年6月号

SOFT BANK 五ロ 一三ロ ・ C 言語実用マスターシリーズ ー新 C 言語入門ビギナー編 複雑な C 言語の規則を整理、数多くのサンプ ルプログラムを駆使して、 " 誰て、もわかる C " を実現し 林晴比古著定価 1,900 円 ・ポインタ理解の秘伝を伝授 燾秘伝 c 言語問答ポインタ編 C 言語を習得する上て、、最大の難関と言われるポイン タについて、先生と生徒との問答形式て初心者にもわ かりやすく解 柴田望洋著定価 2.600 円 ・ C 言語の秘められた能力を解き放つ C プリプロセッサ・パワー C 言語の中て、も、とりわけ秘められたパワーをもつ プリプロセッサ機能の潜在能力を使いこなすにはどう すればよいかを解言林晴比古著定価 2 , 270 円 ・最新 ANSI C 標準規格に完全対応 詳説 C 言語 H & S リファレンス C 言語プログラマから高い評価を受けている「 C : A Reference Manual 」第 2 版の完訳。 S ・ハービソン & G ・スティール共著定価 4 , 500 円 ・ Turbo C の奧義を示す Tu 「 bO C Ve 「 20 プログラミンク 基本操作はもちろんのこと、内部処理の角財斤をはじ め、 BGI の活用方法など上級操作も詳細に解 井上哲理・酒井恵光・佐藤隆共著定価 2 , 900 円 ・初級 C 言語講座 PIay the C ( 上・下 ) C を身につけたい、これまて、の解説書て、はもの足り ない、そんな要求に応え C の世界を系統的にとらえ直 して解説。 林晴比古著定価各 1 , 550 円 ・最も簡潔で、最も完全な ANSI 標準 C の決定版 STANDARD C ANSI 標準 C の特徴をわかりやすく解説した 「 ANSI C の教科書」ともいえる一冊。 P. O. プラウガー・ J. プロティ共著定価 3 , 200 円 ・自然流プログラムのノウハウを公開 C によるプログラミンク・スタイルブック 「美しいプログラムはよいプログラムて、ある」という 持論とその成果を実証する、待望の一冊。全 C プログ 林晴比古著定価 2 , 300 円 ラマ必言売。 ・ MS ー C 活用の王道を示す ガ MS-C Ve 「 .5.1 プログラミンク 基巣作からコンヾイラの内部処理まて詳細に解説。 最産化のための手法、 ANSI 準拠のプログラミング作 石田秋也ほか著定価 3 , 300 円 法なども紹介。 ・ Quick C 活用書の決定版 0 Quick C Ve 「 .2.0 プログラミング グラミング 本書は、主要な機能からライプラリ関数、エラーメ ッセージまて、を網羅した全 C プログラマ必携の書。 本田正隆・三浦義武共著定価 2 , 900 円 ・ C STEP LJP シリース① 0 上級・ C 言語の応用 50 例 画面制御騙、ディスクドライプ制御騙ほか大きく 4 つに分け、それぞれくふうを凝らしてプログラムを作成。 上原・中山・石田・井上共著定価 2 , 480 円 ・ C STEP UP シリース② ++ 2 ミゾ 0 C 十十プロクラミング 今話題の言語、 C + + の本格的入門書。本書の各項目 をカード別にまとめ、サンカレリストや図版を豊富に 掲載。 門内淳・赤堀一郎共著定価 2 , 680 円 ・ C STEP UP シリース③ yacc による C コンバイラブログラミング 「 C マガジン」連載記事を大幅に加筆。字句解析・構 文解断の基礎からコード生成・最商化まて、、 c コンパイ ラの内部を徹底詳解。近藤嘉雪著定価 3 , 300 円 ・ BAS ℃からのアプローチ C 言語の活用理解 具体例を数多く取り上げ、 BASIC の知識を有効に 利用しつつ、 BASIC から C へ順次移行て、きるように 保坂光・若林淳共著定価 2 , 060 円 解説。 ・基本からのアプローチ C 言語の基礎知識 いけれど実用的なサンプルプログラムを紹介しな がら、 C 言語の基礎知識を解説。 門内淳・乗呆智・上原哲郎共著定価 2 , 580 円 ・実践からのアプローチ C 言語の応用 50 例 C 言語を使い 98k て、どのようなフログラムカ咐三れる のかを、豊富なサンプル例によって紹介。 上原・石田・乗松・中山・高木共著定価 2 , 370 円 2.0 0 C 言語の応用 50 例

4. 月刊 C MAGAZINE 1991年6月号

' 91 年 6 月号特別付録 ( 5 " 1.2M 8 セクタ / Human68k 用て、す。移植者 ( 岡田紀雄氏 ) の 本名てす ) ディレクトリへ ! 毎月 , 初心者 トラック MS-DOS フォーマット ) には , 次の のためのガイド ( 文書 ) やユーティリティな 「解を得て収録しました。 プログラムが収録されています。 どを提供するディレクトリてす。 LHAX68k のセットは LHAX68 ディレクト リに収録しました。なお Human68k 用の LHA 今月は , GCLS( グラフィック画面クリア ①付録ディスクの説明 は自己解凍ツールがサポートされていませ ツール ) , 漢字罫線変換ューティリティ ( 98 README ②高圧縮書庫管理プログラム ん。 x 形式の実行ファイルとドキュメントの →新 JIS), 半角全角変換関数 ( 本誌 4 月号「 C MAGA セミナールーム」課題の解答 ) てす。 < ディレクトリ > 圧縮ファイルというセットて、収録しました。 YLHA212 それぞれのドキュメントファイルを読んて、 , ③ X68k 版 LHA 「 LHAX68k 」 詳しくは README をご参照ください。ま こなしてください ! くディレクトリ > た , 岡田氏からの「 C マガジンの読者のみな YLHAX68 ④六角形プロック落とし「 Hextris 」 さまへ」 (YLHA68YREADME) というメッセ MS-DOS 互換指向 くディレクトリ > YHEXTRIS ージも収録しました。 , 一ください XC 「 . 2.0 ライプラリ ⑤ X68000 に移植された GNU C Compiler ② 六角形プロック落とし 本誌「 C プログラム移植の秘訣」をご覧いた くディレクトリ > YXGCC2 「 Hextris 」 ⑥「 ANSI C 言語入門講座」活用集③ だけたてしようか ? このライプラリは MS くディレクトリ > ー DOS 用の C プログラムになるべく手を加え YBOHYOH 本誌 1991 年 4 月号「フリーソフトウェア最 ⑦ MS-DOS 互換指向 XC 用ライプラリ ることなくコンパイルて、きるような環境を 新レポート」て、紹介した「 Hextris 」てす。著 Human68k 上に構築することを目的として YDOS2X くディレクトリ > 作権者 ( 斎藤靖氏 , ちりん氏 , 相沢雅陽 ( ヤ います。 ン ) 氏 ) の了解を得て収録することがてきま ⑧本誌掲載プログラム 収録したファイルは圧縮してあるのて , くディレクトリ > した。今回収録したものは FM ー R 用 , FM- YCM AGA 付録ディスクの説明 (README) に , 解凍 README を参考に解凍してからご使用くだ R の 24 ドット機用 , PC ー 9801 用の 3 種類あり 方法など , さらに詳しい説明が収録してあ ます。それぞれのファイルは HEXTRIS とい りますのて、必ずご一読ください 0README うディレクトリに圧縮ファイルとして収録 本誌掲載プログラム はテキストファイル形式てす。 MS-DOS の しました。 TYPE コマンド , あるいはお使いのエディ 詳しくは README をご参照ください。 タ , ワープロソフトて、読むことがて、きます。 : C 十十て書く単純な ~ TASK X68000 に移植された MODULA2 : Modula-2 による ~ GNU C CompiIere 高圧縮書庫管理プログラム PRAGMA : PragmaC プロジェクト : アルゴリズムと ~ ALGO 本誌「 GCC て、学ぶ 68 ゲームプログラミン 本誌 1991 年 1 月号「特集圧縮アルゴリズム MSDOS : 新 MS-DOS 入門 グ」の筆者 , 吉野智興氏のご好意により , 前 入門」て、紹介した「高圧縮書庫管理プログラ : C 移植の秘訣 DOS2H68 号収録した XGCC のソースファイルてす ( 正 ム LHA Ver. 2.12 」の公式公開版てす。著作 : 応用 C 言語 C の道具箱 OUYOU しくは前号て収録した実行ファイルのソー 権者 ( 吉崎栄泰氏 ) の了解を得て収録するこ : GCC て学ぶ 68 ゲーム X68K スに少し手を加えたものてすが ) 。 とがてきました。 LHA Ver. 2.12 のセット : ANSI C ー more ANSI XGCC 関連のソースファイルだけて、も量が は LHA212. EXE という自動解凍圧縮ファ : 明解 ANSI C 言語入門講座 MEIKAI 膨大なのて、 , 一度て収録するすることがて : スタートアップ C 十十 イルとして収録しました。 CPPJ きません。今回収録した LHA て、圧縮した複 解凍方法については README をご参照くだ : ワンポイントプログラミング O PPK 数のファイルを順次を収録していきます。 さい。解凍後に生成される LHA. I 刈 C は LHA TENSAKU : C MAGA セミナールーム 次号以降ぞもこの形式て継続して収録する 取り扱い説明書てす。 , 一読ください : 丹羽信夫の発想快発 N IWA 予定てす。 : C マガ電脳クラブ PUZZLE 「 ANSI C 言語入門講座」 X68k 版 LHA 「 LHAX68k 」 INFO : インフォメーション 活用集③ それぞれのプログラムについては関連記 ようこそ BohYoh ( [ ばうよう ] と読みます / 事ならびに各 README を参照ください。 「高圧縮書庫管理プログラム LHA 」の ( テキストファイル ) ディスク内容のお知らせ 159

5. 月刊 C MAGAZINE 1991年6月号

Fig. 10 DOS ファンクション 4EH , 4FH への対応 FlLES((b), (p), (a)1 0X20 ) #define dos findfirst(), a, b) \ #define dos findnext #define find t #define attrib #define wr time #define wr date #define size / *#define name NFILES FILBUF atr time date filelen name * / 0X00 0X01 0X02 0X04 0X08 0X10 0X20 #define findfirst(p, FlLES((b), (p), (a)1 0X20 ) #define findnext #define 幵 blk #define 幵 attrib #define 幵 ftime #define 幵 fdate #define 幵 fsize #define 幵 name #defi n e #defi ne #define #define #define #define #defi ne A NORMAL A RDONLY A 印 D D E N A SYSTEM A VOLID A S U B D 旧 A ARCH #define FA RDONLY #define FA HIDDEN #define FA SYSTEM #define FA LABEL #define FA D 旧 EC #define FA ARCH NFILES 日 LB U F a tr time date filelen name 0X01 0X02 0X04 0X08 Ox 1 0 0X20 ー c 処理系間の相違 こまて、くれば , あとは純粋に C 処理系間 の間題て、す。すて、に MS ー DOS 上の C 処理系て、 は ANSI 標準への適合がかなりのところまて、 プープロセッサ 進んて、います。 ん。要は , プログラム中て使われている関 いは , それ自体は移植の障害にはなりませ ヘッダファイルの不足やファイル名の違 へダファイル ません。 になります。とにかく削除しなければなり れないプロック中に現れた場合てもエラー if—#else—#endif て、指示されるコン / ヾイルさ いません。 #error, #pragma については , # います o#error,#pragma もサポートされて 文字列定数の中身まて、マクロ置換してしま には K & R 時代の仕様て、す。マクロ引数中の 部 ANSI 標準を取り入れてはいても , 実質的 XC Ver. 2.0 のプリプロセッサはごく一 数のプロトタイプやマクロが宣言 / 定義され ればよいわけて、す。たとえば ,XC には ANSI 標準の定めた errno. h がありませんが , error. h て、代用て、きます。また , MS-DOS の C プロ グラマはメモリ割り付け関数を使うときに ANSI 標準の定めた stdlib. h て、はなく , mal 10C. h や alloc. h といった処理系独自のヘッダ を取り込むことも多いようてすが , 単に malloc や free のプロトタイプを必要としてい るだけならば , stdlib. h を取り込むように修 正すればすみます。同じことは , メモリ操 作関数のプロトタイプを宣言した memory. h, mem. h についてもいえます。この場合 は , string. h に置き換えればよいわけて、す。 さらに , MS-DOS 上の C 処理系て、は UNIX の 伝統に従い , stat. h, timeb. h, utime. h とい ったヘッダファイルを sys サプディレクトリ に置いていますが , XC て、はすべてのヘッダ がひとつのディレクトリに並べられている のて , <sysYstat. h> を <stat. h> にするとい った修正を加えることになるて、しよう。 あるいは , あらかじめ sys というディレク トリを作り , 実際にその中に stat. h などをコ ヒ。ーしておく方法もあります。そうすれば , ソースの修正は不用てす。上の errno. h など も , ne Oint Edition #include く e 「「 0 「 . h > という 1 行のファイルを作って errno. h の名前 て、用意しておくとよいて、しよう。 こまて、は , 常識的な対応て、すみます。 しかし , ヘッダファイルの内容のわずかな 差が思わぬ事態を引き起こすことがありま す。マイナーな例て、すが , atof のプロトタイ プの問題を取り上げましよう。記憶が正し ければ , ANSI 標準の初期案て、は atof のプロ トタイプは math. h に置かれており , 途中て、 stdlib. h に移されました。 XC て、は , Ver. 1.0 まて、は math. h にあった atof のプロトタイプ を , Ver. 2.0 て、 stdlib. h に移動しています。 MS-C などて、は互換性を保っため , math. h のほうはそのまま残し , stdlib. h の両方て、 atof のプロトタイプ宣言を行っています。 math. h をインクルードして atof を使うようなプロ グラムは MS ー C て、は正常に動いても XC て、は ーライプラリ関数 誤動作することになります。 One Point Edition 87 memcpy るときには , ferror を使って対応しましよ クをきちんとしているプログラムを移植す 工ラーを返してくれません。工ラーチェッ XC の printf, fprintf, vprintf, vfprintf は printf 系 てみます。 中から , 注意が必要な関数をいくつかあげ う場合て、す。ここて、は , XC のライプラリの が細部の仕様がわずかに異なっているとい 題になるのは同一の名前 / 機能の関数はある 籍はいくらて、も手に入ります。むしろ , 問 ませんが , MS ー DOS 上の C 処理系に関する書 の機能がわからなければどうしようもあり ことになるて、しよう。もちろん , その関数 なければ , 機能を合わせた関数を自作する たような機能の関数があればそれて、代用し , 足という事態も起こります。その場合 , 似 移植するうえて、は , ライプラリ関数の不

6. 月刊 C MAGAZINE 1991年6月号

はスケジューラの中て、す。スケジューラは , 今中断したばかりのタスクを引き継ぐ次の タスクをセットアップし , それから , longjmp ( ) を使ってそのタスクの ( 以前にセープされて いる ) 地点へと飛び込みます ( 訳注 : 記述の理解には , setjmp( )/longjmp( ) の機 構の理解が必要て、ある。本稿て、は次の節く 強力な g 。 to > て、詳述される ) 。 一部のプロセッサのアーキテクチャは , コンテキストスイッチを高速化するために 最適化されています。そういうプロセッサ て、は , リストアするのはレジスタ ( スタック ポインタを含む ) の値だけて、す。それぞれの タスクに専用のスタック領域を与えること によって , スタックの内容のリストアを不 要にしています。 一部のタスキングシステムは , スケジュ ーラ中のフラグを使って , あるタスクを走 らせるべきか無視すべきかを判断します。 これは , コンテキストスイッチをした後に タスク自身が休眠に入るべきことを知ると いう方法よりは効率的て、す。しかし今回は , て、きるだけ単純な教育的システムが目的な のて、 , そういうフラグ系統は含めません。 高度な機能は , 後て追加て、きます。 同じ理由から , タスクの順序の並べ換え ( これは , たとえばデッドロックを防ぐため に必要になります ) や資源のロッキング , 他 のタスクの待機などもサポートしていませ 一定の順序 ん。スケジューラは , 単純に て、複数のタスクを切り換えていくだけて、す し , 個々のタスクは自分の実行の可否をチ ェックしません。また , これは , 非先制制 御方式のシステムて、す。スケジューラがタ スクから制御を奪うのて、なく , 各タスクが ーっまたは複数の地点て、 , 関数 task::sus pend( ) をコールすることによって , 自発的 に制御を放棄しなければなりません。 このシステムは , 単純なシミュレーショ ン用に使うのが主目的て、 , マルチタスクの 基礎を簡潔に示しています ( タスキングの細 16 C MAGAZINE 1991 6 aking control t0 Task 8 : 部を複雑にすると , 勉強意欲を損なってし まうて、しよう ) 。 強力な goto C て、は , g 。 t 。を使って同一関数中の任意の 場所にジャンプすることは , 完全に合法的 なコーディングて、す ( goto の使用に眉をひそ める人も多いが・・・・・・ ) 。しかし , 別の関数の 任意の場所へ飛ぶことは , goto 文て、は実現 て、きませんしまた通常は行われません。 C プログラマたちは , ある種の状況 ( たとえ ば例外処理からの復帰 ) て、は , ノンローカル な goto が絶対に必要だと感じました ( ローカ ルな goto は , 同一関数中だけて、有効な goto て、す ) 。ノンローカルな goto は , マルチタス キングにも必要て、す。そこて、 , ノンローカ ルな goto をサポートする二つの関数が ANSI C 規格に含められ , ANSI C 十十規格にも含 まれることになりました (ANSI C のライプ ラリは ANSI C 十十の一部となります ) 。そ の関数が setjmp( ) と longjmp( ) て、す。 別の関数へジャンプする場合に問題にな るのは , ジャンプ先のアドレスやその場所 の正しいマシンステートが , コンノヾイルや リンクの時点て、は分からないということて、 す。したがって , 関数 longjmp() は , 行き先 を指定するだけて、はだめて、す。レジスタや スタックポインタなどを正しい値にリスト アして , ジャンプ後にマシンが正しく動作 しているようにしなければなりません。環 境 , アドレスレジスタの値 , スタックポイ ンタなど , そのマシンアーキテクチャにと って必要な情報のすべてが , jmp buf という 名の構造体にストアされています。 そこて、 ,jmp buf を使用する前は , 初期化 されていなければなりません。それが setjmp( ) の仕事て、あり , この関数を , 後て、 longjmp() によって戻りたい場所て、最初にコールしま す ( 最初のコールて、は 0 が返される ) 。 ( その jmp buf を引数とする ) longjmp( ) がコールさ れると , その setjmp( ) が二度目のリターンを したような形となり , 今度は 0 以外の値が返 されます。 つまり ,jmp buf を引数として setjmp() を 一度だけコールし , そのコールからリター ンし , コードの実行を続けます。そしてそ の後の , その jmp buf を引数とする longjmp( ) のコールは , いずれも , その longjmp() コー ルの直後ヘリターンするのて、はなくて , setj mp ( ) の直後ヘリターンするのて、す ! て、す から , setjmp( ) が二度目のリターンをしたよ うな形になるのて、す。一度目との違いは , ( その見かけ上の setjmp ( ) の返り値が ) 0 て、は ないことて、す。環境のセットアップのため に最初に setjmp( ) をコールしたときは , その 返り値は 0 , そして longjmp ( ) の使用によっ て setjmp ( ) が ( 一見 ) リターンしたときは , そ の返り値は非叩。 ngjmp ( ) の第二引数の値 ) ということて、す。 longjmp ( ) をコールすることは , ある場所 BADJMP. CPP : setjmp( ) と longjmp( ) の間違った使い方 List 1 longjmp(jump_buffer, 0 ) : destination ( ) : 9 : ma i n ( ) { setjmp(jump-buffer) : 5 : void destination() { 3 : jmp-buf jump-buffer; #include く setjmp. h> 1 : / / BADJMP. CPP: setjmp() と longjmp ( ) の間違った使い方 6 : 4 : 2 :

7. 月刊 C MAGAZINE 1991年6月号

more に対する実引数だとはわからない点て、ある。 関する変更はかなり重要なものて、あると考 そのため , NULL は「 void へのポインタ」と えるが , 不思議なことにあまり強調されて 同じ型て、あるとしてコンパイルされてしま おらず , 単に ANSI 標準て、は (*funcp)( ) を うのて、ある。 funcp( ) と書いてもよくなっただけて、あると たとえば , i8086 て、メモリモデルにミディ 田われているらしいのは残念なことて、ある。 ッじ、 アムモデルやコンノヾクトモデルを使ってい 関数へのポインタとキャスト るケースを考えてみよう。これらのモデル て、は「関数へのポインタ」と「オプジェクトへ void w 「 iteSt 「 ing(cha 「 *st 「 , のポインタ」のサイズが異なっている。 void C の機能の中て、も , キャストによって関数 void writeChar(char)) へのポインタは「オプジェクトへのポインタ」 へのポインタを作り出すことがて、きる機能 と同じサイズを有することが要求される。 は , とびきり野蛮なものだといえる。とい うのも , これによって任意のアドレスをコ そのため , 関数へのポインタを期待してい るところへ , void へのポインタとしてコン ールすることが可能になるからて、ある。た パイルされた実引数が渡されることになり , とえば次の式を考えてみよう。 両者のサイズは異なるためにきわめてまず い事態 ( おそらく , NULL て、はないと判断さ これによって 0 番地をコールすることがて、 こて、 , writeString は二つの引数を受け れることになり , writeChar の値を default きる。なお , i80X86 の場合には , コンノヾイラ 取る。ーっは「 char へのポインタ」型の str て、 WriteChar へとセットすることなく間接的に とメモリモデルに依存して , この記述て、は あり , もうーっは「 char の引数を一つ受け取 関数を呼び出そうとして暴走する ) を招いて 「現 CS のオフセット 0 」をコールするコードが り , 値を返さない関数へのポインタ」型の しまうのて、ある。 生成される場合があるのて、 , 0000 : 0000 をコ writeChar て、ある。そうして , もし 2 番目の これを防ぐためには , プロトタイプ宣言 ールしたい場合には , たとえば次のように 引数に対して NULL が渡された場合には をきちんと行っておくことが重要だが , 実 己述する必要がある。 defauItWriteChar という関数を呼び出すよ 引数の NULL を以下のようにキャストする うにしている。 習慣をつけるのも悪くない これはアセンプラ並の低レベルな機能て、 こて、 , writeString の内部の if (write writeString( ” HeIIO world ” あり , C て、はいとも簡単に暴走するプログラ NULL) という比較はまったく問 Char (void(*)(char))NULL); ムを書くことがて、きる理由て、もある ( もちろ 題がないコーディングて、ある。 NULL に関 このように , NULL のキャストには意味 ん , これはあまりほめられたことて、はな しては自動的に型変換されてすべてのポイ があるものとないものがあることを知って ンタ型と自由に比較て、きることが ANSI 標準 おいてほしい ところが , このように少なからず問題が ては保証されている。 ある「関数へのポインタ」へのキャストが , ところが , これを実際に呼び出す場合に [ 注 ] 場合によっては必要不可欠の場合もある。 はどうなるて、あろうか。別なソースファイ [ 1 ] LSI C ー 86 Ver. 3.20 ( 試食版 ) てはこの それは引数として「関数へのポインタ」を期 ルから , プロトタイプ宣言なして、 writeString 仮引数の記述はエラーになる。次バ 待している関数への実引数として , 何らか を呼び出そうとして次のようにコーディン ジョンて、はサポートされるとのことて、 の定数・・・・・・たとえば NULL を渡す場合て、あ グしたとする。 ある。 る。この場合て、も , あらかじめ関数プロト w 「 iteString("Hello world", NULL); [ 2 ] 「一次式」が「後置式」に変更されたこと タイプの宣言が行われていれば , 何も問題 プロトタイプ情報がえられていない場合 , も見逃せない変更なのてあるが , 誌面 はない これは誤った結果を引き起こすかもしれな の関係て、 , それに関してはここて、は触 い。間題は , そのような場合に , の呼び ところが , それが行われていないと危険 れない 出し側のコードをコンパイルする時点て、 2 番 なケースがある。たとえば , 次のような関 数を考えてみよう。 目の引数て、ある NULL が「関数へのポインタ」 #include <stdio. h> void defauItWriteChar(char c) putchar(c); if (writeChar writeChar while (*st 「 ) writeChar( * st 「十十 ) ; NULL) defaultWriteChar; 一三ロ ANSI C more 113

8. 月刊 C MAGAZINE 1991年6月号

もうーっ , getFunc を呼び出している部分 にも注意しよう。これまた List 4 における table を用いた関数呼び出しと同様 , あまり 見かけない形式になっている。 printf( ” %4d", getFunc(k)(i, グするほうが , ループの最内側て switch な 十分な環境ては , List 7 のようにコーディン い。したがって , コンパイラの最適化が不 しようとするといちばん内側て行うしかな む。ところが switch て、呼び出し関数を決定 内側 <table[k] という参照を行わなくてもす List 7 のようにすれば , ループのいちばん ておく ) 。 行効率を高めることがて、きる ( main だけ示し ングは List 7 に示すように変更することて実 て、あるが , List 4 の main の内側のコーディ ような違いはある。実は先ほど示した List 4 行上のペナルティは少ない。ただし , 次の て、 , このように書き換えてもプログラム実 イルしたコードの実行効率はかなりよいの り , それて、なくても通常 switch 文をコンパ テープル参照にコンパイルされるものもあ コンパイラによっては switch は実質的に 6 のように表現することもて、きる。 ば , List 5 て、示した getFunc という関数は List とて、も代用することが可能て、ある。たとえ ば switch 文などを用いて条件判断を行うこ 呼び出し関数を「計算」する処理は , たとえ 念のために補足しておくと , このように のて、ある。 存在するという奇妙な表現が可能になった って , 引数をくくるカッコが二つ連続して のて、ある。これまた ANSI の新しい文法によ して本来行いたかった関数呼び出しを行う が返されるのて、 , それに二つの引数 i , j を渡 び出しを行う。その結果「関数へのポインタ」 すなわち , まず getFunc(k) という関数呼 printf("%4d' (*getFunc(k))(i, j)); R の文法て、は次のように書く必要があった。 すて、に想像されているように , これも K& どの手法を用いて呼び出し関数を決定する よりも望ましいといえる。ただし , コンパ イラの最適化の程度によっては ,List 4 のま まて、も自動的に List 7 と同じコードが生成さ れる場合もあり得ることは注意してほしい ( とにかく最近は非常に頭のよいコンパイラ があるのて、 , コーディングによって効率を 上げるということはだんだん困難な作業に なりつつある ) 。 外から機能を与えるための 関数へのポインタ 関数へのポインタには , もうーっ重要な 用途がある。それは , あらかじめ機能が決 定て、きない部分を関数へのポインタを用い て間接的に関数を呼び出すようにコーディ ングしておき , 実際の使用に当たって具体 的な機能を果たす関数へのポインタを供給 するというものて、ある。この用途のほうが , ほかの手段による代替が難しいという意味 て、 , 関数へのポインタのより本質的な用途 てあると考えることもて、きるだろう。これ を「外 ( そと ) から機能を与えるための関数へ のポインタ」と呼ぶことにしよう。 more して処理を行う必要がある。ところが , そ ーザ側が用意したデータの大小関係を判断 とがより端的になる。いずれの関数も , ユ ートを行うための qsort 関数などて、はこのこ 2 分探索を行うための bsearch 関数や , ソ 相談て、ある。 はなく , また一意に定めることもて、きない うな処理をしたいと望むかなどわかるはず してもいないプログラムの終了時にどのよ い。これは当然て、ある。ューザがまだ作成 行わせればよいかを決定することはて、きな ラリを作成した時点て、はどのような動作を 時点 , つまりコンパイラベンダーがライプ かに , atexit という関数それ自身を作成した を指定 ( 登録 ) するためのものて、ある。明ら ム終了時に自動的に呼び出してほしい関数 な atexit を例にとろう。この関数はプログラ たとえば , この 4 種類の中て、いちばん簡単 すべて外から機能を与えるためのものて、あ の関数が要求している関数へのポインタは ると Fig. 2 の 4 種類が見つかる。実はこれら ンタを引数として受け取るものを探してみ ANSI ライプラリの中から , 関数へのポイ Fig. 2 関数へのポインタを引数として受け取るもの ( 1 ) ( 2 ) ( 3 ) ( 4 ) #include VOid #include int #include VOid #include VOid く signal.h> ( * signal(int sig, void ( * func)(int)))(int) ・ く stdlib. h> atexit(void ( * func)(void)) ・ く stdlib. h> * bsearch(const VOid * key, const VOid * base, Size t nmemb, Size t size, int ( * compar)(const void * , const void * ) ) く stdlib. h> qsort(void * base, size t nmemb, size t size, int ( * compar)(const void * , const void * ) ) , ANSI C : more 111

9. 月刊 C MAGAZINE 1991年6月号

これはどのような型へのポインタかとい うと , 先ほどの Table と同じ型へのポインタ て、ある。すなわちトータルとしていえば table は次のような型の変数て、ある。 tab 厄は「 int の引数をニつ受け取り , int を返す関数」へのポインタの F 要素の配 さてここまて、きたら , 次には ( プログラム の構造化という観点からみると退歩なのだ が ) 関数 showTabIe をなくして main の中て、展 開してしまうことを検討しよう。結論から いうと List 4 のように記述すればよいのて、あ List 4 て、注目したいのは printf のところて、 使用している table を用いた関数呼び出しの 形式て、ある。 printf("%4d", table[k](i, これは K & R の時代て、はコンパイルエラー てある ( 今て、も一部のコンパイラて、はこのよ うな記述を通さないものがあるかもしれな い。しかしそれは ANSI 準拠て、はないという ことて、ある ) 。伝統的な記法て、は , 次のよう に書かねばならなかった。 (*table[k])(i, printf("%4d" 一見してわかるように , List 4 の書き方は 関数へのポインタ変数に対してインディレ クション ( 単項の * の適用 ) を行わなくても よくなったことによって可能になったもの てある。通常の感覚て、は配列の添字づけの うに修正したプログラムを示す。 っていたところが getFunc(int n) となってい 直後に関数の引数を表す力ッコがきている ることと , table の場合には初期化子が与え List 5 の注目点は getFunc という関数の定 のは奇妙に思えるが , ANSI C 標準にきちん られていたところに関数の本体が与えられ 義と , その呼び出し部分て、ある。まず定義 と従っているコンパイラて、あれば , これは ている点て、ある。 table の場合には [ F ] がつ のほうから考えてみる。 getFunc の定義は次 正しいプログラムて、ある。 いていたために配列変数となり , getFunc は さて少し視点を変えて , table という配列 のようになっている。 (int n ) がついているために関数となる。すな の代わりに「どの関数を呼び出すべきかを決 int (*getFunc(int n))(int, int) わち getFunc の型を文章て、述べればこうな 定する関数」を定義してみよう。その場合に は「呼び出すべき関数へのポインタを返す関 getFunc は「 int の引数をニつ受け取り , 数」を作ればよい このようにすると最初に int を返す関数」へのポインタを返す関数 述べた「呼び出すべき関数を計算する」とい であり , その引数は int n である う目的に近づくことになる。 List 5 にそのよ 引数の値に応じて呼び出すべき関数へのポインタを返す関数 LiSt 1 : / * switch を用いて分岐させたバージョン 2 : 3 : int (*getFunc(int n) ) (int, int) switch (n) { 5 : 6 : case 0 : return add; case 1 : 8 : 9 : return sub; case 2 : 11 : return mul; default: fprintf(stderr, "getFunc: is invalid argument*n ” , n) ; 13 : exit(l); 14 : 16 : } LiSt 1 : int 2 : min(void) static int (*tableCF]) (int, int) 4 : 5 : add, 6 : sub, mul, 8 : 9 : 10 : 11 : 14 : 15 : 19 : 20 : 22 : 23 : 24 : 25 : } / * 二つの int の引数を受け取り , int を返す関数へのポインタ f 皿 cp * / int (*funcp) (int, int) : int i, j, k; for k = 0 ; k く F; + + k ) { * k によって決定された呼び出すべき関数へのポインタを 内側のニつの for ループの外で funcp へとセットしておく * / funcp = tableCk] ; for (i = 1 ; i く N; + + i ) { for (j ー priÄtf{"hd" funcp(), j)) ; printf("}n ) ; printf( ”” ) ; return 0 ; これは , 先の table という配列の宣言と非 常によく似ている。違うのは table CF] とな 110 C MAGAZINE 1991 6

10. 月刊 C MAGAZINE 1991年6月号

MAGAZINE 1991 年 6 月 1 日発行 ( 毎月 1 回 1 日発行 ) 第 3 巻第 6 号通巻 21 号 1990 年 2 月 2 日第 3 種郵便物認可 提携・ LA ト、」誌 / 監修・石田晴久 C 言語技術情報誌・ C マカシン 1991 JUN Vol. 3 No. 6 0 980yen 特集・プログラマのための 知的財産権概論 拡大するソフトウェア保護の実態と問題点 1 ・著作権法に見る知的財産権 2 ・著作権法と特許法 3 ・米欧に見るソフトウェア保護の実態 新連載 X68k 活用講座 GCC で学ぶ 68 ゲームプログラミング One Point Edition C プロクラム移植の秘訣 提携記事 ・ Taking Cont 「 0 は 0 Task ・ MuItitasking in M0duIa-2 明解 ANSI C 言語入門講座 新 MS - DOS プログラミンク入門 スタートアップ C 十十 特別付録 : 5"2HD ・ X-GCC ② ・高速圧縮ツール「 LHA Ve 「 .2.12 」 ・ X68k 版 LHNLHAX68k 」 ・叭 NSI C 言語入門講座」活用集 3 ・六角形フロック落とし「 Hext 「 is 」 ・本誌掲載ソースプロクラム