TSG 部報 第 189 号・オリパンフ号

目次

[-> TSG Home Page]


TSG のご紹介

駒場の一角のコンピューターサークル
 TSG の正式名称は「東京大学理論科学グループ Theoretical Science Group」ですが、現在の活動はすべてコンピューターに関することです。
 駒場キャンパスの一角に部室があり、PC-9801 互換機が 2 台と FM TOWNS が 1 台置いてあります。毎日のようにいる人もいれば、月に 1 〜 2 度だけ くる人もいます。

 みんな部室で何をするのかというと、それぞれ興味を持っていることをす るだけです。エキスパートもビギナーも分けへだてありません。ゲーム作り、 ツール作り、音楽、お絵描きなど、いろんな趣味の人がいます。ゲームで盛 りあがったり、ただおしゃべりをするだけというのも、ごく日常的な光景で す。

活動は分科会ごとに行います
 もちろんサークルらしい活動も行っています。それが「分科会」です。
 分科会というのは、何人かで協力して1つのことをやる集まりです。昨年 度は「C 言語入門分科会」「PASCAL 言語入門分科会」「サッカー分科会」 「スケボー分科会」などが活動しました。
 部員はそれぞれ、好きな分科会でプログラムを作ったり勉強をしたりする わけです。

個人を主体としたサークルです
 この「分科会」という活動単位はハッキリしたものではなく、べつに 分科会に参加しなくてもいいですし、いくつの分科会に参加してもかまいま せん。
 主体はいつも個人です。TSG なんて、その環境の一つ、発表の場の一つで しかありません。しかし、TSG には本当のエキスパートと呼んでよい人が何 人も集まっているので、けっして退屈はしません。
 TSG は、気楽だけど、とても刺激的なサークルです。

 TSG は、他大の方や一般の方の参加も歓迎します。プログラミングができ なくてもいっこうにかまいません。私にご連絡いただければ、いつでもご案 内いたします。

〒153 目黒区駒場 3-8-1 東京大学学生会館 305 号
東京大学理論科学グループ 代表 渡辺尚貴 g440056@komaba.ecc.u-tokyo.ac.jp


分科会 お品書き

(NAO, Nishi, ちょもらんま)

 分科会の参加はまったく自由です。幾つもの分科会に出てもいいし、出なくても構いません。ただ楽しく一緒に活動しようというだけのものなのですから。

Pascal 分科会
C 言語分科会
C++ 分科会

 Pascal 分科会、C 言語分科会、C++ 分科会の 3 つの分科会では、各プログラミン グ言語を、初心者を対象に易しく解説していきます。分科会を開く時間は、参加者の 都合上放課後になってしまいますが、プログラムを学びたいというい方にはお勧めで す。
 分科会に入っていなくても、昼休みや空き時間には部室に人が必ずいるので、ちょっ としたことならそのときにも教えてもらえることでしょう。(以上 NAO)

Windows 分科会
 Windows 分科会では基本的には Windows 用アプリケーション (Win16) の開発、ヘルプファイルの作成について研究する予定です。言語は C か C++ を使う予定ですが、要望があれば、386 のアセンブラ上での開発 に関することや Win32API 等についてもやります。参加者は Windows の所 有者である必要は必ずしもありませんし (部室の PC0486GR が使えるから)、 言語製品 (Visual C++ や Borland C++ など) の所有者である必要もありま せん (持ってるに越したことはないけど) 。まだ、Windows 環境ではフリー ソフトウェアの数が (DOS に比べると) 少ないこともあり、余り手のつけら れていない分野も多く、ウィンドウズプロラマーとしての皆さんの活躍が期 待されているのです。バリバリのフリーソフトウェア作者を目指しているあ なた、自作のフリーソフトウェアを載せてもらって Pack5000 をタダで貰お うとひそかに考えているあなた、ぜひ、一緒にウィンドウズプログラマーを 目指しましょう。

TeX 分科会
 皆さん、TeX (「てふ」とか「てっく」という風に読む) を知ってい ますか。TeX はかの Donald Knuth によって開発された文書整形のためのシ ステムで、これを使うことにより、簡単に数式や記号、脚注などの含まれた 文章をつくることができます。ただ、TeX は文章に編集用の命令を書き加え ていく形式であるため、最近の Windows 上のワープロのようにマウスを使っ て編集、というわけにはいかず、最初はとっつきにくいところがあるのも事 実です。それでも、TeX を使うことはそれを補い余るほどのメリットがある のです。特に、機種、OS を問わず、同じ結果が得られるということは重要 です。あなたのパソコンでも、Unix 環境でも印刷所でも同じものが出力さ れるのです。そのため、いまや理系・文系を問わず、大学生には必須のもの となってきました。これで文章を作れば、そのまま印刷用の版下を作ること もできるのです。TeX 分科会では TeX をより使い易くした LaTeX の使い方 について研究します。皆さんも一緒に TeX を使いましょう。

マラソン分科会 (本当に作るのか !?)
 マラソン分科会はただひたすら走ります。で、1 日 5 キロ、1 週間 に 30 キロを目標に走ります。で、ともかく走ります。雨が降っても走りま す (これは、ちょっと無理か)。
 夏合宿が本当に伊豆大島に行くことになったら、伊豆大島を走って一周し ます。というわけで、マラソン分科会はただひたすら走ります。(以上 Nishi)

フラ語分科会
 「フラ語」は、フライパンで料理を作るための言語でもなければ、フ ラダンスを踊るための言語でもありません。東大の第 2 外国語の代表格で あり、このオリパンフを手にとって読んでいる方のなかにも、必ず選択者が いるであろう「フランス語」のことです。
 フランス語は、母音が 15 以上あったり、動詞の活用形が 50 近くあった り、発音に現れない文字が多数あってディクテーションの試験の前にはテキ ストを丸暗記しなければいけなかったりで、毎年単位を落とす人が後を絶ち ません。しかし、この分科会に参加していれば、何の心配もいりません。あ なたの成績表には、必ずや「A」の文字が刻まれることでしょう、たぶん (笑)。

TOWNS 分科会
 「TOWNS を使って、なにかしませんか ?」というのが、この TOWNS 分科会です。具体的に何をするかは、まだ決めていませんし、決める必要も ないかと思っています。C プログラミングでも、アセンブラプログラミング でも、グラフィックでも、サウンドでも、なんでもアリです。参加者が、やっ てみたいこと、体験してみたいこと、すべてが TOWNS 分科会の活動の対象 です。
 とにかく、TOWNS という最高のオモチャで、思いっきり楽しめればいい、 そんなふうに考えています。(以上 ちょもらんま)

なお、今年は情報教育南棟が開設されているので、上記の他にも

 UNIX 分科会
 X Window プログラム分科会

などができるかもしれません。


TSG のイベント

(ちょもらんま, NAO, 小島司)

  • はじめに
     TSG には、年間を通じて、様々なイベントがあります。こうしたイベ ントのうち、主なものについてまとめてみました。

  • 新入生への説明会 & 新歓コンパ
     5 月の上旬に、TSG に入った人、入るかどうか迷っている人のための、 説明会のらしきものがあります。去年は、分科会の説明や部員の自己紹介な どが行われました。ちなみに、新 2 年 (= 当時の新入生) 11 人のうち、こ の説明会のときにいなかった人が、8 人もいます。このことからも、TSG が、 いかに自由なサークルであるかがわかります (笑) 。
     説明会の数日後には新歓コンパがあり、渋谷に飲みに行きました。「飲み に」といっても酒がバシバシ出てくるわけではありません。部員のなかには お酒が好きな人もいることはいますが、ほとんどの人は、お酒は苦手です。 そんなわけで、TSG のコンパではソフトドリンクの嵐が吹き荒れます。

  • 夏合宿
     1 学期の期末試験が終わり一段落した頃、夏合宿があります。去年は 山中湖 (2 泊 3 日) にいきました。合宿して何をするのかというと、遊ぶ わけです。夜遅くまで、スーファミや D&D をやっていました。そういえば、 基盤とハンダゴテを持ち込んで、なにか作っているすごい方がいましたね、 たしか。
     すごいといえば、もっとすごい人がいます。2 日目に、山中湖 (一周十数 キロくらい) を自転車で一周したのですが、途中で、走っていた (←足で !) ある人物を追い抜いたのです。どっかで見たような気もしなくはなかったの ですが、「まさかこんなところを走っているわけは……」と思ってそのまま 通り過ぎたのです。あとでやはりその人であったことがわかり、周囲から驚 嘆の声が上がりました。
     え ? 「その人」が誰かって ? この部報の○科会のところをみればわかる よ。

  • 駒祭
     TSG 最大のイベントと言っていいと思います。この駒祭で、日頃の成 果を発表するわけです。
     まず、10 月下旬に「駒祭総決起コンパ」が開かれます。この席で、企画 の担当者はホラをふきます。そして、そのホラが少しでも現実に近くなるよ うにするのです。
     一年生は毎年、駒祭で占いをすることになっています。去年の占いがどう なったかは、去年の部報の私の原稿を見てもらえればわかるので、詳しいこ とは書きませんが、「準備は少しでも早い方がいいよ」とだけ言っておきま しょう (苦笑) 。
     駒祭後には、打ち上げコンパが開かれます。

     ちなみに、コンパにはここまでに書いたもののほかに、12 月下旬の「ク リスマスコンパ」、3 月上旬の「追い出しコンパ」があります。(以上 ちょ もらんま)

  • 冬合宿
     スキーの合宿です。冬休みの最後に行われます。昨年度は妙高高原に 行ってきました。
     スキーに一度も行ったことのない人は不安かもしれませんが、スキー初心 者でもいちおう滑れるようになります。部長の NAO はそれまで雪が 20 セ ンチ以上積もっているところを見たことが無かったのですが、この合宿でプ ルークボーゲンができるようになりました。
     アフタースキーで、「ああっ女神さまっ !! 」にはまっていた人物がいた のも、記憶に新しいところです。(以上 NAO)

  • ケーキパーティー
     ケーキパーティーは、毎年 1 月中旬に寮食堂にて開かれます。持ち 物は、各自 1 台、或いは自分の限界量のケーキ。つまり、みんなで持ち寄っ て、心ゆくまでお菓子を食べようという幸せな企画なのです (本当か ?)。
     参加する意義としては、主に次の 2 つが挙げられます。

    1. とにかく好きなだけケーキが食べられる。
       大きなテーブルに並べられた約 20 人分のケーキが消えてゆく様は、壮観 です。幾ら食べても顰蹙を買う事はありません。却って、讃えられます。

    2. 血糖値の上昇による寒気を体感出来る。
       こんな経験は滅多に出来るものではありません。供されるドリンクもマミ イなどのジュースなので、実に効率的にハイテンションな状態に達する事が 出来ます。物理的に寒気を加速するアイスクリームケーキを持参すると、喜 ばれます。
       ただ単に、寮食堂が寒いだけだという噂もありますが。^^;

     “こんな事をしたら、体に悪そう”“ケーキが嫌いになっちゃったら、ど うしよう”などとお考えになる向きもあるでしょうが、ご心配無く。今年は、 このパーティーの数日後に某喫茶店のケーキ食べ放題に行った人がちゃんと いました。
     いずれにせよ、4 年間の大学生活を実り多いものとするに足る、稀有な経 験を得られる機会と言えましょう。(以上 小島 司)


  • いぬ。BBS とは何ぞや

    (ったく☆ (Tak@inubbs, SysOp))

     はよーん。ったく☆、でーす。
     SysOpやってまーす☆

    [いぬ。BBS ってどういうところ ?]
     というわけで、いわゆる草の根パソコン通信 BBS 「いぬ。BBS」とか いうものの運用をやってます。パソコン通信がどういうものか、とかいうの は説明は不要ですよね ?
     そもそもいぬ。BBS は、コンピュータゲームサークル「あらかわ犬 (ウシ)」 のソフト開発・サポート BBS として運用すべく、1993 年に開設されました。 あらかわ犬というのは、TSG メンバー有志を中心にして結成されたサークル で、端的に言えば「ゲームを作ってコミケで売る」とかいうような活動をやっ ています。そういう目的で運用が始まったいぬ。BBS なのですが、運営母体 であるあらかわ犬は現在、活動を小休止している状態になってしまっていま す。
     で、現在いぬ。BBS はどうなったかというと、事実上フリートーク中心の BBS として運用されています。って要するに雑談しているわけですね。
     いぬ。BBS の会員は、運営母体であるあらかわ犬がもともと TSG メンバー 中心だったこともあって、ほとんどを TSG 関係者で占めています。今ちょっ と人数を数えてみたら、8 割以上が TSG 関係者でした。うーん。

    [いぬ。BBS でみんななにやってるの ?]
     いぬ。BBS の主な活動は、ずばり雑談です。
     世の中には「アニメについての会話専門 BBS」とか「猫についての会話専 門 BBS」(なんだそりゃ) などというものも存在していますが、いぬ。BBS は決して「犬についての会話専門 BBS」ではありません (笑)
     もっとも、ジャンルを問わない雑談とは言え、なにせメンバーの 8 割は TSG 関係者です。話題も自然と TSG 好みな系統になることが多いようです。

     もちろん、年がら年中雑談ばかりしているわけではありません。プログラ ムの開発・テストなんかをやってる人も結構多いですね。これには、元々い ぬ。BBS が「ゲームの開発をしよう」という目論見で開局したという経緯も ありますし、TSG に強力なプログラマが多い、というのもあります。かくい う私もいぬ。BBS で、自作フリーソフトウェア "lfd." のαテストをやって たりします。
     プログラムだけじゃなくて、例えば絵を描いてる人も結構いたりしますね ー。音楽データを作ってる人は今のところ一人しかいないのですが。
     ちなみに、去年 (1994 年) 1 年間の統計データを紹介しますと、ファイ ルライブラリに登録された「自作プログラム・自作データ」の数は、約 200 本でした。

     いぬ。BBS には、TSG の中心メンバーのうちのかなりの数がアクセスして います。そんなわけで、TSG に関する連絡がいぬ。BBS で行われることは多 いですね。コンパや合宿の告知なんかは必ず電子掲示板に書き込まれるよう です。
     掲示版での告知だけじゃなくて、例えば TSG メンバーの間の連絡には、 電子メールがよく使われているみたいです。学校に行かなくても自宅から連 絡が取れるって、予想以上に便利がいいんですよね。

    [いぬ。BBS にアクセスするには ?]
     いぬ。BBS にアクセスするには、当然パソコン通信をするための機材 が必要です。パソコンとモデムと電話線ですね。そりゃそーだ。最近はモデ ムも安くなったし、敷居はとっても低くなりましたよねー。
     準備ができたら、おもむろにいぬ。BBS に電話をかけます。回線がつなが ると ID の入力画面になりますから、そこで new [リターン] と入力するだ けで、自分の ID を発行してもらえます。簡単ですね☆
     いぬ。BBS は、「積極的に宣伝はしてないけど一応公開 BBS 」です。TSG の人じゃないと入れない、とかいうことは全然ありません。ゲストアクセス でも制限はほとんど無いですから、一度ためしにアクセスしてみてください。
     とゆーわけで、みなさまのお越しをお待ちしております。

    いぬ。BBS
    300/1200/2400/9600/14400/28800(V.34/V.FC) (bps)
    (1 回線、24 時間運用)
    mmm Rev. 4.1
    オンラインサインアップ可、会費無料

    ったく☆
    Tak@いぬ。BBS (SysOp)
    taka@is.s.u-tokyo.ac.jp


    根津研について

    (Tellur)

    1. 根津研とは
       駒場では TSG の活動拠点として学生会館の 305 号室の一部を使用してい るのですが、本郷には学生会館のような建物がキャンパス内にありません (少々不正確な表現ですが)。そこで根津駅から徒歩 3 分の所にアパートの 一室を借りたのが、「TSG 根津中央研究所」すなわち根津研なのです。

    2. 根津研の中には
       もちろんマシンもあります。PC-286 に AT 互換機、初代 X68000、なぜか 88 もあります。よく探せばメガドライブやあのツインファミコンを見つけ ることが出来るでしょう。
       しかし、はじめて入った人が圧倒されるのが少女漫画をはじめとする様々 な漫画の山。何百冊あるか数えたこともないのですが、読みふけるとあっと 言う間に時間は過ぎ去ってくれることでしょう。
       風呂はないけど、冷暖房は一応完備。学生会館のような閉館時刻は存在し ないので、宿泊も可能となっております。夜遅くまで研究をしていて終電を 逃した部員にとっては格好の休憩所なのです。

    3. 根津研に入るには
       駒場の TSG は会費がタダになっていますが、根津研はアパートの家賃や 光熱費を払うために年会費を取っています。会費を払った人には漏れなく根 津研のスペアキーが貸与されます。会費は一応年四万円となっていますが、 金はないけど是非根津研を利用したい方や駒場生には年一万円の会費を払っ て戴ければ鍵を渡します。
       鍵は持っていないけれど一時的に根津研を利用したい方は、予め電話で部 員の在室を確認してから行くのがよろしいでしょう。
       そんなわけで、皆さんも本郷に寄った際には是非お立ち寄りください。お そらく日が暮れてからの方が人がいっぱいいることでしょう。


    Windows は見栄えが一番 !! V2 [CTL3DV2 の使用方法]

    (Nishi)

    はじめに
     最近、巷では Windows が普及してきたようです。そこで、Windows の プログラムを組む人も増えてきたようです。でも、Windows の標準コントロー ルを使うとあまり見栄えがしません。そこで、CTL3D をプログラムに組み込 んで、見栄えを良くしましょう。

    部報 186 号に書いた甲斐あってか、最近の Windows 対応のフリーソフトウェアには CTL3D.DLL / CTL3DV2.DLL を使ったものが 増えてきました。(おいおい (^^;)

     冗談はさておき、私は部報 186 号の記事「Windows は見栄えが一番 !! [CTL3D の使用方法]」で CTL3D.DLL の使い方を解説し たわけですが、その時点では 1 つ不明なことがありました。それは CICA 等で配布されていた "CTL3D.DLL" と Excel 等に付属する DLL "CTL3DV2.DLL"との違いは何かということでした。CICA 等で配布されている ものは古いバージョンのもので、それよりも新しい CTL3DV2.DLL 等に関す る記述は当然何もなかったのです。また、バージョンアップされた情報が MSDN の CD-ROM にあることは判っていましたが、それを入手する手段はあ りませんでした。
     そこで、私は 1 つの仮説を立てました。「"CTL3DV2.DLL" は "CTL3D.DLL" の機能に加えて Excel、Word 等マイクロソフト製品で使われ ている『タブ・コントロール』 を含んでいるのではないか」と。しかし、 その予想は完全に裏切られました。SDK 付属の「スパイ」ユーティリティで 調べてみたところ「タブ・コントロール」は Excel、Word 独自のクラスに よって実現されていたのです。私は大きく落胆し、「では違いは一体何なの だ」と悩むあまり眠れない日々が続きました (大ウソ)。

     ところが、ある日 Borland C++ 4.0 の CD-ROM のディレクトリを眺めて いたとき、"CTL3D.HLP" というファイルが私の目にとまりました。「どうせ CICA の CD-ROM に含まれているのと同じ古いドキュメントだろう。」とは 思いながらもそのファイルのオープンすると、開けてびっくり玉手箱、その ドキュメントは最近の物だったのです。そして、その中には CTL3D.DLL だ けでなく CTL3DV2.DLL、CTL3D32.DLL に関する記述、また、DLL を使わずに スタティックライブラリを用いて同様の表示を行う方法等それまで私が持っ ていたドキュメントよりもずっと詳細な情報・説明がありました (やっぱり 英語だったけど)。ついに私の悩みは解決したのです !

     本記事では、"CTL3DV2.DLL" の使い方、それから "CTL3D.DLL" と "CTL3DV2.DLL" の違いについて簡単に解説したいと思います。

    必要なファイル
     まず CTL3DV2.DLL がなくてはなりません。ところが最近のアプリケー ションでも、まだ "CTL3D.DLL" しか同梱していないことが多いのです。と はいえ、ここ最近 "CTL3DV2.DLL" がついてくるものが増えてきてはいます。 この手の DLL はいつの間にかアプリのインストーラによってインストール ていることが多いものです。まず WINDOWS\SYSTEM ディレクトリを調べてみ ましょう。

     更に開発には CTL3D.H と CTL3DV2.LIB が必要です。CTL3D.H は CICA 等 の旧バージョンの物でもとりあえず大丈夫です。
     ちなみに最近の言語製品 (Visual C++ 2.0 に含まれている Visual C++ 1.5、Borland C++ 4.0) 等には CTL3DV2.DLL が再配布可能ファイルとして、 また CTL3D.H と CTL3DV2.LIB が含まれています。
     CTL3D.H が入手できないときは、あとに述べる API のプロトタイプ宣言 を参考にヘッダーファイルを作ればよく、CTL3DV2.LIB が入手できないとき はインポートライブラリアン (Visual C++ 等マイクロソフトの処理系、 Borland の処理系ともに IMPLIB.EXE) を用いれば CTL3DV2.DLL から CTL3DV2.LIB と同等のものを生成することができます。
     ちなみに旧バージョンのファイルとドキュメント (英語) 等がまとめてあ るアーカイブ 3dctrl.zip は CICA の CD-ROM や直接 CICA から入手できま す。VisualBasic 関連のところを探してみてください。

    最も簡単な使い方
     最も簡単な使い方を説明します。Windows のバージョンは 3.1、 MSC/C++ バージョン 7.0 以上、Borland C++ 3.1 以降を前提としています。
     手順は CTL3D.DLL のときとほとんど同じで、リンクするライブラリが違 うだけです。

    (注意) 旧バージョンのヘルプファイルでは以下に述べる Ctl3dUnregister 関数は 'Ctl3dUnRegister' と記述されていた (CTL3D.H 内のプロトタイプ 宣言およびサンプルプログラムでは 'Ctl3dUnregister' となっていたが)。 このヘルプファイルのバグは最近のバージョンでは修正されている。

    1. WinMain のあるソースファイルで、CTL3D.H をインクルードする。ただ しこのとき CTL3D.H より前に WINDOWS.H がインクルードされていなければ なりません。
    2. プログラムの初期化時に Ctl3dRegister 関数を呼び出す。
    3. 続いて、Ctl3dAutoSubclass 関数を呼び出す。(この後に Create され る標準コントロールはすべてサブクラス化され、ダイアログボックスも含め、 標準コントロールの表示が立体感のあるものに替わります。)
    4. WM_SYSCOLORCHANGE メッセージを処理するとき Ctl3dColorChange 関数 を呼び出す。(ウィンドウプロシージャ内に WM_SYSCOLORCHANGE メッセージ 処理部分を付け加えてください。)
    5. 終了処理時に Ctl3dUnregister 関数を呼び出す。
    6. CTL3DV2.LIB をリンクする。

     ちなみに、2, 3 の Ctl3dRegister, Ctl3dAutoSubclass 関数は WinMain 関数の初めの方、5 の Ctl3dUnregister 関数は WM_DESTOROY メッセージ処 理中か、WinMain 関数の最後に呼ぶようにすればよいでしょう。以下に例を 示します。

    #include <windows.h>
    #include <ctl3d.h>
    
    int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                       LPSTR lpszCmdParam, int nCmdShow)
    {
      HWND     hwnd;
      MSG      msg;
      WNDCLASS wndclass;
      
      Ctl3dRegister(hInstance);
      Ctl3dAutoSubclass(hInstance);
                .
                .
                .
                .
                .
      while (GetMessage(&msg, NULL, 0, 0))
        {
          TranslateMessage(&msg);
          DispatchMessage(&msg);
        }
      
      Ctl3dUnregister(hInstance);
      return msg.wParam;
    }
    
    long PASCAL _export WndProc(HWND hwnd, UINT message,
                                WPARAM wParam, LPARAM lParam)
    {
      switch (message)
        {
                .
                .
                .
           case WM_SYSCOLORCHANGE:
    	 Ctl3dColorChange();
             break;
                .
                .
                .
    
    CTL3D の API の概要 (一部の API のみ)
    CTL3D の API には以下のようなものがあります。
    CTL3DV2.DLL の存在意義
     かつて、CTL3D.DLL にはいくつかの問題点がありました。例え ば、CTL3D.DLL の旧いバージョンではコモンダイアログのうち、File Open と Page Setup が Ctl3dAutoSubclass を使った方法では 3D 表 示されないことがありました。
     この問題について、はじめ Microsoft はアプリ側で対処すること を求めていました。ところが、CTL3D.DLL が (予想に反して ?) 普及 してしまったためか、この問題は、最近のバージョンでは修正されて おり、アプリ側で対処する必要はなくなりました。
     また API も少し拡張されました。かくして、CTL3D はバージョン アップされ、一般的な DLL となったのです。実は、CTL3DV2.DLL の 存在意義はここにあります。
     Microsoft は DLL 使用時にバージョンチェックをしないで済むよ うに、新バージョンの名前を変えたコピーを用意したのです。つまり CTL3DV2.DLL を呼び出せば、それが新バージョンであること (バージョ ン 2.0 以上であること) が保証されるのです。CTL3D.DLL と CTL3DV2.DLL の内部の相違はほとんど無いのです。相違といえば、 CTL3DV2.LIB に CTL3DV2.DL L非存在時にその旨を通知するメッセー ジボックスを表示するルーチンが追加されたくらいなのです。

    CTL3D.DLL と CTL3DV2.DLL のどちらを使うべきか
    部報 186 号に書いたような DLL 非存 在時にもプログラムを動作させる方法 (Windows API の LoadLibrary 関数と GetProcAddress 関数を使って DLL を呼び出す方法) を使う ならば、CTL3DV2.DLL がなければ CTL3D.DLL を使うということが簡 単にできるでしょう。そうでなくこの記事で述べたようなインポート ライブラリを使う方法では、CTL3D.DLL を使おうとせずに CTL3DV2.DLL を使うようにしてしまうのが最も簡単で、安全です。 CTL3D.DLL を使うと、先述したような旧バージョンの問題 (特にコモ ンダイアログが 3D 表示されないという問題) が生じる可能性があり、 これを回避するために、アプリ側のコードを先述した以上に追加・変 更しなくてはならない場合があります。(詳しくは旧バージョンのド キュメント参照) また、CTL3DV2.DLL は再配布可能ですからアーカイ ブに入れてしまっても問題ありません。

    ついでに
     CTL3D には Win32 版があります。CTL3D32.DLL、CTL3D32.LIB がそうです (ヘッダーファイルは Win16 版と共通です)。これは Win16 版の CTL3DV2.DLL、CTL3DV2.LIB に相当します。ただし注意す る点が1つあります。Microsoft の 3 2ビット処理系のオブジェクト ファイルのフォーマットはインテルオブジェクトフォーマットではなく COFF(Common Object File Format) なのです。そのため Borland の 処理系のように COFF を吐かない環境では Microsoft のインポート ライブラリファイルが使えない (より具体的には TLINK32.EXE では Microsoft のライブラリが使えない) のです。そのため Borland C++ 4.0 には Borland 専用の CTL3D32.LIB がついてきます。しかし、使 い方に何ら変わるところはありません。

    さいごに
     かつて謎の DLL だった "CTL3DV2.DLL" は実はたいした物でな いことが判りました。しかし、それを知る手段は少なすぎました。 MSDN の CD-ROM には CTL3D の新しいバージョンに関するドキュメン トがあるらしいのですが、少なくとも日本においてはそれ以外に MSKK はドキュメントを直接ユーザーに公開していないのです。ボー ランドの C++ 処理系に含まれる CTL3D 新バージョンに関するドキュ メントは MSKK の処理系には含まれていないのです (最新の Windows 開発環境であるはずの Visual C++2.0 にも)。
     このドキュメントが英語だからという理由で添付しなかったのでしょ うか ? (Visual C++ 2.0 には日本語訳されていないドキュメントが 他にいっぱいあるのに。) それとも将来 Windows95 では必要がなく なるから現時点においても不要だと判断したのでしょうか?ヘッダー ファイル CTL3D.H とインポートライブラリ CTL3DV2.LIB があるのに、 それに関するドキュメントが無いなんて、ちょっとおかしな状況です よね。
     言語製品にはこういったライブラリのドキュメントを完全な形で添 付してもらいたいのです。あと、サポート、バージョンアップなしで いいですから、MSDN の Development Library CD を安価で提供して いただきたいものです。>MSKK

    参考文献
    Windows SDK マニュアル
    CICA から入手した 3dctrl.zip についてくるドキュメント
    Borland C++ 4.0 (CD-ROM 版) 付属のヘルプファイル、CTL3D.HLP

    ほんとうにはじめての人のための C 言語プログラミング入門

    (渡辺 尚貴 / NAO)

    序.ようこそ TSG & C Language
     本日は TSG のオリにようこそおいでくださいました。ここに 来られる方はコンピュータに興味をお持ちでしょうので C 言語とい う言葉をよく聞かれることと思います。この C 言語はプログラムを 記述するために世界中で最も普及しているプログラム言語です。C 言 語の入門書は数多く市販されていますが、「入門書」と称していても、 実はある程度の知識を持った人を相手に書かれている本が多いようで す。私もそのために C 言語の習得には苦労し、一時はあきらめよう かと思う程でした。皆様の中にもかつて C 言語の習得を試みたもの の、あきらめてしまった人がいるのではないでしょうか。
     そこで今回のこのオリパンフにおいて私がまったくのプログラム初 心者を対象にした C 言語入門を書き下ろすことになりました。みな さん是非これを読んでC言語プログラマーを目指しましょう。
     今回の私の話しにはなるべく前提知識を要しないよう配慮しました が、最低限としてエディッタの使い方と算数の理解をご用意下さい。 それが用意できれば、C 言語プログラミングというのは別段難しいも のではないのです。こわがらすに行きましょう。

    その 1. プログラミング言語とは何なのだろう
     まずはプログラミング言語とは何かの説明をします。よく頭の 硬い人のことを「コンピュータのようだ」と言いますが、なぜコンピュー タが硬いのでしょう。それはコンピュータが 0 と 1 の言葉しか理解 ないからです。コンピュータに仕事をしてもらいたいときはこの 0 と 1 だけの言葉でお願いしなくてはなりません。この言葉をマシン 語と呼ぶのですが、他の言葉ではコンピュータはお願いを理解してく れないのです。なので頭が硬いというわけなのです。
     でも当然わたしたち人間にはこの 0 と 1 だけの言葉は扱い切れま せん。そこで現れたのが人間とコンピュータの間に入る通訳者です。 彼は人間にも扱い易い言葉をコンピュータが理解できる 0 と 1 だけ の言葉に通訳してくれます。この人間にも扱い易い言葉というのがプ ログラミング言語なのです。プログラムとはコンピュータにさせる仕 事の順番を書いたようなものなのです。そしてその書の文法がプログ ラミング言語で、それは人間にも理解しやすい言語なのです。
     人間の言葉には日本語、英語、ドイツ語、フランス語、中国語、ロ シア語、スペイン語などがあるように、プログラム言語にも Basic, C, C++, Pascal, Fortran, Cobol, Prolog, Lisp, アセンブラなどい ろいろなものがあります。それぞれの言語によって文法が大きく異なっ ているので BASIC は話せても C は話せないといった状況がよくおこ ります。
     先程、通訳者がこの人間が使えるプログラム言語をマシン語に通訳 するといいましたが通訳者のほかにも翻訳者というものがいます。通 訳者というのは 1 文 1 文をその場で変換して相手に伝えますが、翻 訳者というのは全文すべてを変換し終えてから相手に伝えます。プロ グラミング言語をマシン語に変換する際に通訳者を使うものと翻訳者 を使うものの 2 種類の言語があります。前者をインタープリター (interpreter) 型言語、後者をコンパイラー (compiler) 型言語と呼 びます。
     コンパイラー型言語の方は、コンピュータに対する命令が実行前に 全部まとめて翻訳されているので、インタープリター型言語のように 次の命令が通訳されるのを待つ必要がないのでさっさと命令が実行さ れます。でも全部を翻訳するのに時間がかかるので実行するまでに時 間がかかることになります。この翻訳という作業のことをコンパイル と呼びます。今回の私のお話しはそのコンパイラー型言語の代表格で ある C 言語についてです。
     ところでこの通訳者とか翻訳者とかの実体は一体何なのでしょうか ? それは業者により市販されたソフトウェアです。これは買わなくて はなりません。同じ日本語でも大阪弁、京都弁、名古屋弁、九州弁、 東北弁などの方言があるように C 言語にも Borland, Microsoft, Turbo Wuick, Visual, Lsi などの方言があります。各方言は基本的 には C 言語ですが互いに少しづつ文法が異なっています。
     今回の私のお話しは基本的な部分のみなのでどの方言でも共通なの で、安心下さい。でもさらに奥深く学びたいという方はお持ちの方言 の C 言語の名前の書いてあるC言語の本を買うことを勧めます。

    その 2. とにかくプログラミングを始めよう
     さていよいよ C 言語プログラミングを始めてみましょう。ま ず最初に画面に「 TSG is a nice computer circle !! 」という一文 を表示させるプログラムを作ってみましょう。「キーボードにその文 章を打ち込めばもう画面にでているではないか」と、思いになる方が おられるかもしれませんが、それはプログラムではありません。エディ ターという市販のプログラムがあなたの押したキーに対応する文字を 画面に表示させるようになっているだけです。コンピュータに命令し ているのではなくエディターに命令しているだけです。それは全くプ ログラムではありません。これからコンピュータにこの文章を表示さ せる命令を C 言語で書くのです。それには次のような文章のファイ ルを書いてください。
    #include <stdio.h>
    
    void main(void)
    {
        printf(" TSG is a nice computer circle !! \n");
    }
    
    これがプログラムです。なんだかわけの分からない記号がたくさんあ るでしょう。そのようなことは気にせずにこれを拡張子が .c のファ イルにしてコンパイルしてください。コンパイルの仕方は各方言によっ て異なるのであなたと同じ方言の C 言語を使われている方に尋ねて ください。
     コンパイルがうまく終了するとその旨が表示されます。もしうまく いかないで画面に error とかがででいたら何かあなたが上の文章の 一部を書き間違えたことが考えられます。少しでも異なるとすぐ error とでますので十分に気をつけてください。それでも error が でる場合はプログラム以外に原因があります。だれかよくしっている 人を呼んでください。
     さてうまくコンパイルできたなら実行してみましょう。 果たして 例の文章が画面に表示されることでしょう。うまくいったところで C 言語の文法について解説していきます。

     まずこのプログラムの下から 2 行目にある printf という文字は プリントエフまたはプリントフォーマットと呼ばれていまして、要す るに文字を画面に出す命令です。画面に出したい文字を (" ") で包 んで printf のすぐ後ろに書きます。この例では文字列の最後に \n という謎の文字があります。これには特殊な意味がありまして、この 文字を画面に出力するとその文字が表れるのではなく改行が行われる のです。その結果次に表示する文字は前に表示した文のすぐ下の行に 表示されます。試しにこの \n を printf(" ") の中に入れないで実 行してみてください。\n の効果が良くわかることでしょう。 printf(" ")のすぐ後ろに ; (セミコロン) がありますが、これは命 令と命令との間の区切りのマークなのです。このセミコロンがないと コンパイラは命令がどこで終わるのか判らないのでエラーを出してし まいます。セミコロンは命令の終わりには必ず付けるようにしましょ う。
     さて printf 文以外の #include や void main(void) や { } は 今のところはまだわからなくても全然かまいません。呪文のように覚 えておいてください。

     次にもう少しコンピュータらしく簡単な計算をするプログラムを作っ てみます。

    #include <stdio.h>
    void main(void)
    {
        int a, b, c ;
        
        a=1;
        b=2;
        c=a+b;
        
        printf(" %d + %d = %d \n", a, b, c );
    }
    
     これを実行すると 1 + 2 = 3 と画面にでます。プログラムの解説 をしましょう。int a,b,c; という文は変数宣言と呼ばれる文です。 この文によってこれ以後は a b c の三文字は整数の値を記憶してお く変数となります。次の a=1; で a に 1 が代入され b=2; で b に 2が代入されます。そして c=a+b; で 1+2 が計算されてその結果の 3 が c に代入されます。
     最後の printf 文の様子が先のものとだいぶ違うでしょう。実は printf 文というのは " " で包まれた部分のみを画面に表示するので すが %d という変な文字はそのまま画面にでるのではなく " " の後 ろにある変数の値がその %d のある場所に置換されて画面に出力され るのです。%d が複数ある場合は " " の後ろの変数がその並んだ順に 置換されて表示されます。
     よってこの例では 1 + 2 = 3 と画面にでるわけです。このように printf 文は変数の値の表示の仕方を書式で制御することができます。 それゆえプリントフォーマットなのです。

     ここで代入について少し補足しておきます。= マークは代入演算子 と呼ばれていて、= マークの右がわの式を計算した結果を左の変数に 代入する演算子です。したがって a+b=c; のようなことはできません。 また変数にはもともとなにかしらの値が入っているのですが、代入を することによってその値は消滅して新しい値が記憶されます。極端な 例を上げると

        a=1;
        a=2;
        a=a+1;
    
    2 行目までは a は 1 なのですがここで 2 に強制的に変更されま す。そして 3 行目で代入演算子の定義にしたがって a は 3 になり ます。= マークは数学と同じではなく等しいという意味は全然ありま せん。

    その 3. ループ文で繰り返し命令を実行しよう
     さてこのぐらいのプログラムでは、なにもわざわざプログラミ ングするより電卓でしたほうがずっと簡単ですね。でも次のプログラ ムは電卓ではそんなに簡単にはできないでしょう。
     それは 1 から 10 までの整数の総和を求めるプログラムです。 sum=1+2+3+4+...+10 と書けば確かに和は求まります。しかしそれは 書くのが大変です。もし 1000 迄の和だったら絶対に書き切れません。 そこで必要になるのがループと呼ばれる機構です。ループとは輪のこ とで、この輪に沿ってくるくると同じ命令を何度も実行します。具体 的に見てみましょう。
    #include <stdio.h>
    
    void main(void)
    {
        int n;
        int sum=0;
        
        for (n=1; n<=10; n++ ){
            sum+=n;
        }
        
        printf("The sum is %d \n", sum);
    }
    
    for 文の説明に入る前に int sum=0; について解説します。これは変 数の宣言と代入を一緒にしてしまう方法です。このような宣言と同時 の代入を初期化と呼びます。
     ループ文 for を説明しましょう。この for の後ろの ( ) になにや らいろいろ書かれていますが、これが for 文のループの仕方を決め る重要な設定なのです。その書式は
        for (初期化文; ループ条件文; ループ変更文){
             ループ実行文(複数可);
        }
    
    となっています。
     これを例に沿って解説します。まずプログラムの実行がこの for 文に来ると、まず初期化文が実行されます。つまり n=1 です。次に ループ条件文が評価されます。つまり n<=10 です。もしこれが真な らループ実行文が実行されます。偽ならこの for 文を終了して次の printf 文に移ります。ループ実行文がすべて実行されるとループ変 更文が実行されます。ここでは n++ です。この n++ というのは整数 n の値をひとつだけ増やす命令です。ループ変更文が実行された後、 再びループ条件文が評価されます。こうしてループ条件文が偽になる まで { } で囲まれた文が何度も実行されます。sum+=n; というのは sum の値を n だけ増やすという命令です。
     いかがでしょう。こんな説明でこれで 1 から 10 までの和が計算 されることが理解できてもらえたでしょうか。ここは大事な所ですか らもう一度説明しておきますね。
     実行が for 文に入る前には sum の値は 0 になっています。for 文に入るとまず n=1 がなされます。次に n<=10 がチェックされます。 n は 1 なのですからこれは真です。よって { } の中の sum+=n が実 行されます。sum の値が 0 で n の値が 1 なのでこの命令によって sum の値は1増えて1になります。次に n++ で n が 2 になります。 また n<=10 がチェックされます。これも真なので sum+=n が実行さ れます。今度は sum は 2 増えることになります。これで 1+2 がな されたわけです。これを繰り返して n が 11 になったときループが 終了します。こうして 1 から 10 までの総和が求まったわけです。
     みなさんもじっくりこれを追っていってみてください。

     ループ文には for 文のほかにも while 文というのがあります。次 のプログラムを眺めてください。

    #include <stdio.h>
    
    void main(void)
    {
        double x, y;
        double a, b;
        
        printf("Input a real number ");
        scanf("%lf", &x);
        
        a=x/2.0;
        b=x;
        
        while (a-b<-0.0001 && +0.0001<a-b){
            b=a;
            a=(x/a+a)/2.0;
        }
    
        y=a;
    
        printf("\n The root of %lf is %lf \n", x, y);
    }
    
     これは平方根を求めるプログラムです。平方根ですからその値は小 数ですので整数の変数を宣言する int は使いません。替わりに double x, y; とします。こうすると x, y は小数の値を記憶する変 数として使えるようになります。scanf (スキャンエフまたはスキャ ンフォーマットと呼びます) という文はプログラム実行中にキーボー ドから値を変数に代入する命令です。この文の書き方についてにはあ まりこだわらないでください。double で宣言した変数に値を入れる ときはこうしてください。またその値を printf で表示するにはその 位置を %lf で示します。int で宣言した変数には scanf("%d",&n); のようにします。この & の印についはずっと後で説明します。
     さて本題の while 文についてですがこれは簡単で書式は
        while( ループ条件文 ){
            ループ実行文(複数可);
        }
    
    となっています。while の ( ) の中の条件文が真ならループを繰り 返すわけです。例の場合真ん中に && マークがありますがこれの意味 は「且つ」つまり「アンド」の意味です。要するにここでは a-b の 値が ±0.0001 以外ならループをするとういうわけです。なお条件の 真偽を 1 と 0 で表すこともありまして while(1) なら永久ループと なります。
     どうしてこれで平方根が求まるのかという数学的な話は今回抜きに しますが、とにかく while 文の使い方を理解してください。

     ループではないのですけれど非常に大事な文を忘れていました。そ れは条件分岐文 if 文です。ある条件が満たされているときのみに命 令を行うという例外処理がよくあります。if 文は簡単です。書式を 示します。   

        if (条件文){
            条件文が真のときに実行する命令群;
        }else{
            条件文が偽のときに実行する命令群;
        }
    
    else より後の部分はもし偽の命令群が無いのなら省くことができま す。やはり、ひとつ例をお見せしたほうがよろしいでしょう。
        if (a<0){
            a=-a;
        }
    
    これで絶対値が計算されるわけです。  ループの中で突然ループから出たくなる場合があります。そういう ときには break 命令を使います。またループで次のステップに強制 的に飛びたいときは continue 命令を使います。例を見てください。
        while (1){
            ループ実行文
            if (ループ脱出条件文) break;
        }
    
        for(a=1; a<10; a++){
            if (ループネクスト条件文)continue;
            ループ実行文
        }
    
    実際に作ってみてその効果を確認しておいてください。

     さてとちょっと横道に行きますが、この場を借りていろいろな演算 子を紹介しましょう。

      算術演算子
        + 加算
        - 減算
        * 乗算
        / 除算
        % 余り
        ( ) 優先計算
      関係演算子
        == 等しい
        != 等しくない
        >= 以上
        <= 以下
        > 越える
        < 未満
      論理演算子
        ! 否定
        && 且つ
        || または
      インクリメント・デクリメント演算子
        ++ ひとつ増やす
        -- ひとつ減らす
      ビット演算子
        ~  ビット反転
        << 左シフト
        >> 右シフト
        &  and
        ^  exor(xor)
        |  or
      代入演算子
        単項代入演算子
          =
        複合代入演算子
          +=
          -=
          *=
          /=
          %=
          <<=
          >>=
          &=
          |=
          ^=
        (a+=b は a=a+(b) と等価です。他の演算子も同じです)

    その 4. 配列変数によってデータを効率良く扱おう
     2 人の人のテストの平均点を計算するプログラムを作ってみま しょう。たいてい次のようになりますよね。
    #include <stdio.h>
    
    void main(void)
    {
        int score1, score2;
        double mean;
        
        printf("Input the score of No1 = ");
        scanf("%d", &score1);
        printf("Input the score of No2 = ");
        scanf("%d", &score2);
        
        mean=(score1+score2 )/2.0;
        
        printf("The mean score is %lf \n", mean);
    }
    
     解説は何も必要ではありませんね。あえて言うなら mean の値を計 算するときに 2 で割るのではなく 2.0 で割るということです。もし ここで 2 で割ると期待している値にはなりません。一般に整数を整 数で割るとその結果はたとえ割り切れなくても整数になります。つま り商を求めてしまうのです。これはこれで使えることなので覚えてお いてください。ついでに整数の割り算の余りを求める方法も述べてお きましょう。それには % を使うだけです。つまり c=a%b; とのよう にするだけです。
     さて本題に戻って今度は 10 人分の平均値を計算するプログラムを 作ってみましょう。作り方はみなさんもうおわかりですね。でも作る のはかなり面倒です。何しろ 10 個の変数をそれぞれ宣言して入力し て総和を計算して 10.0 で割るのですから記述するのが激しくたいへ んでしょうす。これでは 100 人ぶんの平均値の計算はとても無理で すね。100 個の変数をうまくまとめて簡潔に扱う方法が必要となりま す。
     そうした需要から配列変数というものが登場します。配列変数とい うのは変数をたくさん束ねてひとつの団体名で代表して、その個々の アクセスは団体名に数字の添え字をつけることによって実現するもの です。ごちゃごちゃ話すより具体例を見せましょう。まず配列変数の 宣言は
        int score[10];
    
    のようにします。これで int 型の変数が 10 個作られ、それをまと めた団体名が score になります。そしてその変数に値を代入したり、 また値をそこから取り出したりするには例えば
        score[2]=90;
        a=score[3];
    
    の様に [ ] の中に数字を入れて配列のどの要素かを指定することに よって実現されます。この数字のことを添え字と呼ぶのですがその数 字の範囲は 0 からその配列変数を宣言した時の [ ] の中の数字 -1 の数字までです。つまり int score[10] としたら score[0] から score[9] までの変数ができるわけです。くれぐれも score[10] とか score[-1] としないで下さい。プログラムが暴走することもあります から。
     さて、ではこの配列変数を用いて先の 10 人ぶんのテストの平均点 を計算するプログラムを作ってみます。プログラムの右に /* */ の 間に書かれている日本語はプログラムの注釈に過ぎません。このプロ グラムを皆さんが自分のコンピュータに打ち込むときにはその部分を 省いても構いません。
    #include <stdio.h>
    
    void main(void)
    {
        int score[10];                        /*配列の宣言*/
        int n, sum;
        double mean;
        
        for (n=0; n<10 ; n++ ){               /*配列の各要素に値を入力*/
            printf("Input the score of No.%d", n);
            scanf("%d", &score[n]);
        }
        sum=0;
        for (n=0; n<10; n++){                 /*合計点の計算*/
            sum+=score[n];
        }
        mean=sum/10.0;                        /*平均点の計算*/
    
        printf("The mean score is %lf \n", mean);
    }
    
     どうですか。 for 文など組み合わせることにより非常にすっきり と大量のデータを扱うことができるようになることがわかりますね。 これなら 100 人ぶんのデータの処理も、このプログラムの 10 と書 いてあるところを 100 にするだけでできます。
     ここでひとつ便利な機能を紹介しましょう。いま 10 を 100 に変 える、と言いましたが今回のプログラムでは 10 は 4 カ所にしかあ りませんが、これがもっとたくさんになったら修正するのは結構たい へんです。そこで 10 となるべきところはあらかじめすべて NUM の ような文字で書いておきまして、そしてプログラムの先頭の方の #include 文のすぐ下辺りに次のように書きます。
        #define NUM 10
    
    これでコンパイルの時には NUM のところはすべて自動的に 10 にな ります。100 人分にしたいのなら、ここの文の 10 を 100 にするだ けです。非常にエレガントですね。この #define のようなプログラ ム実行時の命令ではなく、コンパイル時のコンパイラに対する命令の ことをコンパイラ疑似命令と呼びます。
     あとひとつ注意があります。平均点を求めるところは
        mean=(double)sum/NUM;
    
    のように sum の前に (double) と書いてください。これによって sum の値は double 型に変換されます。だから整数である NUM で割 られても小数の値が計算されるわけです。このような (double) をキャ スト演算子と呼びます。キャスト演算子には他にもいろいろあります が今紹介できるのは double 型の変数の値を int 型に変換する (int) ぐらいでしょう。
        printf("%d", (int)mean);
    
    とすれば mean の値の整数部分だけが見られますよ。
     配列変数の初期化についても触れておきましょう。このようにしま す。
        int a[10]={10, 50, 30, 60, 90, 40, 20, 40, 20, 50};
    
    こうするとコンパイルのときに自動的に各要素に値が初期化されます。 注意しておきますが、このように ={ , , , , } で初期化できるのは 配列変数の宣言のときだけです。宣言後にはそのようにして代入はで きません。

    その 5. 構造化プログラミングをしよう
    printf 文は文字列を画面に出力する命令ですが、実はこの printf 文を実行する際にコンピュータはとてもたくさんの仕事をし ています。文字列を画面に表示する仕事というのは思っているよりも ずっと複雑な仕事なのです。たくさんの基本的な命令を組み合わせる ことによって printf 命令は作られているのです。けれども私達はそ のようなことは意識せずにただ単に printf(" ") と唱えるだけで文 字列を画面に出すことができます。
     このようにいくつかの命令をまとめてあたかもひとつの命令の様に してしまうことを関数化と呼びます。そして、そのまとまったひとつ の命令を関数と呼びます。さらに命令をどんどん関数化していったプ ログラムのことを構造化プログラムと呼びます。関数という言葉は数 学でよくでてきますが、その意味は或る値を受けてそれに応じてひと つの値を算出するという操作でした。しかしプログラムにおいての関 数の意味はそれとは少し異なっていて、別に或る値を受ける必要もな くまた算出するひつようも無いのです。とにかく関数を呼び出せば何 かが起こるというぐらいの意味なのです。
     この printf 関数は既に業者によって作られているので私達はこれ を使うことができます。どこにそれが作られているかというとライブ ラリというファイルの中にあります。しかしこのファイルの printf 関数を構成する命令群はマシン語で書かれているので一部のマシンな 人間を除いて見てもさっぱりわかりません。
     自分で関数を新たに作ることもできます。まえのプログラムの 1 から 10 までの和を求める部分を関数にしてみましょう。
     その関数の名前は calc_series とします。逐次に解説していきま す。
    #include <stdio.h>
    
    void calc_series(void)       /* 関数 calc_series の中身の定義の開始 */
    {                            /* 関数名の前や () の中に書いてある    */
        int n;                   /* void は気にしないでください。       */
        int sum=0 ;
        
        for(n=1; n<=10; n++){
            sum+=n;
        }
        printf("The sum is %d \n", sum);
    }                            /* 関数 calc_series の中身の定義の終了 */
    
    void main(void)
    {
        calc_series();           /* 関数 calc_series の呼び出し(実行)*/
    }
    
     この {} (中括弧) で囲まれた部分が関数の中身です。
     ここでそろそろ今まで隠してきました void main(void){} につい てその正体を明かしましょう。このプログラムをみればうすうす感じ るように main というのは calc_series と同じような関数の格好を していますね。中身は全然違いますけど。この main というのはメイ ン関数と呼ばれていまして他の関数とはちょっと異なるある特別な関 数なのです。その特別さというのは、main 関数はプログラムの実行 と共に自動的に一番最初に行われる関数なのです。つまり OS が main 関数を自動的に実行するのです。例のプログラムでは calc_series 関数のほうが main 関数より先にその内容の定義がなさ れていますが、先に実行されるのは main のほうです。 main 関数の 中で calc_series 関数が呼び出されています。関数の中にまた関数 があるわけですね。ここで始めて calc_series 関数が実行されるの です。その中身の命令をすべてし終えた後、プログラムの実行は再び main 関数に戻ってそして main 関数内のすべての命令が終わったこ とになるので main 関数から OS に戻ります。すなわちプログラムの 終了です。
     ここで気をつけておかなければならないのことは、関数の中身の定 義をその関数の呼び出しの前のところに書かなければならないことで す。こうしないとコンパイラは calc_series が何者かがわからない のでエラーを出しコンパイルを中止します。でもどうしても関数の中 身の定義をその関数の呼び出しの後ろに書きたいという場合が必ず起 こります。そういうときにはプログラムのかなり先頭にその関数の名 前だけを書いておけばエラーはでなくなります。つまり
    #include <stdio.h>
    void calc_series(void);
    
    のように #include の文のすぐ次に行ぐらいにこのように関数名を書 くのです。セミコロンを忘れずに。このなぞの void についてはすぐ 次に説明します。このように関数の中身より先にその名前だけを書い ておくことをプロトタイプ宣言と呼びます。このプロトタイプ宣言が あればコンパイラは関数の中身が定義される前にその関数が呼び出さ れても、それがとりあえず関数の名前であることがわかるので、エラー を出さずにコンパイルを完了します。
     いつもプログラムの先頭に #include <stdio.h> と書いてい ましたが実はこの stdio.h はヘッダーファイルまたはインクルード ファイルと呼ばれていまして、その中にはプロトタイプ宣言などがた くさん書かれているのです。そのうちのひとつに printf 関数のプロ トタイプ宣言があったのです。このプロトタイプ宣言などが書かれた ファイルを #include < > とするとコンパイルの時に自動的に #include の位置にそのファイルが展開されるわけなのです。このよ うなわけで今まで #include <stdio.h> としていたのです。な おインクルードファイルには他にもいろいろありまして

    stdio.h には標準入出力関数のプロトタイプ宣言が
    math.h には数学関数のそれが
    conio.h にはコンソール入出力関数のそれが
    string.h には文字列操作関数のそれが
    dos.h には DOS 制御関数のそれが
    graph.h または graphics.h にはグラフィック関数のそれが
    入っています。それぞれの出番はいずれでてくるでしょう。

     それから非常に大切なことなのですが calc_series 関数内で定義さ れている変数 n や sum にアクセスできるのは、この calc_series 関数内でのみです。メイン関数からはこれらにアクセスすることはで きません。つまりメイン関数内で n=1; とすると コンパイルの時に 「n という変数は定義されていません。」と出てコンパイルが中止さ れるのです。
     一般に関数内で宣言された変数 (ローカル変数と呼びます) はその 関数内からでしかアクセスできません。この変数のアクセス可能な範 囲を変数のスコープと呼びます。では関数外で宣言された変数 (グロー バル変数と呼びます) のスコープはどのようなのでしょうか。このス コープはプログラム全範囲となります。どこからでもアクセスできる のですからとても便利そうですね。しかしそれは半分間違いです。な ぜなら変数をたくさん作っていくうちに、そのグローバル変数と同じ 名前の変数を作ってしまって変数の衝突が起こるからです。知らない うちに誤って別のところで変数に値を代入してしまったりするのでバ グの原因となります。そうしたことを事前に防ぐためにもなるべくグ ローバル変数の使用は避けましょう。ローカル変数なら別の関数内で なら同じ名前の変数を使ってもいっこうに構わないのですから、変数 名を他と異なるように苦心して付ける必要もないのです。でもやはり、 どこからでもアクセスできるということにはある種の魅力を感じてや まないのです。なので時々使われます。

     さてもう一歩進んだ関数の使い方を習いましょう。つぎのプログラ ムは無限等比級数の総和を求めるプログラムです。無限と言ってもあ る有限項で見切っていますが。しかしこれは先のプログラムとは根本 的に異なっていて関数に級数の比例乗数を与えることができるように なっています。

    #include <stdio.h>
    
    void calc_series(double);            /* プロトタイプ宣言  */
    
    void main(void)                      /* メイン関数の定義  */
    {
        double k;
        printf("Input the value of k ");
        scanf("%lf", &k);                /* 比例乗数 k の入力 */
    
        calc_series(k);                  /* 関数の呼びだし    */
    }
    
    void calc_series(double r)           /* 関数の定義        */
    {
        double term=1.0, sum=0.0;
        
        while (term<-0.0001 && +0.0001<term){
            sum+=term;
            term*=r;
        }
            /* 結果の表示 */
        printf("The sum is %lf \n", sum);
    }
    
     この前のプログラムと違って関数の定義の部分の関数名の後の括弧 の中に double r というのがありますね。また関数の呼び出しのとき に括弧の中に変数 k がはいっています。こうすることによってメイ ン関数の中の変数 k の値が calc_series 関数の中の変数 r に代入 されるのです。このように関数に与えられる変数のことを引き数と呼 びます。
     関数の定義の部分の (double r) というのはその引き数の名前と型 を定義しているわけです。
     プロトタイプ宣言の方を見てください。引き数の変数の名前を定義 していませんね。プロトタイプ宣言には引き数の型 (例えば int, double) のみを書いておけば良いのです。
     今までの関数ではこの部分は (void) となっていましたね。これは 引き数を取らないという意味だったのです。(void) の意味はこれで 良くわかりましたね。
     それでは関数名の前に書いてある void の意味は何でしょうか。こ れは関数がそれを呼び出した方に値を返さないということを意味して います。では逆に値を返す関数はどのように作るのでしょうか。以下 に例として、相加平均値を計算する関数 mean を作りました。この関 数は引き数として二つの int 型の値を必要とし、ひとつの double 型の値を呼び出した所に返値します。
    #include <stdio.h>
    
    double mean(int, int);              /* プロトタイプ宣言 */
    
    void main(void)                     /* メイン関数 */
    {
        int a, b;
        double c;
        
        a=80;
        b=100;
        
            /* 関数の呼び出しと返ってきた値の変数 c への代入 */
        c=mean(a, b);
    
        printf("the mean of %d and %d is %lf \n", a, b, c);
    }
    
    double mean(int x, int y)          /* 関数の定義 */
    {
        double z;
        z=(x+y)/2.0;
        return (z);                    /* 値を返す */
    }
    
     つまり return ( ) の中に返す値の変数を入れておくのです。こう して数学と同じ意味の関数ができあがりました。関数を呼び出した側 での返値の扱い方はまさに数学関数と同じです。この場合は多変数引 き数関数ですが引き数が複数ある場合の関数の定義の仕方も理解して おいてくださいね。多変数引き数の関数の次は多返値関数を作りたい ところですが残念ながら複数の値を同時に返値する関数は作れません。

    その 6. C 言語入門最大の壁 ポインタ−に挑もう
     これまでのわたしの話は理解いただけたでしょうか。もし BASIC 言語の知識をお持ちでしたら理解は簡単のことと思います。し かしこれから私がするポインタ−の話は BASIC の知識では理解に苦 しむことと思います。でもこれを理解して自分の物にできれば、もう C 言語の文法はほとんど制覇したも同然です。頑張りましょう。

     いままで int a; とすることによって int 型の変数が定義される と話してきましたが、もう少し詳しい話をしましょう。そもそも int 型の変数とは 2 バイト以内の大きさの整数を記憶するメモリ−領域 のことなのです。ですから int a; とすることはメモリ−に a とい う名前で 2 バイトぶんの記憶領域を作ることなのです。メモリ−の 全体は非常に広大で、その 1 バイトごとに数字の住所が付いていま す。この住所というのがアドレスと呼ばれているものです。作った変 数の在るアドレスを表示してみます。

    #include <stdio.h>
    
    void main(void)
    {
        int a, b;
        double c;
        
        printf("a:%d  b:%d  c:%d \n", &a, &b, &c);
    }
    
    printf 関数の引き数の a, b, c に & マーク (アンパサンドと呼び ます) が付いていますね。これを変数の前に付けることによってその 変数の在るアドレスの先頭の値を示すことができるのです。したがっ てこれを実行して得られる a:-12 b:-14 c:-22 という結果は a, b, c の先頭のアドレスがその数字であることを示しています。int 型の 変数の先頭アドレスも double 型の変数の先頭アドレスも同じような 数字ですね。住所という物が大きい家でも小さい家でも同じような書 き方であることと同じです。以後アドレスと言ったらこの先頭アドレ スのことと考えてください。
     アドレスの数字は整数で大きさが 2 バイトなので実は int 型と同 じなのです。この数字を記憶する新しい型の変数を作ってみましょう。 アドレスの数字はどの型の変数のアドレスも 2 バイトなのですがそ の変数のメモリ上での占める大きさは型によってまちまちです。たと えば以下のような変数の型があります。
        int     2 バイト  単精度整数型
        long    4 バイト  倍精度整数型
        float   4 バイト  単精度浮動小数点型
        double  8 バイト  倍精度浮動小数点型
        char    1 バイト  文字型
    
     よって変数のアドレスを記憶するための新しい変数とはその変数の 型にあったものでなければなりません。この新しい変数というものが、 有名なポインター変数と呼ばれるものなのです。具体的に言えば int 型の変数用のポインター変数は int * で宣言します。例を出しましょ う。
    #include <stdio.h>
    
    void main(void)
    {
        int a, b;
        double c;
                    /* int 型変数用のポインター変数の宣言 */
        int *pa, *pb;
                    /* double 型変数用のポインター変数の宣言 */
        double *pc;
                    /* ポインター変数にアドレスを代入 */
        pa=&a;
        pb=&b;
        pc=&c;
                    /* ポインター変数の値を表示 */
        printf("a:%d  b:%d  c:%d \n", pa, pb, pc);
    }
    
     これの実行結果は先のプログラムと同じです。& マークを普通の変 数に付けるとその変数のアドレスを表すのでしたね。それでポインター 変数にアドレスがうまく代入できるのです。このようにポインター変 数にアドレスの値が代入されることを「ポインターがその変数を指す」 と表現します。この表現からポインターという名前が付けられたので す。逆にポインター変数の値であるアドレスからそこにある変数の値 を表すにはポインター変数に * マーク (アスタリスクと呼びます) を付けることによって実現されます。例えば
    #include <stdio.h>
    
    void main(void){
        int  a;
        int *p;
        
        a=1;
        p=&a;
        
        printf("The value at %d is %d.\n", p, *p);
    }
    
    といった具合です。実行結果は The value at -12 is 1. です。そう なる理由はおわかりですか。a に 1 が入っていて p は a のアドレ スが値として入っています。ここで *p というのは p の値であるア ドレスに存在する int 型変数の値のことです。ですから *p は 1 な のです。
     ここでひとつ考えてもらいたいことがあります。
        a=1;
        p=&a;
    
    のところを
        p=&a;
        a=1;
    
    の様に変えたら実行結果はどのようになるでしょうか。実は変える前 と全く変わりません。その理由は良く考えてみれば解るでしょう。
     次の部分的なプログラムを見てください。
        int  a;
        int *p;
        
        p=&a;
        *p=1;
    
    こうしたとき、a の値は何でしょうか。a の値は 1 です。この最後 の行で *p=1; としていますが、この行の意味はポインター変数 p の 値であるアドレスに存在する int 型変数に 1 を代入するということ です。ここで注意です。くれぐれも次の様にしないでください。
        int  a;
        int *p;
        
        *p=1;
        p=&a;
    
    この場合、*p=1; の行では p の値は何になっているかわかりません。 つまりどこかわけのわからない所に 1 が代入されてしまうのです。 なんでもない所に代入されるのなら平気ですが、たまたま p がある 種の危険な所を指していたりしますとプログラムの暴走を招きます。 ポインターを扱う際にはこのことに常に気をつけておいてください。

     さて、ここまでの話でポインターが何者かがおわかり頂けたことと 思います。しかしポインターが何のためにあるのか、ポインターのど こが面白いのかということが全然わからないことと思います。
     では、これからいよいよポインターのうま味を説明していきます。

     かなり前の方で scanf 関数の引き数に & マークが出てきていまし たね。

        scanf("%lf", &x);
        scanf("%d", &score1);
    
    もうおわかりでしょうが、実は scanf 関数に変数 x や score1 のア ドレスを送っていたのです。ではなぜアドレスを送ったのでしょうか。 scanf 関数はキーボードで入力された数字ないし文字列を指定の変数 に代入する関数です。したがってこの関数が引き数として必要な物は 変数の値ではなく、そのアドレスなのです。どこに代入するかの情報 が必要なわけなのです。だから & マークを付けていたのでした。結 局 scanf("%lf", &x); とすることで x に値が入るので、scanf は値 を返す関数と同じように見えますね。しかし scanf はもっと凄いこ とができます。多返値関数です。
        scanf("%lf %lf %lf", &x, &y, &z);
    
    これで同時に 3 つの変数に値が入ります。" " の中の書式が変数に 正しく対応することに気をつけて下さい。多返値関数を自分でも作っ てみましょう。よくある例ですが swap 関数を作ります。swap とは 二つの変数の値をお互いに交換する命令のことです。
    #inlcude <stdio.h>
    
    void swap int *, int * ;
    
    void main(void)
    {
        int a=1, b=2;
    
        printf("a=%d b=%d \n", a, b);
        swap( &a, &b );                    /* swap 関数の呼びだし */
        printf("a=%d b=%d \n", a, b);
    }
    
    void swap(int *x, int *y)
    {
        int temp;
        
        temp=*x;                        /* 値の交換の実行 */
        *x=*y;
        *y=temp;
    }
    
    解説します。swap 関数の引き数には a, b のアドレスを置きます。 a, b にはあらかじめ 1, 2 の値が入っています。swap 関数は 2 つ のアドレスをポインター変数 x, y で受けます。そしてそのポインター をもとにメイン関数内の変数 a, b の値を swap 関数内から変更して いるのです。実に巧妙ですね。この例は非常に大切ですからしっかり 理解してください。

     これでポインターのおいしさを少し理解してもらえたと思います。 でもポインターをうまく使うことによってもっとダイナミックなデー タの扱いが可能になります。そのちょっと最初の部分だけの話をしま しょう。

    その 7. ポインターと配列変数の関係 文字列の扱い方
     int a[10]; と宣言すると int 型の変数格納領域が連続して 10 個分作られます。10 個の変数がメモリー上に連続して存在している ので、a[0] のアドレスのすぐ後に a[1] のアドレスがあるのです。 ですから、かなりぎょうぎょうしい書き方をすれば *(&a[0]+1) は a[1] を表すのです。int 型は 2 バイトなのですから a[1] は *(&a[0]+2) なのではないかと思いになる方もおられるかもしれませ んが、 int 型変数のアドレスの足し算では +1 と書いてもコンパイ ラが自動的に +2 に変換してくれるのです。double 型なら +8 に変 換されるわけですね。ですから *(&a[0]+1) は a[1] と同じで *(&a[0]+2) は a[2] の同じになるのです。いかがでしょうか。そし て実は &a[0] は単に a と書いてもまったく同じになっています。つ まり配列の団体名はその配列の先頭の要素を指すポインター変数だっ たわけです。よって a[1] は *(a+1) と等価なのです。このことを用 いて配列の値を関数間で参照しあう仕組みを作ってみましょう。
    #inlcude <stdio.h>
    
    void movearray(int *, int *);
    void printarray(int *);
    
    void main(void)
    {
            /* 配列の宣言、一方は初期化する。*/
        int a[10], b[10]={0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
            /* 配列ごとのコピー */
        movearray(a, b);
            /*配列ごとの表示*/
        printarray(a);
    }
    
    void movearray(int *p1 ,int *p2)
    {
        int n;
        for (n=0; n<10; n++){
            *(p1+n)=*(p2+n);
        }
    }
    
    void printarray(int *p)
    {
        int n;
        for (n=0; n<10; n++){
            printf("%d ", *(p+n));
        }
    }
    
    この movearray 関数で配列 b の全成分を配列 a の全成分に代入す ることができるのです。また printarray 関数で配列 a の全成分の 表示ができるのです。ポインターと配列をうまくあわせて使うことに よってこのような多変数引き数多返値関数がいとも簡単に作れるので す。

     次にこのことを利用した文字列の操作方法について説明します。文 字 1 文字を記憶する変数の型は char です。例えば次のようにして 使います。

        char c = 'A' ;
        printf("%c", c ) ;
    
    つまり文字定数 A は ' ' で包んで使い、printf の書式には %c を 使うのです。文字列とは文字が複数連なったものなのですからここで 配列を使います。
        char str[30] = "Theoritical Science Group" ;
        printf("%s", str);
    
     数字の配列の初期化には ={0, 1, 2, 3} のように成分ごとに ,で 区切っていました。でも文字列でこれと同じことをすると ={'T', 'h', 'o', 'r', 'i', 't', 'i', 'c', 'a', 'l'} のようになっ て非常に醜いです。そこで例のような " " で包むだけで文字列の初 期化ができるようになっています。これは便利ですね。実はこのとき Group の p の後に '\0' という特殊な文字が自動的に入って配列が 初期化されます。ですから配列の大きさは文字数 +1 以上にしておか なければなりません。そして printf の書式には %s を使い引き数に は配列の先頭アドレスを指定するのです。%s を指定すると printf 関数はその指定されたアドレスから文字を順に表示していき '\0'の 文字で表示を終了します。つまり '\0' の文字はその文字列の終端を 表しているのです。したがって '\0' の無い文字列を表示させたら表 示が終了しなくなり、プログラムの暴走を招きます。でも大抵 '\0' は自動的に文字列に付加されるのでその心配はいらないでしょう。

     ここで文字列操作の関数をいくつか紹介しましょう。これらの関数 はヘッダーファイル string.h にそのプロトタイプ宣言が入っていま ので #include <string.h> としてください。

    strcpy( ) STRing CoPY の略
    文字列のコピーの関数です。
    第一引き数にコピー先のアドレス、
    第二引き数にコピー元のアドレスをいれます。
    返値はコピー先のアドレスとなります。
    使用例 1
     char str1[10], str2[10]="Naoki";
     strcpy(str1, str2);
    使用例 2
     char str[10];
     strcpy(str, "Watanabe");

    strcat( ) STRing CATch の略
    2 つの文字列を連結をして前側の配列にコピーする関数です。
    第一引き数に連結先のアドレス、
    第二引き数に連結元のアドレスをいれます。
    返値は連結先のアドレスとなります。
    使用例 1
     char str1[20]="Watanabe ", str2[10]="Naoki";
     strcat(str1, str2);
    使用例 2
     char str[20];
     strcpy(str, "Watanabe ");
     strcat(str, "Naoki");

    strcmp( ) STRing CoMPare の略
    2 つの文字列を辞書順に比較します。
    比較する 2 つの文字列のアドレスを 2 つの引き数とします。
    第一引き数の方が辞書順に後なら正の値を返し
    逆なら負の値を返し、そして同じなら 0 の値を返します。
    使用例
     if (strcmp(str1, str2)==0){
      printf("The string is match ! \n");
     }

    strlen( ) STRing LENgth の略
    文字列の長さ('\0'は含まない)を返値します。
    引き数に文字列のアドレスをいれます。
    使用例
     int n;
     n=strlen("C Computer Programing Language");
     /* n は 30 となります */

     コンピュータは計算するだけではありません。たまにはこういった 文字列関数を使って心暖まるメッセージを表示してみてはいかがです か ? (謎)

    その 8. ちょっと大きめのプログラムを作ってみましょう
     最終章です。今までに得た知識の総力を結集してカレンダーを 表示するプログラムを作ってみましょう。
     プログラムのまえに暦の規則について説明しておきます。

    1. 西暦 1 年 1 月 1 日は月曜日とする。
    2. 1 年は原則的に 365 日とする。
    3. 1 年が 366 日となる閏年を以下の規則で定める。
      1. 西暦の値が 4 の倍数である年は閏年あるとする。
      2. 西暦の値が 100 の倍数である年は閏年でないとする。
      3. 西暦の値が 400 の倍数である年は閏年であるとする。
        (つまり西暦 2000 年は閏年ですが西暦 1900 年は閏年ではないのです)
    4. 閏年の年の 2 月は 29 日までとする。
    5. 閏年でない年の各月の日数は以下の通りに定める。
      1 月 31 日, 2 月 28 日, 3 月 31 日, 4 月 30 日, 5 月 31 日, 6 月 30 日, 7 月 31 日, 8 月 31 日, 9 月 30 日, 10 月 31 日, 11 月 30 日, 12 月 31 日
    6. 一週間は 7 日とする。週の始めは月曜日とする。

    これらから導き出せる定理に次があります。
    1. ある年が閏年でなければ、次の年の 1 月 1 日の曜日はその年の 1 月 1 日の曜日の次の曜日である。
    2. ある年が閏年であれば、次の年の 1 月 1 日の曜日はその年の 1 月 1 日の曜日の次の次の曜日である。
      1. 任意の年の 1 月 1 日の曜日は、西暦 1 年からその年 まで (その年含まず) の年数とその間にある閏年の年数の和 を 7 で割った余りで決定される。
      2. 任意の年の任意の日の曜日はその年の 1 月 1 日の曜日 から決定される。

     これでカレンダーを作るための知識は万全です。プログラムでは曜 日は数字で扱うことにします。月曜日を 0 、火曜日を 1 、そして日 曜日を 6 とします。でははじまり。
    #include <stdio.h>
    
    /* プロトタイプ宣言 */
    void input(int *, int *);
    int calc_year(int);
    int calc_month(int, int);
    void print_calender(int, int, int);
    
    /* グローバル配列変数の宣言と初期化 */
    int days[13]={0 ,31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    
    /* メイン関数 */
    void main(void)
    {
        int year, month;              /* 変数宣言 */
        int diff_y, diff_m;
        
        input(&year, &month);         /* 年と月を入力する */
        
                                      /* 1 月 1 日の曜日を計算する */
        diff_y=calc_year(year);
                                      /* 月のついたちの曜日を計算する */
        diff_m=calc_month(year, month);
        
                                      /* カレンダーを書く */
        print_calender(year, month, (diff_y+diff_m)%7 );
    }
    
    /* 年と月を入力をまとめた関数 */
    /* 引き数は年と月の変数のアドレス */
    void input(int *y, int *m)
    {
            /* 年の入力、正しい年が入力されるまでループを繰り返す。*/
        while (1){
            printf("Input year  = ");
            scanf("%d", y);     /* scanf の引き数に & が無いことに注意 */
                                /* y の値は既にアドレスであるから。    */
               /* 正の数のみを年として受け入れそれ以外なら入力し直し   */
            if (*y<=0){
                printf("Please input a positive number. \n");
            }else{
                break; 
            }
        }
                /* 月の入力、正しい月が入力されるまでループを繰り返す。*/
        while (1){
            printf("Input month = ");
            scanf("%d", m);    /* scanf の引き数に & が無いことに注意  */
                               /* m の値は既にアドレスであるから。     */
                               /* 1 から 12 の数のみを月として受け入れ */
                               /* それ以外なら入力し直し               */
            if (*m<1 || 12<*m){
                printf("Please input a right number. \n");
            }else{
                break;
            }
        }
    }
    
    /* 閏年の数を計算して、この年の 1 月 1 日の曜日を計算する */
    int calc_year(int y)
    {
        int diff;
        diff=(y-1)+(y-1)/4-(y-1)/100+(y-1)/400;
            /* 1 月 1 日の曜日を返値する */
        return (diff);
    }
    
    /* 閏年のチェックをして、*/
    /* この月のついたちと 1 月 1 日との曜日のずれを計算する */
    int calc_month(int y, int m)
    {
        int n;
        int diff;
        
            /* 閏年のチェック */
        if (y%400==0 || (y%100!=0 && y%4==0) ){
            days[2]=29;        /* 閏年なら 2 月の日数を 29 日とする */
        }
            /* この月までの日数の計算 */
        diff=0;
        for (n=1; n<m; n++ ){
            diff+=days[n];
        }
            /* 曜日のずれの計算し返値する */
        return(diff%7);
    }
    
    /* 一月分のカレンダーの表示 */
    void print_calender(int y, int m, int diff)
    {
        int n;
        
        printf("\n====%4d year %2d month ====\n", y, m);
        printf("MON TUE WED THU FRI SAT SUN\n");
        
            /* ついたちまでの空白を入れる */
        for (n=0; n<diff; n++){
            printf("    ");
        }
            /* カレンダーを書く */
        for (n=1; n<=days[m]; n++){
            printf(" %2d ", n);        /* 日の表示 */
                /* 週末のチェック */
            if ((diff+n)%7==0){
                printf("\n");         /* 週末で改行 */
            }
        }
        printf("\n");            /*ただの改行*/
    }
    
    
    このプログラムの実行結果を示します。
    
    Input year  = 1995
    Input month = 4
    
    ====1995 year  4 month ====
    MON TUE WED THU FRI SAT SUN
                          1   2 
      3   4   5   6   7   8   9 
     10  11  12  13  14  15  16 
     17  18  19  20  21  22  23 
     24  25  26  27  28  29  30 
    
     さあどうですか。プログラムはわりと平易に読めたことと思います。 これでもうあなたは立派な C 言語のプログラマーの仲間入りです。 TSG に入ってもっとその腕を磨きましょう。

     今回の私の話の続きの話として、構造体とポインターとが織りなす ダイナミックなデータ構造の話が TSG 部報の 95 年度 1 月ケーキパー ティー号に載っていますので、是非ともそちらもご覧下さい。またさ らに先の話として C++ 言語のクラスの話が TSG 部報 95 年度 3 月 追い出しコンパ号に載っていますので是非 是非そちらもご覧下さいませ。

     それではこの辺でわたしの話を終わりにするとしましょう。どうも 最後まで読んでいただき有難うございました。
     TSG は皆様のお越しをお待ちしております。

    TSG 95 年度 代表 理科一類 二年三組 渡辺 尚貴


    今はやりの WWW

    (GANA)

     去年の秋、駒場東大前駅の前に、一つのビルが建ちました。情報教育南棟で す。それまでは情報教育棟(現在は情報教育北棟)で、 FM-R を使って BBS ソ フトを使って、学内のニュースを読み書きしたり、日本や世界のインターネッ トにながれているニュースを読んだり、インターネットにつながっているもの 同士でのメール交換ができただけですが、南棟では、最近パソコン関連の雑誌 を見るとたいてい載っている WWW (World Wide Web) で流れている情報を見た り、聞いたり、自分で情報を発信したり、その他様々なことができるようにな りました。ここでは、WWW に関して僕の知っていることを、新入生に分かりや すいよう努力しつつ書いてみたいと思います。

     WWW で流れている情報を見聞きするには WWW ブラウザを使います。南棟で は、xmosaic というソフトを使います。使い方は非常に簡単で、青くなってい る文章をマウスでクリックすることを繰り返せばいいのです。青くなっている 文章は、URL というもので指し示される他の情報につながっています。
     URL とはどういうものでしょう ? URL はそれだけで、どのコンピューター の、どのディレクトリにある、どのファイルを、どうやって自分のコンピュー ターに持ってくるかということを指定しています(telnet は少々違う。)。具 体的に、 http://www.komaba.ecc.u-tokyo.ac.jp/~g541119/TSG/TSGhome.html とい う URL を例として説明します。はじめの http というのは、この情報を要求 する方法の指定です。http プロトコルの他にも、file (ftp プロトコル)、 gopher (gopher プロトコル)、telnet (telnet プロトコル)、etc. 色々あり ます。次の www.komaba.ecc.u-tokyo.ac.jp は、この情報を持っているコンピュー ターの名前です。localhost というコンピューターにすると、自分の使ってい るコンピューターになります。www.komaba.ecc.u-tokyo.ac.jp:10000 といっ た感じでこのプロトコルで使うポート番号を書くこともできます。次の ~g440604/TSGhome.html は、この情報を持っているコンピューターの ~g440604/ というディレクトリの TSGhome.html というファイルを指定してい ます。結構ややこしそうに見えますが、何ということはありません。自分で情 報を発信しようと思わないのなら、全く気にする必要はないのです。

     南棟から、自分で情報を発信しようとする時、どうすればいいかを次に説明 します。
     まず、~/WWW というディレクトリをつくります。chmod go+rx ~/WWW をしま す(多分。やっておけばまちがいない)。そして、自分のホームページとなる ~/WWW/index.html をつくり、chmod go+r ~/WWW/index.html としておきます (重要・よく忘れます)。
     次に index.html の書き方ですが、WWW ブラウザでみる文章は主に HTML と いう書式(簡易言語)で書かれています。URL は http://.../...html 又は、 http://.../...html#.... となります。ファイルは、JIS で書くといいそうで すが、南棟では EUC で書いても ShiftJIS で書いても読めます。文章の中で は、<> で囲まれた部分が書式を指定しています。僕の良く使うのとしては、

      <H1>...</H1>       ... の文章を大きな文字で表示。
                     <H1>〜<H6> がある。
      TITLE>...TITLE>     ... の文章を Document Title にする。
      <ADDRESS>...</ADDRESS> ... がアドレスだと分かるように書体を変える。
      <CODE>...</CODE>     ... を等しい幅の書体で表示。
      <TT>...<TT>        同上
      <BR>            改行する。
      <HR>            水平の線を挿入。
      <IMG SRC="URL">      絵を表示。絵は GIF。
      <A HREF="URL">...</A>  ...をクリックできるように色を変えて(青)
                              表示。クリックすれば、URL で示される情報を表示。
      <A NAME="label">...</A> URL を、http://.../...html#label と
                              して、その情報を表示すると <A NAME="label"> 
                              のある場所から表示される。(... には 1 文字以上の
                              文字がないといけない)
    
    
    	書式                        表示結果
    
    	箇条書
    
    	<UL>
    	  <LI> ....                 ・....
    	  <LI> ....                 ・....
    	  <LI> ....                 ・....
    	</UL>
    
    	番号付箇条書
    
    	<OL>
    	  <LI> ....                 1. ....
    	  <LI> ....                 2. ....
    	  <LI> ....                 3. ....
    	</OL>
    
    	説明付箇条書
    
    	<DL>
    	  <DT> ....                 ....
    	  <DD> ..............          .............. 
    	       ..............          .............. 
    	  <DT> ....                 ....
    	  <DD> ..............          .............. 
    	       ..............          .............. 
    	</DL>
    
    	(ネスティングできます)
    
     まだまだ色々ありますが、駒場のホームページの下にある HTML について説 明してある文章を読んだり、xmosaic の File - View Source で、他の人の作っ た文章を見て研究して下さい。HRD さんのものなど、こちらから情報を発信す るだけでなく、相手からの反応を受けとれるようにすることもできます。

     より良い資料としては、 こんなのや、 こんなのや、 こんなのがあります。

     最後に、http://www.komaba.ecc.u-tokyo.ac.jp/~g541119/TSG/TSGhome.html につ いて。僕は、南棟が使えるようになってからしばらくは xmosaic は、首相官 邸(?)のホームページの村山総理の写真を見てみたり、Microsoft のページか ら、ソフトを持ってきたりというようなことをしていたのですが、そのうち自 分でも作れることになり、自分のホームページとこの TSG のホームページを 作ったのです。しかし、作ったはいいが書くことがありません。結局、いまで も、ほとんど情報量のないページとなっていますが、少しでも情報量を増やす ために、1. 平安京エイリアンの MS-Windows 版と X Window 版の配布、2. 部 誌を読めるようにする。ということをしたいと思っています。1.は、僕が「何 かホームページに載せる情報ありませんか」とニュースで聞いたところ出てき たもので、僕が作ることになってしまいました。少々怠けていたので、まだ出 来上がっていません。この冊子ができあがったころにはできているといいけど。 2.については、原稿がまだもらえないので、載せられません。原稿ちょーだい >ちょもらんま。みなさんも、なにか載せるべき情報があれば、僕にメールで もして下さい。お願いします。

     なにしろ、WWW とつきあったのは、去年の秋からですので、間違っていると ころや、変なところがあると思いますが、御容赦下さい。文章中の間違い、内 容についての質問などがありましたら、下記へメールをください。では。

           g440604@komaba.ecc.u-tokyo.ac.jp (TAGA Nayuta)


    ごみ 2.LZH

    (TEA)

     今回は、LZH の圧縮についてもう少し書こうと思います。まずは前回の記事 の訂正です。
     文字列をハッシュ表に登録していく方法の場合、同じ文字が何文字も並んで いるとスピードが非常に遅くなってしまうので、それを防ぐ方法として PKZIP が探索する回数を制限したり、同じ文字が並んでいるところをハッシュに登録 しないんじゃないかと書きましたが、やはりうそでした。回数を制限している というのはあってるみたいですが、やはり LHA のようにハッシュの値を計算 する位置をずらしていました。でも LHA とはかなり違うみたいです。これか ら圧縮しようとする位置の 1 文字前からの文字列が、それよりも前の文字列 と一致しているときに、その一致している部分でそれぞれリストの次の要素と の距離を求めて、それが一番遠くなっているリストを探索するみたいです。と 書いてもよくわかりませんね。ぼくもよくわかっていません (爆)。でも、同 じ文字が連続しているところを検出しているのは確かなのですが、それは何の ためにやっているのかさっぱりわかりません。
     もう 1 つの訂正は、ぼくも圧縮を作りたいけど気力がないと書いておきな がら、作り始めていたことです。言い訳をさせてもらうと、原稿の締め切りが 土曜日だったので、金曜日に書いて提出したのですが、日曜日に気合いを入れ て作ったのでした。(^^; もっとも、かなり前から LHA の C のソースをいじっ たり、紙の上でプログラムを書いたりしていたし、スライド辞書以外の部分は 1 年前に作ったいんちき圧縮プログラムと同じなので、わりとすぐにできまし た。うれしくなって、油すまし君に見せたのですが、どこが変わったのと言わ れて、-lh6- になったと言ったら、意味ないとか言われたのはショックだった なあ。(^^;

     では、ぼくが現在作っている LZH の圧縮プログラムについて書きます。名 前は SFA です。今は別の名前になってしまった SF & アニメーション同好会 とは関係ないはずです。
     ぼくは圧縮率がいいものよりもスピードが速いものが好きなので、何もオプ ションをつけないときは圧縮率を少しだけ落としてわりと高速に圧縮するモー ドにしようかなと思っています。このモードでは 486 では LHA 2.63 の 2 倍 くらいのスピードでした。解凍の場合は 286 だと 486 で比べたときより LHA との差がかなり大きかったのですが、圧縮の場合は 286 でも 2 倍くらいみた いです。正確に測っていないのでわかりませんが。
     あとは、LHA より圧縮率を数パーセント落として 3 倍くらいのスピードで 圧縮するモードと、LHA とほぼ同じ圧縮率で 1.5 倍のスピードのモードがあ ります。もしかしたらもう少し遅くて縮むモードをつけるかもしれません。で も、普通の人はオプションはつけない (それどころかドキュメントを読まない) ので、デフォルトのモードをどうするかというのは非常に重要なんですよね。

     SFA のアルゴリズムですが、LHA とほとんど同じになってしまいました。な さけない。最初は、例の遅くならない工夫は LHA と同じになるし、判定に時 間がかかるかもしれないので使いたくなかったし、遅くなるようなファイルの ときに適当に検索を打ち切るような簡単な仕組みをいれていたので、あまり複 雑なことはしていませんでした。
     やっていたのは、探索する回数を適当に制限するくらいでした。ところが、 ARJ の逆アセンブルリストを圧縮してみたら LHA とほとんどスピードが変わ らなかったので、これは SFA でも採用しないといけないなあと思っていれて みました。するとだいぶ速くなってしまったので、くやしいけど採用すること にしました。今までの工夫は、圧縮率を落とすかわりにスピードを上げるもの だったのですが、これは圧縮率とスピードが共に良くなりました。解凍の場合 はいかに判定を減らして高速化するかが問題だったのですが、圧縮の場合は、 多少判定に時間を掛けてもそのあとの処理が軽くなるようにすれば速くなるの ですね。
     ちなみに、LHA 2.66 がしている工夫を禁止する方法を書いておきます。 symdeb などで、次のように書き換えてみてください。

    <2257:4280 7422           JZ     42A4 (リストが長くなかったらジャンプ)
    >2257:4280 EB22           JMP    42A4 (無条件ジャンプに)
    
    <2257:4327 7427           JZ     4350 (一定の回数探索したらおしまい)
    >2257:4327 7400           JZ     4329 (次の命令へのジャンプに)
    
     こうするとかなり遅くなります。ちなみに、本当はあとのほうの 1 バイト で十分でした。(^^; 一定の回数探索した場合、リストが長いというフラグを 立てるので、フラグが絶対に立たなくすればいいんですね。また、最初のほう の 1 バイトだけにすると、あとのほうを変えるよりは少し速くなります。

     LHA とほぼ同じ圧縮率になるモードを作ったはずなのですが、すべてのファ イルでそうなるとは限らないんですよね。2 倍速くても LHA より縮むファイ ルがあったり、どんなにがんばっても圧縮率が LHA より数 % 悪いファイルが あったりします。LHA より縮む方は、PKZIP と同じように 3 バイトのコピー を遠くから行わないからだと思いますが、LHA より縮まない方は、たぶんハフ マン符号にするブロックが LHA より小さいからではないかと思います。SFA では高速化のためにメモリにデータを蓄える方法が違うので、これはどうしよ うもありません。常に LHA より圧縮率が良くなるものを作ろうと思ったら、 中間バッファのサイズを LHA と同じにしなくてはいけないはずなので、そう すると遅くなるのでやめました。

     圧縮のアルゴリズムはだいたい固まったので、最近はディスクへの書き込み を高速化していました。LHA の場合はテンポラリのディレクトリにアーカイブ を作ってから、最後に目的のディレクトリに転送しています。この方がフロッ ピーの同じドライブの中で圧縮するよりは速くなりますが、たまにテンポラリ のディスクが足りなくなって、途中で止まってしまうんですよね。そのときに LHA は途中まで作ったアーカイブを消してしまうので、ぼくはむかつきます。 だから現在は目的のディレクトリに直接アーカイブを作っていくようにしてい ます。
     1 年前に作った SFA では、このへんは何も考えてなくて、1 つのファイル を圧縮し終わるごとにヘッダーの部分にシークして、圧縮後のサイズと CRC を書き込むということをしているので、フロッピー上に圧縮するとしぬほどお そかったんです。でも今は圧縮したデータをなるべくメモリ内にとっておくよ うにしているので、オーバーヘッドはあまりないようです。LHA よりはなぜか 速いようなので、これでいいでしょう。本当は、テンポラリのディスクに容量 ぎりぎりまで作って、いっぱいになったら転送というようにするのがいいんで しょうね。
     あと、SFA ではディスクが足りなくなって途中で止まったときに、正常にディ スクに書き込めたところまでは書庫を残すようにしています。フロッピーにバッ クアップをとるときは便利かもしれませんね。でも最近は MO があるからいら ないか。(^^;

     SFA は、今は新しい書庫を作ることしか出来ません。この部分を作るのが一 番問題なんですよね。圧縮したいファイル名をメモリ内に全部覚えておかなく てはならないので、そのメモリがどのくらい必要なのかわかんないし。そもそ も LHA の動作をすべて理解しているわけではないし。ああ面倒。(^^; だから こんど NIFTY にアップロードするときも、たぶん書庫の更新はできないもの になると思います。(^^;

     油すまし君も LZH の圧縮を作っている途中なので、SFA を見せたときに油 くんの LXF のソースを見せてもらったのですが、思わず笑ってしまいました。 なぜかというと、ぼくのプログラムと同じようなことをしていたからです。考 えることはみんな同じなんだなあ。(^^;

     なんか (そういえば、坂井真紀は昔、なんかという言葉をよく使ってたなあ)、 ぼくがした工夫のことを全く書いてありませんが、たいしたことはしてないの で書くまでもないんです。(^^; 探索をいつ打ち切るかを決める方法をちょっ と変えただけなので。

     LZH の圧縮のプログラムは、昔からいつかは作りたいなあと思っていたので すが、だいぶ現実的になってきました。でもこの先を作る気力があるかなあ。 (^^; 今の目標は、LHA 3.0 が公開される前に SFA を公開することです。も ちろん -lh6- で圧縮できることは内緒にして。:-)

     新入生の皆さん、ご入学おめでとうございます。ところで、team や LHE と いうプログラムをご存じの方はいらっしゃいますか? いたらうれしいなあ。 (^^; もし知っているという方がいらしたら、メールください。アドレスは、 sada@is.s.u-tokyo.ac.jp です。もちろん、知らない方でも、プログラミング に興味があったり、森口博子さんが好きだという人はメールくださいね。(^^)


    MODRM バイト & SIBバイトの役割

    (ちょもらんま)


    8086 感覚で 4Gb メモリ空間を扱う方法

    (Zephyr)

    【はじめに】

     最近 MS-DOS の環境でプロテクトモードのプログラミングをすることがはやっ ているようで、解説書の数も増えてきました。おかげで、プログラミングがま すますやりやすくなってきています。この原稿では、そうして僕がプロテクト モードをいじっているときに見つけた、裏技的な動作モードについて説明しま しょう。題して、「8086 感覚で 4Gb メモリ空間を扱う方法」です。勘のいい 人はもうわかったかもしれません。そう、あれです。
     この動作モードは普通の人は使う気は起こらないとは思いますが、何かの折 りに役に立つこともあるでしょう。
     いちおう使用機種は 98 ですが、これはあまり本稿の主題とはあまり関係が ないはずです。使用するアセンブラは TASM で、ideal モードを使っています。 邪悪、なんて言わないように。みりゃ大体わかるでしょう。

     386 以上でサポートされるプロテクトモードについては、ここではいちいち 説明しませんので、これについてある程度以上の知識があることが前提です。 自分で勉強したい人は、最後の【参考書】で紹介している本を読んでみてくだ さい。

    【概要】

     8086 のプログラミングで「セグメントレジスタ」というと、普通 CS, DS などの 16 ビット幅のレジスタを指しますが、386 内部ではセグメントレジス タというのは全部で 80 ビット幅のレジスタであり、このうち 16 ビットを 「セレクタレジスタ」、残り 64 ビットを「ディスクリプタレジスタ」と呼び ます。これが CS, DS, ES, FS, GS, SS それぞれに用意されています。
     この様子は次の図ように表せます。これはオーム社の『80486 の使い方』と いう本から引用したものです。

    16             0      64                                                  0
    +--------------+      +----------+------------------------+---------------+
    |              |  CS  |   属性    |     先頭番地(32 bit)     | 大きさ(20 bit) |
    +--------------+      +----------+------------------------+---------------+
    
    +--------------+      +----------+------------------------+---------------+
    |              |  DS  |   属性    |     先頭番地(32 bit)     | 大きさ(20 bit) |
    +--------------+      +----------+------------------------+---------------+
    
    +--------------+      +----------+------------------------+---------------+
    |              |  ES  |   属性    |     先頭番地(32 bit)     | 大きさ(20 bit) |
    +--------------+      +----------+------------------------+---------------+
    
    +--------------+      +----------+------------------------+---------------+
    |              |  FS  |   属性    |     先頭番地(32 bit)     | 大きさ(20 bit) |
    +--------------+      +----------+------------------------+---------------+
    
    +--------------+      +----------+------------------------+---------------+
    |              |  GS  |   属性    |     先頭番地(32 bit)     | 大きさ(20 bit) |
    +--------------+      +----------+------------------------+---------------+
    
    +--------------+      +----------+------------------------+---------------+
    |              |  SS  |   属性    |     先頭番地(32 bit)     | 大きさ(20 bit) |
    +--------------+      +----------+------------------------+---------------+
    セレクタレジスタ                        ディスクリプタレジスタ
    
     mov DS, AX などのセグメントにかかわる命令は、このセグメントレジスタ のうち、セレクタレジスタをいじっていることになります。そして、ディスク リプタレジスタの更新は、セレクタレジスタの更新時に同時に行われます。こ のセレクタレジスタ更新に際して、どのようなことが 386 内部で起こってい るかを、プロテクトモード、リアルモードの場合について考えてみることにし ましょう。なお、以下の説明は多分に僕の推理によるもので、実際にこう動作 しているということを保証しているわけではありません。単にこう考えるとう まくいく、という程度のものです。

     まず、プロテクトモードではどうでしょうか。
     セレクタレジスタを更新すると、GDTR の指すメモリ上のディスクリプタテー ブルから、そのセレクタが指すディスクリプタをディスクリプタレジスタに読 み込みます。
     つまり、ディスクリプタレジスタはセレクタレジスタの指すディスクリプタ の単なるコピーで、アクセスする度にディスクリプタテーブルを見に行くのを 避けるための、いわばキャッシュのようなものなのです。これによって、高速 にメモリをアクセスすることができます。

     さて次に、リアルモードではどうなっているのでしょうか。
     リアルモードでは、セレクタレジスタを更新すると、更新されたセレクタの 16 倍をディスクリプタレジスタのセグメントベースアドレスのフィールドに セットします。ディスクリプタレジスタの他のフィールドは変更されないまま 残されます。
     そしてメモリアクセスは、プロテクトモードと同様、ディスクリプタレジス タを参照しつつ行われます。セレクタレジスタはまったく参照されません。
     プロテクトモードからリアルモードに切り替わる際に、CS には無意味なセ レクタ値がそのまま残っているにもかかわらず、正しく命令を読み込むことが できるのは、このような構造をしているからであろうと思われます。

     セグメントディスクリプタはセグメントのベースアドレス/リミット/属性な ど、様々なことを記述できるわけですが、このディスクリプタレジスタの内容 は、リアルモード、プロテクトモードにかかわらず、すべてのメモリアクセス に関して影響します。
     そのために、プロテクトモードからリアルモードに切り替える時には、リア ルモード用のセグメントを記述したディスクリプタを指すセレクタをロードす ることが必要になるわけです。
     具体的には、CS には 64Kb の大きさの 16-bit コードセグメント、DS, ES, FS, GS には 64Kb の大きさのデータセグメントを記述したディスクリプタを 指すセレクタをロードします。この作業は、プログラマの責任で行わなくては なりません。ありがたいことに、386 が自動的にやってくれるわけではないの です。
     そこで、このディスクリプタの復旧を意図的にさぼることによって、リアル モードで、つまり 8086 感覚で 4Gb メモリ空間を自由にアクセスしようとい うのが、この原稿で説明しようとしているアイディアなのです。

     このような状態でプログラムを実行するメリットは何でしょうか。
     まず、プロテクトモードでなくリアルモードで実行していることになるので、 時間のかかる保護機構が働かなくなり、すべての命令を最高速で実行すること ができます。特にセグメントセレクタの更新、割り込みの発生に際しては明ら かな違いが現れます。リアルモードなので、CS に対してデータの書き込みも 何の問題もなく行うことができます。
     また、メモリアクセスが完全に 8086 感覚なので、特に DOS 環境でのプロ グラムが作りやすくなります。割り込みも 8086 とまったく同じしくみなので、 めんどうな割り込みディスクリプタの設定が必要ありません。

     さらに、ROM BIOS や int 21h の DOS ファンクションコールなど、いまま でのリアルモード用のルーチンも利用も容易です。

     さて、Intel がこのような使い方を保証しているという話は聞いたことがな いので、将来発表されるプロセッサではこのアイディアは通用しない可能性が あります。しかし、386/486/Pentium ではきちんと動くことが確認されていま すし、AMD 486DX4 でも動作することが報告されていますので、まあ当分の間 は実用になることでしょう。

    【簡単なサンプル】

     さて、前置きが長くなってしまいましたが、このアイディアの応用のしかた の簡単な例として、次のプログラムを見てみましょう。

    ________________________________________________________________________
      1 ; 98 グラフィック VRAM をクリアするプログラム
      2 ; 仮想 86 モードでは実行できません
      3 
      4         ideal
      5         p386
      6         model   use16 small, c
      7         jumps
      8         locals
      9 
     10 macro   switchProtectedMode
     11         mov     EAX,CR0
     12         or      AL,1
     13         mov     CR0,EAX
     14         jmp     $+2
     15         endm
     16 
     17 macro   switchRealMode
     18         mov     EAX,CR0
     19         and     AL,0FEh
     20         mov     CR0,EAX
     21         jmp     $+2
     22         endm
     23 
     24 
     25         stack   100h
     26 
     27         dataseg
     28 
     29 GDTP    df      ?
     30 GDT     db      8 dup (0)                               ; 00h: ヌルセレクタ
     31         db      0FFh, 0FFh, 0, 0, 0, 92h, 8Fh, 0        ; 08h: 4GB DATA
     32         db      0FFh, 0FFh, 0, 0, 0, 92h, 00h, 0        ; 10h: 64KB DATA
     33 
     34         codeseg
     35 
     36 proc    breakWall
     37         uses    DS, ES
     38 
     39                 ; GDT のポインタをロード
     40         xor     EAX,EAX
     41         xor     EBX,EBX
     42         mov     AX,@data
     43         shl     EAX,4
     44         mov     BX,offset GDT
     45         add     EAX,EBX
     46         mov     [dword ptr GDTP+2],EAX
     47         mov     [word ptr GDTP],17h
     48         lgdt    [GDTP]
     49 
     50         pushf
     51         cli
     52         switchProtectedMode
     53 
     54         mov     AX,08h
     55         mov     DS,AX
     56         mov     ES,AX
     57 
     58         switchRealMode
     59         popf
     60         ret
     61 
     62 endp    breakWall
     63 
     64 proc    makeWall
     65         uses    DS, ES
     66 
     67         pushf
     68         cli
     69         switchProtectedMode
     70 
     71         mov     AX,10h
     72         mov     DS,AX
     73         mov     ES,AX
     74 
     75         switchRealMode
     76         popf
     77         ret
     78 
     79 endp    makeWall
     80 
     81 
     82 entry:
     83         mov     AX,@data
     84         mov     DS,AX
     85 
     86                 ; 64KB の壁を消す
     87         call    breakWall
     88 
     89                 ; G-VRAM のクリア
     90         cld
     91         xor     EAX,EAX
     92         mov     ES,AX
     93         mov     EDI,0A8000h
     94         mov     ECX,32768*3/4
     95         db      66h, 67h        ; ECX, EDI を使用
     96         rep stosd
     97         mov     EDI,0E0000h
     98         mov     ECX,32768/4
     99         db      66h, 67h        ; ECX, EDI を使用
    100         rep stosd
    101 
    102                 ; 壁を再構築する
    103         call    makeWall
    104 
    105                 ; 終了
    106         mov     AX,4C00h
    107         int     21h
    108 
    109         end entry
    ------------------------------------------------------------------------
    
     これは 98 用ですが、単にグラフィック VRAM を消去するもので簡単なので、 98 をお持ちでない方でも言っていることはだいたいわかるでしょう。GRCG を 使ったほうが速いとか野暮なことは言わないように。
     このプログラムは TASM と TLINK を使って普通にアセンブル、リンクでき ます。ただし実行に際しては、いくつかの特権命令が使われているので、仮想 8086 モードでは動きません。リアルモードで実行してください。

     このプログラムのメインルーチンは 82 行目から始まります。DS の設定を した後、breakWall というプロシージャを呼び出します。
     breakWall では、switchProtectedMode, switchRealMode という 2 つのマ クロが使われています。これは冒頭の定義を見ればわかるとおり、CR0 レジス タの PE ビットを 1 あるいは 0 にしたあと、命令先読みキューをフラッシュ するというものです。このふたつのマクロでプロテクトモードとリアルモード を行き来します。

     まず breakWall では GDT を設定します。GDT は初期化済みデータセグメン ト(dataseg)であらかじめ用意されていますので、そこを指すリニアアドレス ポインタと、GDT のサイズをメモリにストアし、それを lgdt 命令で読み込み ます。
     その後、switchProtectedMode でプロテクトモードに移行した後、DS, ES にセレクタ 08h をロードします。このセレクタは、ベースが 0h で、リミッ トが 4Gb のデータセグメントを指します。これによって、DS, ES を使って 386 のリニアなアドレス空間をアクセスすることができます。
     しかしこのプログラムでは、このセレクタへの更新を行ったあと何もするこ となく、さらに、変更した DS, ES の復旧も行うこともなく、すぐにまた switchRealMode でリアルモードに戻ってきてしまいます。
     この「DS, ES の復旧も行うことなく」というのがこのプログラムで一番重 要なところです。リアルモードでも、ここのセレクタの更新によって変更され たディスクリプタレジスタの中身はそのまま残っています。つまり、ベースア ドレスが 0h、リミットが 4Gb のままなのです。
     プロシージャ breakWall はこれでおしまいですが、このプロシージャには uses 指定があるので、プロシージャを抜けるとき、DS, ES はコール前の値に 再び更新されます。リアルモードでは、DS, ES が更新されるときに変更され るセグメントレジスタはそのベースアドレスだけなので、セグメントリミット は 4Gb のままです。

     メインルーチンに戻ると、リアルモードにしては見慣れない記述が続きます。 95 行で db 66h, 67h と置いていますが、これは続く命令のオペランドサイズ を修飾するプリフィクスで、66h は rep が CX ではなく ECX をカウントする ようにします。同様に 67h はアドレスサイズを修飾して sotsd 命令が、 ES:DI ではなく ES:EDI をディスティネーションとするようにします。
     結局、ここではセグメントベース 0h, オフセット 0A8000h から 0 を 18000h 個書き込んでいるわけです。
     これを普通のリアルモードで実行しようと、一般保護例外 #13 が発生して、 rep stosd を実行する瞬間に止まってしまいます。普通のリアルモードでは、 セグメントリミットは 64Kb だからです。しかし、このプログラムでは、何の 問題もなく実行できます。
     続いて、n 行目からはセグメントベース 0h、オフセット 0E0000h から 0 を 8000h 個書き込みます。これで、2 つにわかれている 98 のグラフィック VRAM をすべて消去したことになります。

     VRAM の消去が完了したら、プロシージャ makeWall を呼び出します。これ は GDT の設定をいじらないこと以外は breakWall と構成は同じで、DS, ES のディスクリプタをリアルモードの値に戻すというものです。
     これでこのプログラムはおしまいです。意外と簡単ですね。

    【32-bit コードセグメントの実行】

     プログラムを 32-bit コードセグメントで実行することはもちろん可能です。 これによって大量のプリフィクス 66h, 67h の削減が期待でき、プログラムが コンパクトかつ高速になります。しかし、プログラムは途端にややこしくなり ます。
     CS を更新するには、基本的にはセグメント間ジャンプ/コールを行います。 そこで 32-bit セグメントを記述したディスクリプタを作って、そのセグメン ト内の 32-bit ルーチンにジャンプ/コールし、そのルーチン内でリアルモー ドに切り替えればよい、というのはだいたい想像がつくでしょう。
     しかしこの方法だとプログラムの構造が複雑になりますので、ここではコー ルしたら CS の D bit を 0 にしたり 1 にしたりして戻ってくるルーチンを 作ってみます。次のサンプルを見てください。

    ________________________________________________________________________
      1         dataseg
      2 
      3 GDT00   db      8 dup (0)                            ; 00h: ヌルセレクタ
      4 GDT08   db      0FFh, 0FFh, 0, 0, 0, 9Ah, 0,    0    ; 08h: 64KB use16 CODE
      5 GDT10   db      0FFh, 0FFh, 0, 0, 0, 9Ah, 0CFh, 0    ; 10h:  4GB use32 CODE
      6 GDT18   db      0FFh, 0FFh, 0, 0, 0, 92h, 0,    0    ; 18h: 64KB DATA
      7 GDT20   db      0FFh, 0FFh, 0, 0, 0, 92h, 8Fh,  0    ; 20h:  4GB DATA
      8 
      9 
     10         ; (省略)
     11 
     12                 ; GDT 初期化コード
     13         xor     EAX,EAX
     14         mov     AX,INIT32
     15         shl     EAX,4
     16         mov     [word ptr GDT08+2],AX
     17         mov     [word ptr GDT10+2],AX
     18         shr     EAX,16
     19         mov     [GDT08+4],AL
     20         mov     [GDT10+4],AL
     21         mov     [GDT08+7],AH
     22         mov     [GDT10+7],AH
     23 
     24         ; (省略)
     25 
     26 
     27 segment INIT32 use32 para public 'CODE'
     28         assume  CS:INIT32, DS:DGROUP
     29 
     30 proc    switch32 far
     31 
     32         switchProtectedMode
     33 
     34         db      66h, 0EAh       ; jmp far ptr _32bit_label0
     35         dd      offset _32bit_label0    ; オフセット
     36         dw      10h                     ; セレクタ 10h
     37 _32bit_label0:  ; now you are in 32-bit segment.
     38 
     39         switchRealMode
     40 
     41         db      0EAh            ; jmp far ptr _32bit_label1
     42         dd      offset _32bit_label1    ; オフセット
     43         dw      seg _32bit_label1       ; セグメント
     44 _32bit_label1:
     45 
     46         ret             ; 32-bit far リターン
     47 
     48 endp    switch32
     49 
     50 
     51 proc    switch16 far
     52 
     53         switchProtectedMode
     54 
     55         db      0EAh            ; jmp far ptr _16bit_label0
     56         dd      offset _16bit_label0    ; オフセット
     57         dw      08h                     ; セレクタ 08h
     58 _16bit_label0:  ; now you are in 16bit-segment.
     59 
     60         switchRealMode
     61 
     62         db      66h, 0EAh       ; jmp far ptr _16bit_label1
     63         dd      offset _16bit_label1    ; オフセット
     64         dw      seg _16bit_label1       ; セグメント
     65 _16bit_label1:
     66 
     67         db      66h     ; 32-bit far リターン
     68         ret
     69 
     70 endp    switch16
     71 
     72 ends    INIT32
    ------------------------------------------------------------------------
    
     例によって GDT はすでに初期化済みデータセグメントの中にあります。こ のうちセレクタ 08h と 10h がコードセグメントですが、このままでは不完全 なので、13 行目から書かれている初期化コードをあらかじめ実行して、セグ メントのベースを INIT32 のセグメントアドレスの 16 倍にあわせておきます。
     プロシージャ switch32 も switch16 も far として宣言されており、プロ シージャは INIT32 とは別のセグメントから呼ばれることを前提としてます。
     セグメント INIT32 は use32 指定があるので、CS の D bit=1 である 32-bit コードセグメントとしてアセンブラに解釈されています。しかし、呼 び出し側は、16-bit, 32-bit 両方の可能性があります。
     プロテクトモードでは、far コールによってセレクタを更新するときには、 D bit もディスクリプタにしたがって適切に更新されます。しかしリアルモー ドにおいては、セレクタの更新で更新されるのはセグメントのベースアドレス だけですから、このプロシージャに制御を移すときにも、呼び出し側の CS の D bit の状態は保たれたままということに注意してください。つまり、ソース の記述と実行時の状態がかみ合わなくなってしまうのです。
     switch32 を呼ぶのは CS の D bit=0 のとき、switch16 を呼ぶのは CS の D bit=1 のときのみですから、66h, 67h をうまく配置して、この違いを調整 しなくてはなりません。
     ここで switchProtectedMode と switchRealMode というマクロが出てきま が、これはひとつ前のサンプルで出てきたものとまったく変わりがありません。 アセンブルしてみればわかりますが、このマクロは、コードセグメントが 16-bit であろうと 32-bit であろうとアセンブラはまったく同じコードを生 成しますので、CS の D bit がどういう状態でも共通に使うことができます。
     switch16, switch32 とも、まず switchProtectedMode でプロテクトモード に移行した後、目的のセレクタをロードするためにセグメント間ジャンプを行 います。プロテクトモードでのセグメント間ジャンプを TASM が理解できるよ うに表現する方法はないので、このようにコードを db 命令で直接並べる必要 があります。最初の行のコメントにあるのが、このコード列の意味です。この far ジャンプによって、CS の D bit を 1 にしたり、0 にしたりすることが できます。
     続いて switchRealMode ですぐにリアルモードに戻った後、不正なセレクタ 値を正すために再びセグメント間ジャンプを行います。ここでも db 命令を並 べていますが、これは普通のリアルモードの far ジャンプですので、普通に jmp 命令で同じことを書くことはできます。
    この 2 番目のセグメント間ジャンプによってセレクタは正しい値がロード されます。D bit はそのままです。これで、CS の D bit だけを変更すること ができたことがわかるでしょう。

     このルーチンを利用して 32-bit コードセグメントを実行できます。

    ________________________________________________________________________
      1         ideal
      2         p386
      3         model   use32 small, c
      4 
      5         codeseg                 ; 32-bit セグメント
      6 
      7 proc    main    far
      8 
      9         ; (省略)
     10 
     11         ret     ; 自動的に far リターン
     12 endp    main
     13 
     14 
     15 
     16 segment INIT16 use16 byte public 'code'
     17         assume  CS:INIT16, DS:DGROUP
     18 
     19 entry:
     20         ; (省略)
     21 
     22         call    switch32
     23         db      09Ah            ; call far ptr main
     24         dd      offset main     ; オフセット
     25         dw      seg main        ; セグメント
     26         db      09Ah            ; call far ptr switch16
     27         dd      offset switch16 ; オフセット
     28         dw      seg switch16    ; セグメント
     29 
     30         ; (省略)
     31 
     32         mov     AX,4C00h
     33         int     21h
     34 
     35 ends    INIT16
     36 
     37         end     entry
     38 
    ------------------------------------------------------------------------
    
     INIT16 から INIT32 のルーチンをコールするのに call switch32 としか書 かれていませんが、これは TASM が自動的に 32-bit セグメントへの far コー ルであることを認識するので、これだけで 66h プリフィクスつきの far コー ルを生成してくれます。
     次はメインルーチンのコールです。ただのリアルモードのコールなのにいち いち db でコードを並べていますが、これはさきほどの TASM の便利な機能が 裏目に出て、いらないところに 66h を付けてしまうからなのです。call switch32 から帰ってきたときには、CS の D bit=1 なのですから、66h はい りません。
     その次の switch16 プロシージャの呼び出しも同じです。これで D bit=0 に戻り、普通にプログラムを終了することができます。

    【割り込み】

     32-bit コードセグメント実行中に一番気を付けなくてはならないのは割り 込みです。このモードで割り込みが生じるときには、たとえ 32-bit セグメン トで実行していても、リアルモードと同じ 16-bit の割り込みが発生します。 つまり、FLAGS, CS, IP をスタックに待避し、リニアアドレス 0h から始まる 割り込みベクタテーブルから 16-bit far ポインタをロードして CS:IP にセッ トします。これがどういう問題を引き起こすのかというと、CS:IP しか変更さ れないので、EIP の上位 16 ビットがすべて 0 でない場合は、正しく割り込 み処理ルーチンにジャンプしない、ということなのです。
    これは結局、割り込みが起きる可能性のある状態では、CS は 64kb の壁を 越えることはできない、ということを意味します。もっとも、このモードで作 られるプログラムというのはそれほどコード領域を消費するとは思えませんか ら(small モデルでは収まらないという話を聞きますが、それはたいていは図 体の大きい高級言語ライブラリを使っているからです)、あまり大きな問題に はならないと思われます。どうしても足りない場合は、昔ながらの方法でコー ドを複数のセグメントに分割することになります。
    また、例によって割り込みプログラムも CS の D bit は変更されずにその まま実行されてしまいます。したがって、32-bit セグメントのプログラムを 実行する場合、リニアアドレス 0h にある既存の割り込みベクタテーブルは、 そのベクタの指す割り込みハンドラはみな 16-bit セグメントのものでしょう から、そのまま流用することはできません。したがって、32-bit セグメント 用に書かれた割り込みハンドラを指すように書き換える必要があります。しか し 1Kb の領域を待避して書き換えるというのも大変ですので、ここでは lidt 命令を使うと、割り込みベクタテーブルの位置を移動できるという事実を使う といいと思います。

    【16-bit ルーチン の活用】

     BIOS や MS-DOS の int 21h ファンクションは 16-bit セグメント用にでき ているので、当然のことながら 32-bit セグメントで実行されるプログラムか らは利用できません。そこで、画面表示からなにからすべてのルーチンを自分 で作る必要があります。
     しかし、さきほどの D bit 切り替えルーチンを利用すると、16-bit のサブ ルーチンも呼び出すことが可能になります。次のプログラムを見てください。

    ________________________________________________________________________
      1 segment INIT32 use32 para public 'CODE'
      2         assume  CS:INIT32, DS:DGROUP
      3 
      4 proc    int16bit far
      5 
      6         pushfd
      7         cli
      8         push    EAX
      9         mov     EAX,[ESP+16]
     10         mov     [intno],AL
     11         lidt    [IDT16ptr]
     12         call    switch16            ; 同じセグメントに far コール
     13         db 66h
     14         pop     EAX
     15         db 66h
     16         popfd
     17 
     18         db      0CDh    ; int
     19 intno   db      ?
     20 
     21         db 66h
     22         pushfd
     23         cli
     24         db 66h
     25         push    EAX
     26         push    CS                  ; 同じセグメントに far コール
     27         db 66h
     28         call    near ptr switch32
     29         lidt    [IDT32ptr]
     30         pop     EAX
     31         popfd
     32         ret     ; 自動的に far リターン
     33 
     34 endp    int16bit
     35 
     36 ends    INIT32
    ------------------------------------------------------------------------
    
     この int16bit プロシージャは、割り込みを起こしたいベクタ番号をスタッ クにプッシュしてコールすると、16-bit セグメントに切り替えて、その割り 込みを起こしてくれるものです。スタックの幅は 4 bytes でこと、また far プロシージャであり、D bit=1 を前提にしていることに気を付けて読んでくだ さい。
     プロシージャが呼ばれると、まず lidt 命令を用いて割り込みベクタテーブ ルをリアルモード用に切り替えた後、16-bit セグメントに切り替えます。
     int 命令のオペランドは即値しか受け付けないので、このようにコード書き 換えを行って希望するベクタ番号の割り込みを起こしています。このテクニッ クは C の geninterrupt 関数などで一般的に使われているものです。
     割り込みから帰ってきたら、割り込みベクタテーブルを元に戻し、32-bit セグメントに切り替え、ベクタテーブルを 32-bit 用にした後、終了します。

     このルーチンを使えば、いくつかの制限つきながら、実際に int 21h によ る DOS ファンクションを呼び出すことができます。呼び出すときには、アド レスサイズは 16-bit でなくてはならないことを常に気を付けなくてはなりま せん。たとえば、ファンクション 09h で 32-bit データセグメントにある文 字列 msg を標準出力に書き出す場合、

            mov     EDX,offset msg
            mov     AH,9
            push    21h
            call    int16bit
            add     ESP,4
    
    と書いてもよいのですが、この場合は msg はセグメントの始め 64Kb の部分 になくてはなりません。

     この制限を満たせない場合や、リニアアドレス 10FFF0h 以上の領域を扱い たい場合は、そのままコールすることはできません。この状況、特に後者は、 プロテクトメモリに確保したワークエリアにファイルを読み込みたいときなど、 往々にして生じます。この場合は、適当な領域を用いてバッファリングをする 必要があります。面倒ですが、たいして難しいプログラムでもないので、すぐ に書けることでしょう。
     この問題を完全に解決したのがいわゆる DOS エクステンダと呼ばれるもの で、int 21h ファンクションも違和感なく 32-bit セグメントから呼び出せる ようになっています。完璧な物を作ろうとするとなかなか面倒です。

    【活用例】

     実際に僕はこの 386 の動作モードを使って、PC-9801 Ap の 256 色グラフィッ クモードで 3D ポリゴングラフィックスを行うプログラムを書いています。昨 年の駒場祭で作った 4 人対戦戦車ゲームも 3D ポリゴンでしたが、あれは完 全にリアルモードのプログラムで、プリフィクスの嵐でとても遅い上に、64Kb の壁のため、セグメントオーバライドや、ポリゴンのワークエリアの確保など に非常に苦労していました。
     でもこのモードでは状況はいっきに好転します。セグメントリミットを 4Gb に設定し、VRAM を含むすべてのデータを DS を使って基準にアクセスできる ようにしました。これによってすべての領域をセグメントオーバライドを必要 することなくアクセスできます。また、CS の D bit も 1 にしてあるので、 32-bit コードが何のオーバーヘッドもなく実行できます。
     256 色 VRAM はリニアアドレス 0F00000h からパックドピクセル VRAM とし てアクセスすることができます。また Ap2/As2 以前の機種では、リニアアド レス 0A8000h から始まるウィンドウを通して、EGC を使った 8 枚同時プレー ンアクセスも可能となります。これらの領域をセグメントレジスタの変更をす ることなく透過的にアクセスすることができるので、高速なゲームが作れそう です。
     メモリ管理は、1Mb のひとかたまりのブロックを確保して、そこから自分で 書いたヒープ管理ルーチンで個々に割り振るようにしています。この 1Mb の ブロックは、デフォルトではリニアアドレス 100000h から始まる 1Mb を確保 するようにしていますが、XMS ドライバとは共存が可能なので、XMS ドライバ がいるときはこの 1Mb は XMS ドライバからもらうようにしています。
     今後の課題としては、やはり仮想 86 モードで実行できないというのは不便 なので、VCPI/DPMI に対応してすべての動作モードから実行できるようにする ことでしょうか。幸い日本語で VCPI/DPMI 規格を説明している本が発売され たので、苦労して英語のドキュメントを読む必要はなさそうです。

    【参考書】

     まず、オーム社の『80386 の使い方』と『80486 の使い方』という本。この 2 冊は書いていることはほとんど同じですので、どちらか 1 冊があればいい でしょう。この本はほんとに 386/486 の使い方だけが、過不足なく書かれて いるので、具体的なプログラムの書き方を知りたい人には不向きかもしれませ ん。もっとも、すでにあらかた理解している人にとってはこのくらいの薄さの ほうがかえって便利でしょう。

     アスキー『はじめて読む 486』というのは僕が一番最初に買った本です。 486 なんて書いてありますが 386 でも Pentium でも変わりなく使えます。わ かりやすく書いてありますが、そのぶん分厚くて説明がくどすぎるというのが 難点です。タスク切り替え・ページング・仮想 8086 モードなど、486 の各機 能について動作確認のためのサンプルプログラム(IBM PC/98 に対応)が付属し ており、なかなか参考になります。ただしディスクはついているわけではない ので、試してみたかったら自分で長いリストを打ち込むかディスケットサービ スを申し込まなくてはなりません。

     『X86 プロテクトモード・プログラミング』という本が CQ 出版社から 3 月 23 日に出ました。IBM PC/98 でのプロテクトモードプログラムの書き方、 VCPI/DPMI の扱い方など、網羅的に説明しています。さらにサンプルも豊富で、 ディスクもついています。プロテクトモードで動くプログラムを自分で書きた いというひとは、とりあえずこれ 1 冊あればいいでしょう。

     本ではありませんが、TSG にはプロテクトモードプログラミングについて相 談に乗れる人が結構います。自力でプロテクトモードを活用する方法や、様々 な小手先のテクニックについては、僕や Applause 君に聞くといいでしょう。

    【最後に】

     なんか、間違いがたくさんありそうな気がします。理解できないところは直 接僕に質問してください。見つかった間違いは次の部報で訂正することにしま す。

    1.Apr.'95 (Sat) 00:36:16
    Written by Zephyr


    編集後記
     春休みは実家に逃げて車を乗り回していたため原稿を書けませんでした。 ごめんなさい。最近のパソコンの値段の安さには目をみはるばかりです。大学 に入って何か新しいことをやってみようと思ってるあなた、大学デビューのス ポーツはみっともないけどパソコンは全然 Ok だよ。これを機に始めてみては いかが。(しげ)

     初めて原稿を書くのでちょっと緊張して、なんだか堅い文章になってし まいました。初心者にも解りやすかったのだろうか ? うーん。
     みなさん、TSG のホームページに載せる情報を募集しています。何か適当な のがあったら、教えてください。お願いします。(GANA)

     ふう。めちゃくちゃな C 言語入門書をこんなにも長く書いてしまいま した。長さだけなら不動の最長新記録となることでしょう。でも全部読んでく れる人はいるのかなあ。わかりやすく書いたつもりだからみんな読んでね。
     TSG の部員は学生会館 305 にいつもいます。気軽に遊びに来てください。 (NAO)

     いやいや、今回は参りました。せいぜい 50 ページ + αだと思っての ほほんとしていたのですが、編集作業が進むにつれて、80 ページを突破する ことが判明っ ! 紙が足りないっ ! いつもなら原稿が多ければ多いほど幸せに なれる編集長の私ですが、今回ばかりは真っ青になってしまいました (苦笑)。 文字の大きさを小さくして、やっとの思いで乗り切ることができました。
     TSG は、東大を代表するコンピューターサークルです。全国的に有名なフリー ソフトウェアの作者も大勢います。「平安京エイリアン」や、98 版 dviout (TeX 用プリンタドライバ) を作ったのも、TSG です。もちろん初心者も、ワ イワイガヤガヤと楽しくやっています。
     気が向いたら、ぜひ遊びに来てください。部員一同、心よりお待ちしており ます。(ちょもらんま 編集長)


    理論科学グループ部報 TSG 第 189 号
    平成 7 年 4 月 10 日 初版 第 1 刷発行

    発行者 渡辺 尚貴
    編集者 安田 知弘
    HTML化 多賀 奈由太

    発行所 理論科学グループ
    〒153 東京都目黒区駒場 3-8-1 東京大学教養学部内学生会館 305 TEL 03-5454-4343


    (C)Theoretical Science Group, University of Tokyo, 1995 Printed in Japan.
    g541119@komaba.ecc.u-tokyo.ac.jp