Yoshihiko Hyodo 6f06b9514a first commit
2024-11-18 22:21:26 +09:00

9.0 KiB
Raw Blame History

楽しいプログラミング 《第2回》

相変わらずCにさわれません。今年一杯は無理かなぁ。あぁ、早く思う存分Cのプログラムを書きまくりたい・・と思いつつ、今日もアセンブラでしこしこデバッグです。もしかして、もうCが書けない体になってしまっているのでは・・。

さて今月は、ずっと以前にCを使っていた頃常々感じていた、Cの不満な点を、いくつか考えてみましょう。だからといってどう成るものでも無いのですが・・。ちなみに、予告では「CC++言語の不満な点」となっていましたが、C++は、入門書を読んだっきり使わないうちに、あらかた忘れてしまいましたので、今回は言及しません。でも、extern"C" はヤだなぁ)  むろん不満といっても、ライブラリや生成コードに関するものではありません。個人プログラマにとって、標準ライブラリなんて在って無きが如くですし、生成コードに不満があるくらいなら、アセンブラで書けばよいのですから。

  1. enumの不満
  2. includeの不満
  3. externの不満
  4. シンボルスコープの不満

. enumの不満

最初にenumに出会った時は嬉しかったです。これで#defineの羅列ともお別れ。何がいいって、途中にシンボルを追加した時、《手動リナンバ》しないで済むもの。しかしそのうち、はたと気付きました。どうしてenumはint型なんだenumで定義するようなシンボル、たいていcharで表現できますよね。例えばこんな時。

enum Flag { FALSE, TRUE };
Flag x[**100**];

そうそう、これはC++風の定義でした。Cでは

typedef enum { FALSE, TRUE } Flag;

と書かなくてはなりません。余談ですが、私はつい最近までダミーの型名を省略できる事を知らず、いちいち

typedef enum _flag { FALSE, TRUE } Flag;

なんて書いてました。それはともかく・・、Flagで大きな配列や構造体のメンバを指定する場合、charで取りたい場合がよくあります。しかし、enumを使う限りintにするしかありません。これがどうしても嫌な場合には、型定義でのenumの使用はあきらめ、charを使うしかないのです。これは美しくない。前回提示した

 おきて4物の性質に即して表現する。

に反します。FALSEかTRUEしか取らない変数は、char文字ではなくFlagなのです。こんな風に定義できればよかったのですが。

enum char Flag { FALSE, TRUE };

ところで、Turbo C++は、-b-オプションで、enumのデフォルトをbyteにすることができるようです。これを知った時は小踊りして喜びましたが、よくよく考えると常にbyteでも不便です。状況によっては0255でもintにしたい。それに使ってみると、ちょっとおかしなコードを吐いている気がしたのですが・・。

ついでにもう1つ。連続したビットマスクの定義も欲しかったですね。

bitmask Keymask { K_SHIFT, K_CAPS, K_KANA, K_GRPH, K_CTRL };

といった感じの。この場合、

K_SHIFT=1, K_CAPS=2, K_KANA=4, K_GRPH=8, K_CTRL=16

となるわけです。

. includeの不満

以前、WINDOWSの解説本を読んでいた時です。いえ、どんなもんかいな、という程度の興味で。そこで、ぎょっとする記述を見つけました。

 windows.hは、サイズが88KBもある。

唖然・・。これって・・普通じゃないですよ。ほんの小さなプログラムを書く時にも、88KBものファイルをincludeするわけあの、亀が這うように遅いMSCで。とんでもない。私は嫌ですよ。そもそも、そんな羽目になる前に、どうして「これはヤバイな」と思わなかったのでしょう。ちなみに3.0では、100KBにも及ぶそうです。 そうでなくとも、include指定はうっとおしいです。とくにプロトタイプ宣言が推奨あるいはC++では強要されてからは。strcpyを使えば<string.h>を、mallocを使えば<alloc.h>か<stdlib.h>を。どうしてそんな事にいちいち気を使わなければならないのでしょう。THELPの様なオンラインヘルプがあれば、かなり手助けにはなりますが。私の場合、自分が使ってもいない関数の宣言を毎回読み込んでいると考えただけで、背筋を「ぞわぞわ」と悪寒が走るので、さっさと自分の使う関数の宣言だけをcmos.hとかにまとめてしまいます。邪道ですかね。
 これがC++になると、事態はさらに深刻です。《クラスの部品化》とかかっこいいこと言ったって、要は、寄せ集めのセット販売です。いらない物まで一切合切ぜーんぶ面倒みてちょ。この長大なヘッダーファイルを、やはりincludeして毎回コンパイルしなくてはならないのですよ。気が重くなるばかりです。そこで提案です。

  ヘッダーファイルをコンパイルしましょう。

これで全て解決です。コンパイル時に、適当な《コンパイルドヘッダーライブラリ》を参照させるように指定すればよいのです。#defineマクロの処理がどうなるか等、多少の疑問もありますが、まぁ、どうにかなるでしょう。私が作るんじゃなし。少なくとも、標準ライブラリのプロトタイプ宣言くらいはライブラリ化できそうな気がするのですが。
 ちなみに、Turbo Pascalのユニットって、これに近いものなんでしょうかだからといってPascalじゃあねぇ・・。Borlandのコンパイラは、どうもCよりPascalの方が出来が良いように思うのは、気のせいでしょうか いずれにしろ、こういった手法が導入されない限りは、本格的なC++での大規模アプリケーションの開発は、おぼつかないように思います。マシンパワーがどんどん上がって、コンパイル速度が向上すれば、大した問題ではないのかも知れませんが。

. externの不満

 グローバル変数を定義する場合、私はこんな風に書いていました。

global.h:
	#ifdef MAIN
	  int var1=1;
	#else
	  extern int var1;
	#endif
main.c:
	#define MAIN
	#include "global.h"
sub.c:
	#include "global.h"

var1の定義をmain.cに書く場合もありますが、いずれにしろvar1を2箇所に分けて記述しなくてはなりません。これは、

 おきて5重複した記述を避ける。

これに反します。C++に倣って、extern { ... }なんて書けたとしましょう。

global.h: #ifndef MAIN extern { #endif int var1=1; #ifndef MAIN } #endif

extern { ... }内の変数宣言はextern修飾とみなされ、かつ初期化指定は無視されるわけです。
 ここまで書いて、はたと気付きました。もともとextern指定で初期化してもエラーにならないのでは

extern int var1=1;

はは、通ってしまった・・。ということは、次のように書けてしまうではないか。

global.h:
	#ifdef MAIN
	  #define global
	#else
	  #define global extern
	#endif
	global int var1=1;

うむ、これは美しい。いや、まてまて。通ったはいいが、そのままリンクできてしまったぞということは、externが無効になってしまっているようだ。これでは二重定義エラーになってしまう。こりゃだめだ。というわけで、やっぱりexternには不満がありました。

. シンボルスコープの不満

これは、関数外シンボルのスコープが、ファイル内staticか、グローバルかの2通りしかない事です。このため、1つのアプリを幾つかのモジュール単位で開発しようとした場合、モジュールを複数のファイルにわけ、その中でのローカルスコープをもつようなシンボルを定義することができません。そもそも、「ファイル」という物理的な単位と、「モジュール」という論理的な単位を同一視することは、簡便ではありますが、アプリケーションの規模が大きくなった場合にはちょっと不便です。
 具体的な仕様の想定は・・、再び墓穴を掘るとまずいのでやめておきます。

そんなわけで、不満といってもこの程度。実にCには惚れ込んでいるわけです。次回は・・はは、もうネタが尽きてしまった。ひと月ゆっくり考えます。

(雑誌「PC POWER 1992年8月号」掲載