〜最高の言語仕様について考える(嘘)〜
2002年3月24日
Leshade Entis
 

■ 初めに光ありき −初めに−

 コンピュータは遥か太古、神話時代の物を除き、すべてノイマン型と呼ばれる方式をベースに構築されています。これは、プログラムもデータもメモリに置くと言う方式です。ここで初めてプログラムと言うものが定義されます。
 神話時代のコンピュータのプログラムは結線論理(ワイヤード・ロジック)でした(更にそれ以前は色々です)。これは、分かりやすく言えば、「プログラムはハードで、データはソフト」ということです。当然、現在のコンピュータでは、「プログラムもデータもソフト」です。ROM に焼き付けられたプログラムはハードではないかと勘違いする方も大勢いるかと思いますので、少し補足しますが、ROM に焼き付けられているのは「処理をする」回路ではなく、「データを記録した」回路なのです。
 結線論理のプログラムというのは、ファミコンに例えて言うなら、ファミコン本体はただの空箱で、カセットの中にゲームを処理する回路が入っているようなものなのです。
 もはや ROM の主流はディスクになってしまったので、勘違いはしないとは思いますが、中央演算処理装置(CPU)などのハードがあって、プログラムやデータを差し替えるだけで、いろいろなことが出来るようになりましたが、これはノイマン型コンピュータだから出来ることなのです。
 さて、余談はこの程度にしておきましょう。
 すっかり本題からそれている気がします。(汗)
 ここで、コンピュータ・プログラムと言うものが出てくるわけです。
 すべてのコンピュータは、プログラムの指示によって動作しています。このプログラムのコーディング手法(書き方)は、太古からさまざまな変遷を経て現在に至っているわけですが、現在主流のプログラミング言語について見ていきながら、最高の言語とは一体どんな言語なのかと言うことを考えてみたいと思います。
 

■ 神聖文字と神聖言語 −機械語とニーモニック−

 コンピュータと言うのは、「0」と「1」の組み合わせで処理を実現します。
 データはもちろん、プログラムも「0」と「1」の組み合わせです。
 「0」と「1」だけの表現方法を2進法と言いますが、やたら冗長なので普通は16進数を使います。「0」と「1」を4つ組み合わせて(4ビットで)1つの文字で表現します。
 すると1バイト(8ビット)は2桁の16進数となります。
 この数値をコンピュータは命令として受け取り、処理を実行するわけですが、到底普通の人間には理解できる表記ではありません。
 これを機械語(マシン語)と言いますが、これを理解できたのは、神からの信託を受け、かなりの荒行をかいくぐってきた上級神官だけでした。
 しかしさすがにこれではあまりにも難解すぎるため、通常は1つの機械語を1つのニーモニックと呼ばれる記号に置き換える手法が取られていました。ニーモニックと言うのは、機械語がコンピュータにどういう指令を出すのかと言うことを、人間が直感的に理解しやすように割り当てられた記号です。
 そして、ニーモニックによって記述する言語体系をアセンブリ言語(アセンブラ)と言います。機械語とアセンブリ言語はきわめて近い関係にあり、他のどの言語とも記述の方法が随分異なります。
 

■ 宣教師 −コンパイラとインタプリタ−

 コンピュータの発達と共に、プログラムの記述の方法も進化を続けていました。
 最も根本的なことに、アセンブリ言語では数式の表現が極めて難解だと言う問題がありました。そこで、こういった表現を人間が普通に使っている数式で記述できるように改良されていったと言うことは必然でしょう。
 当然のことながら、コンピュータは今も昔もマシン語しか理解できませんから、人間の使っている表現をマシン語に翻訳すると言う作業が必要となります。この作業を行うプログラムがコンパイラと呼ばれるもので、あらかじめ翻訳することによってプログラムを作る言語のことをコンパイラ言語と言います。
 このコンパイラ言語とは別に、逐一翻訳する方式も存在しました。この逐一翻訳して実行するプログラムのことをインタプリタと言い、そのような言語のことをインタプリタ言語と言います。
 このコンパイラとインタプリタの登場によってコンピュータは飛躍的に使い勝手の良いものとなりました。現在のコンピュータ言語は、アセンブラ言語以外、すべてコンパイラ言語かインタプリタ言語に分類されます。
 

■ 邪悪な言語 −C言語−

 かなり昔から存在し、現在最もよく使われているのがC言語でしょう。
 C言語は、現存する高級言語の中では最もアセンブラに近い言語ですが、言語仕様が定められたのが古いため(そしてそれからあまり変わっていないため)かなり邪悪です。
 C言語は、UNIXの普及に大きく貢献しましたが、その言語仕様は間接的にB言語から来ていると言われており(直接的にはBCPL言語から来ていると考えられる)、かなり古いものです。
 そして恐らくは当時有用であった仕様が、現在では邪悪になっているとしか言いようがありません。詰まるところ、私はC言語が昔から嫌いなのです。
 ここでは、C言語が邪悪である理由をいくつか挙げましょう。

▼ 移植性と汎用性
 C言語は、アセンブリの様な柔軟な記述が可能である上に、ニーモニックを使用しないため CPU の種類に依存せず、どのようなコンピュータへも移植可能なプログラムを記述することが出来る、と言うのは幻想でしかありません。
 最終的には、ワードサイズ(いわゆる int 型のサイズやポインタ型のサイズ)や、アラインメント、エンディアンなど、CPU の種類に依存してしまいます(それ以外にも多数、CPU の種類に依存することがあります)。
 しかも、プラットフォームへ依存するコードは、同じ CPU でも移植性の低いコードとなってしまいます。

▼ 半端なオペレーター
 C言語は、柔軟な記述を可能にするために、インクリメントデクリメントシフト演算子と言う、奇妙怪奇極まりない演算子が存在します。しかも、シフト演算はただの算術シフトのみであり、今となっては乗除算と同じ事です。これらの置き換えは現在のコンパイラでは最適化によって自動的に行われるのが普通で、この存在は邪悪としか言いようがありません。
 しかも、キャリーローテーションなどを扱う演算子は存在せず、ビット処理で最も必要とされるような記述がまったくスマートに出来ません。

▼ 古典的な擬似命令
 C言語は、基本的に古典的な擬似命令しかサポートしていません。
 #define, #if, #else, #elif, #endif です。これらは、今となっては擬似命令としてはまったく不足しており、また、#define などは、もはや使い道がありません。
 もちろん昔は #define にはかなり重要な用途があったことは想像に難くないのですが、今となっては、簡単な数式を置き換える程度しか使い道はありません(※)。しかもそれは、C++言語によって、よりスマートに記述できるようになってしまっているのが実情です。

※ C言語において、テキストマクロは、定数の置き換えなどに良く使われますが、最近では型を伴うグローバル変数にする方が良いとされています。

▼ ポインタと文字列
 メモリ管理と、文字列の取り扱いについては、C言語は最悪の言語だと言わざるを得ません。
 まずメモリ管理ですが、malloc 関数などで確保したメモリは、不必要になれば free 関数で解放しなければなりませんが、この解放を忘れることを防ぐ言語仕様的な仕組みは一切存在しません。
 また、文字列は文字型の配列で表現しなければならず、(受け渡しやメモリの管理など)非常に取り扱いが厄介です。
 こんな最悪の言語は他には存在しません。

 

■ 邪神の息子 −Java−

 Java は半コンパイラ言語で、コンパイルはしますが出力するのは中間コードです。中間コードは当然そのまま実行は出来ませんから、インタプリタによって実行されます。
 Java は、C言語をベースとした言語仕様で、しかも、出力するのは中間コードですからC言語の利点がその時点で失われています。
 もちろん、CPU への非依存性は重要なポイントですが、インタプリタとしての利点を十分発揮できていない言語仕様であり、C言語以上に邪悪です。
 

■ 基本ソフト、基本言語 −BASIC−

 私がはじめて触れたコンピュータ言語は、Hu-BASIC でした。
 この世代のパソコンでは、BASIC 言語が、基本ソフト(OS)でもありました。
 現在の BASIC は、基本的に OS の上で走るただのインタプリタ、またはコンパイラでしかありませんが、昔の BASIC は対話式で、ファイルシステム、ラインエディタ、インタプリタ、グラフィック出力及び入力インターフェースを供給するプラットフォームが一体となったようなものでした。(更に機械語モニタが付属しているのはまったく珍しくないことでしょう)
 BASIC は昔から手軽なプログラミングが可能な高級言語でした。
 もちろん昔の BASIC には、構造化をサポートするような言語的仕組みはありませんし、オブジェクト化指向では当然ありませんから、動的な型の概念も当然ありません。
 しかし、手軽という点では、BASIC を超えるものには未だに出会っていません。
 私が未だに愛用しているポケコンには、BASIC、C言語、CASL、Z80アセンブラの機能が付属していますが(それとラインエディタ)、BASIC の RUN MODE は大変に便利です(因みに RUN MODE と PROGRAM MODE が別になっているのはメモリ空間の関係だと思いますが)。
 また、現在世界で最もよく使われている BASIC 言語であろう VBScript ですが、やはり手軽さと言う観点では優れていると言えるでしょう。
 

■ マクロな嗜好 −C++言語−
 C言語では、構造化プログラミングと言うコーディング手法を言語仕様によってサポートしています。
 構造化とは、プログラムやデータに構造を与えることによって、プログラムやデータを管理しやすくするためのものです。
 しかし、先ほど述べましたとおりC言語はかなり邪悪ですので、これを解消するべく改良が加えられました。そうして出来たのがC++言語です。
 C言語とC++言語は、名前はよく似ていますが、まったく似て非なる存在です。
 基本的には、C言語に、オブジェクト化指向プログラミングの手法を言語仕様レベルでサポートした形になりますが、結果、まったく違う言語として生まれ変わったといっても良いでしょう。
 C++言語が優れている点について、いくつか挙げてみましょう。

▼ インライン関数
 インライン関数は、関数の呼び出しを記述した場所に直接関数の実装を展開する関数のことです。
 これによって、#define を使うのは、#if で利用する以外、ほとんど意味をなさなくなりました。

▼ テンプレート
 テンプレートとは、型や即値を特定しないで、関数やクラスを定義する機能のことです。
 これによってコンパイラ言語でありながら、非常に高級な記述が可能になりました。

▼ メモリ管理の隠蔽
 オブジェクト化指向プログラミングの言語仕様を利用することによって、メモリ管理を隠蔽することが可能になり、メモリの解放忘れをコンパイラレベルで防ぐことが可能になりました。

▼ 文字列を初めとする抽象表現
 オブジェクト化指向プログラミングを採用することにより、文字列を BASIC 並に平易に扱うことが出来るようになりました。

 

■ 最後の楽園 −MASM6−

 私が現在こよなく愛しているのが、MASM6.11 です。
 MASM とは、マイクロソフト社製のアセンブリ言語で、マクロアセンブラの略称です。
 MASM は、バージョン5を境にかなりの擬似命令に変更が加えられ、バージョン6では私の知る限りの中で、最もマクロ機能が充実した言語になりました。
 MASM はアセンブリ言語ですから、当然、ニーモニックによってプログラムを記述します。これは、マシン語1つ1つを人間の手によって記述しなければならないことを意味していますが、実際には高度な擬似命令を初めとするマクロ機能によって、C言語以上に高級な記述が可能なのです。
 そして同時に、ニーモニックを利用して、柔軟なビット処理や、SIMD 命令などを利用した高速なプログラミングが可能なのです。
 では、MASM6 がどの程度高級な記述が可能なのか、いくつか例を上げながら見ていきましょう。

▼ 構造化プログラミング
 MASM は、構造化プログラミングが可能です。
 例えば、オブジェクト a のメンバ data1 を、オブジェクト b のメンバ data2 へコピーするプログラムは以下のようになります。

mov     eax    , a.data1
mov     b.data2, eax
 また、関数呼び出しも簡単です。C言語の記述で、func( &a, b ) に相当する関数呼び出しは、以下のような記述になります。
INVOKE  func , ADDR a, b
 
▼ 制御移行
 MASM は、入れ子状の条件分岐(if)、入れ子状の反復文(while文, repeat文)が使えます。
 例えば、次のようなプログラミングが可能です。
.IF  (ecx != 0) && (esi != 0) && (edi != 0)
     .REPEAT
          mov     eax, DWORD PTR [esi]
          add     esi, SIZEOF DWORD
          mov     DWORD PTR [edi], eax
          add     edi, SIZEOF DWORD
          dec     ecx
     .UNTIL ZERO?
.ENDIF
 また、アセンブリの特徴を生かし、break, continue 文は次のような記述が可能です。
.BREAK .IF  CARRY?
 これは、キャリーが発生したら最も内側の反復ブロックから抜けるとこと意味しています。
 

▼ 関数
 MASM は、関数をプロトタイプ付きで記述することが可能です。
 しかも、エピローグコードプロローグコードをあらかじめマクロで定義しておけば、関数の初期化と終了処理を独自に定義することも出来ます。
 もちろん、型付のローカル変数の定義も可能です。
 

▼ 反復展開
 MASM には、数種類の反復展開マクロが用意されています。
 例えば、次のような記述が可能です。

FOR     MEMBER, <data1, data2, data3>
        mov  eax         , [esi].MEMBER
        mov  [edi].MEMBER, eax
ENDM
 これは、ESI レジスタでポイントされるオブジェクトから、EDI レジスタでポイントされるオブジェクトへ、メンバ変数 data1, data2, data3 をコピーするものです。
 

▼ マクロ関数
 MASM には、マクロ関数の機能があります。
 マクロ関数とは、何かのリテラルを生成する、関数のようなマクロのことです。
 例えば、次のマクロ関数 factorial は、n に対する階乗を返します。

factorial     MACRO   n:REQ
        LOCAL i, r
        i = 1
        r = 1
        REPEAT  n
                r = r * i
                i = i + 1
        ENDM
        EXITM   %r
ENDM
 このように定義しておけば、例えば、
mov     eax, factorial(4)
と、すぐにレジスタに階乗の値をロードできますし、
F_TABLE LABEL DWORD
        i = 1
        REPEAT 10
                DWORD factorial(i)
                i = i + 1
        ENDM
と、F_TABLE という名前の、1から10までの階乗を格納したテーブルを生成することも出来ます。
 

▼ その他、高度なマクロ機能
 MASM は、他にも高度なマクロ機能を備えています。
 例えば、次のマクロ load32 は、フレキシブルに値をレジスタにロードします。

load32  MACRO   reg:REQ, src:REQ
        IF  (OPATTR(src)) AND 00010000B
                IFDIFI  reg, src
                        mov     reg, src
                ENDIF
        ELSEIF (OPATTR(src)) AND 00000100B
                IF      src
                        mov     reg, src
                ELSE
                        xor     reg, reg
                ENDIF
        ELSEIF (TYPE(src) EQ BYTE) OR (TYPE(src) EQ WORD)
                movzx   reg, src
        ELSEIF (TYPE(src) EQ SBYTE) OR (TYPE(src) EQ SWORD)
                movsx   reg, src
        ELSEIF (TYPE(src) EQ DWORD) OR (TYPE(src) EQ SDWORD)
                mov     reg, src
        ELSE
                .ERR
        ENDIF
ENDM
 各条件ごとに、どのようにマクロが展開されるのか見てみましょう。
 
記述
出力コード
説明
load32  ecx, ecx   同じレジスタ同士では、コピーを行いません
load32  ecx, edx mov     ecx, edx 異なるレジスタ同士では、値をコピーします
load32  edx, 5 mov     edx, 5 即値をレジスタに設定します
load32  edx, 0 xor     edx, edx ゼロをレジスタに設定します
load32  eax, BYTE PTR [esi] movzx   eax, BYTE PTR [esi] 符号なし8ビットの変数をレジスタにロードします
load32  ebx, SWORD PTR [esi] movsx   ebx, SWORD PTR [esi] 符号あり16ビットの変数をレジスタにロードします
load32  ecx, DWORD PTR [esi] mov     ecx, DWORD PTR [esi] 符号なし32ビットの変数をレジスタにロードします

 他にも、MASM のマクロ機能を活用する例は枚挙に遑が無いので、とりあえずこれだけにしておきますが、高度でなおかつ繊細な記述が可能であることは分かっていただけたと思います。

 

■ 地を這う者 −プログラミング言語の選択−

 プログラムを記述するプログラマには、普通次の二つの要件が突きつけられます。
 1つは、プログラムの処理速度などの、性能に関するもの。
 もう1つは、プログラムそのものの生産性、つまり、プログラムを記述するのに要する時間(の短さ)です。
 普通これらの2つは相反しますが、最近では CPU の性能が飛躍的に向上し、前者が問題になるようなプログラマはほんの一握りでしょう。ですから大抵のプログラマであれば、Visual Basic 等の高級言語の中でも高級な言語を選択するのがベストでしょう。
 しかし、プログラムの処理速度が一部でも必要となる場合、Visual Basic の選択はありえません。と言うのも、速度を必要とする部分のみ C 言語やアセンブラで記述するとしても、親和性があまり無いからです。
 そのような場合には、C++ を初めから選択しておくのが良いでしょう。最近の C++ 言語には、プラットフォームに対する十分なライブラリが用意されており、普通のアプリケーションを構築する上では、Visual Basic と何ら変わりありません。
 私の場合、ターゲットをパソコンに限っていることが多く、その場合、CPU はIA32互換、OS は Win32 と限定しても、ターゲットはほとんど狭まることは無いため、基本的にはこれにチューニングしたプログラムを記述することにしています。
 ERINA-Library を見れば分かるように、Win32 環境であれば、汎用な C++ コードがあるため、CPU、コンパイラは問いません。そして、主要な C++ コンパイラである、MS-C++、及び Borland C++ に対しては、アセンブリを提供しています。
 なんと言っても、醍醐味は、クラスのメンバ関数を直接アセンブラで記述することです。この行為はコンパイラさえも限定してしまいますが、高速なプログラムを記述する上ではかなり有用です。
 少なくとも、MASM を使ってコーディングを行う場合、プログラムの生産性と処理速度はある程度両立可能ですから、これほど良いことはありません。もちろん、すべてを MASM で記述するのはさすがに大変ですので、大して重要でない部分は C++ で記述しておくのが得策でしょう。
 

■ 万能の知恵 −用語解説−

 用語について、補足します。


▼ 神話時代
 真空管より前の時代のこと(ここでは、便宜的にこう定義している)。
 機械式のコンピュータは19世紀に発明されたとされるが、古代エジプトにも同様の機械式コンピュータは存在した。日本風に言えば、「からくり」のようなものである。
 その後、機械式の部分をリレー回路に置き換えたものが登場し、更にそれを真空管、そして半導体へと置き換えることになる。

▼ 高級言語
 アセンブラ以外のコンピュータ言語のこと。
 ただし、C言語を低級言語と呼ぶ場合もある。

▼ ラインエディタ
 エディタとは、文章を記述するためのツールである。
 文章には、プレーンテキスト、リッチテキスト、ハイパーテキストなどさまざまあるが、プログラムはプレーンテキストで記述する。
 ラインエディタとは、最も原始的なエディタで、対話式のコマンド入力によってプレーンテキストを編集するプログラムである。

▼ SIMD 命令
 Single Instruction Multi Data の略。
 1つの命令で、複数のデータを処理するタイプの命令のことを指す。
 IA32 では、MMX や SSE などがそれにあたるが、いまいち活用の幅が狭い。

▼ 擬似命令(ディレクティブ)
 擬似命令とは偽者の命令と言う意味ではない。
 コンパイラやアセンブラに対する命令のことを指す。
 普通、プログラムに書かれているのは、そのプログラムが実行された場合にどのような命令を出すかであるが、擬似命令では、どのようにコンパイル(アセンブル)するかを指示する。
 C言語では、複数の CPU や OS へ、1つのプログラムソースで対応するために擬似命令をが活用されている。(それ以外の利用方法はほとんど無い)

▼ IA32
 インテル・アーキテクチャ32の略。
 いわゆる、86系と呼ばれるCPUの事を指している。