読者です 読者をやめる 読者になる 読者になる

僕がネイティブコード教からC#教に改宗したわけ

長年、C/C++プログラマをやってましたが、最近は、完全にC#にぞっこん状態です。というか、C++を捨ててC#一本で行っても良いと思えるぐらいC#が好きになりました。ここでは、その理由を自分の偏見に満ちあふれた文章で説明していこうと思います。ねつ造などはないですが、未来の話(つまりまだ起きていない出来事)を書いてある部分も多く、その信憑性は「あるある大事典」なみかもしれませんので、むやみに信用するのは止めてください。

C++の難解さ

元来、C言語プログラマというのは、オブジェクト指向言語や、関数型言語への憧れを抱きながらも、現実的な解を求めてC言語という言語を使ってきたのだと思います。そのオブジェクト指向言語への憧れが、C++という全く純ではない、ある意味では、泥臭い、オブジェクト指向風味の言語を創ったといってもおかしくないかもしれません。そして、C++という言語は、その進化で、templateという新たな武器を手に入れましたが、この武器も関数型言語の影響を少なからず受けています。しかしながら、C++はその進化を経て巨大な言語になりました。今では、仕様があまりにも複雑すぎて、コンパイラ毎に文法が違う有様です。そして、まだまだ進化を続けています。

こんななか、正直、僕はC++という言語の進化に嫌気がさしてきていました。確かに、generics(template)は面白いし、Boostの中には、関数型言語の発想を以てしなければ理解できないような芸当を使ったものもたくさんあり、それら自体は非常に興味深い物です。にもかかわらず、逆にその興味深さは、ある程度の熟練したC++プログラマによってしか理解されない物になりつつもあります。言語の直行性や実用性を追い求めた結果、逆に非常に使いにくく、さらに読みにくい言語になってしまいました。これでは、逆に開発の現場では利用することが難しくなってきます。熟練した人が書いたコードは、素人にとっては難解で、保守なんて出来た物ではありません。

それに比べると、C#という言語は、誰にとっても理解しやすい言語です。その文法は、CやC++Javaに酷似しており、CやC++Javaプログラマならば、すぐに理解できますし、また、今までに全くプログラムをやったことがなかった人でも勉強するのは難しくありません。

素人プログラマにとってさらに良いのは、C/C++では比較的難解だとされるポインタが、C#では、あまり出てきません。少なくとも必須知識とはなりません。とはいえ、C/C++プログラマが使いたいと思えば、使うことが出来ます。そういう意味では、あえて純粋な世界に閉じていないことがJavaにはないC#の柔軟さともいえるかもしれません*1

銃のない「安全」な社会

Cの精神としては、いろいろとあると思いますが、最も重視されていることは、「プログラマのやることを妨げない」というものでしょう。そして、このことは、「自分の足を銃で撃つことが出来る」ことを意味します。Cという言語はあまりに実直で、プログラマが指示したことは何でもやってしまうから、もしプログラマが、プログラマ自身の足を撃てとCに指示すれば、Cは躊躇なくプログラマの足を撃つだろうというものです。そのぐらい、Cという言語はプログラマを信頼しているのです。しかしながら、プログラマは普通の人間なので、ミスを犯すことは日常茶飯事ですし、誤って、自分の足を打ち抜こうとしてしまったり、最悪な場合、他人を殺してしまおうとしたりしてしまう可能性があります。セキュリティがこれほどに叫ばれる今、こんな危なっかしい言語を24時間使い続けるのは現実的ではありません。

たとえ話が過ぎて、わかりにくいかもしれませんが、ここで銃に例えられているのは、「ポインタ」や「固定長バッファ」です。そのぐらい、「ポインタ」や「固定長バッファ」は危険なものと理解されているわけです。

そして、Javaでは、この銃の存在を否定するところから始まります。Javaの世界では、銃なんて物は存在しません。従って、自分の足を打ち抜くこともありませんし、ましてや、他人を殺してしまうことなんて、考えることすら出来ません。仮に人を殺そうとしても、例外というシステムによって、未然に防がれます。このような世界では、他人に殺されるといった心配をせずに自由に生きることが出来ます。銃を持つという自由を放棄することによって、むしろ楽に生きられるわけです。ポインタも同じです。ポインタがあれば、確かにいろいろなことが出来るかもしれませんが、そのことによって失う自由があることも考慮しなければなりません。

一方で、C#ではもう少し、柔軟なアプローチをとっています。C#ではポインタは存在しますが、普通は使えません。従って、普段は自由に生きることが出来るわけです。しかしながら、特別な手続きを経て*2、特別なエリアに入れば、ポインタ、そして固定長バッファすら利用することも出来ます。このエリアとは、いわゆる隔離された施設です。ここでは銃を使うことが出来、娯楽として利用したり、自殺をするぐらいのことは出来ますが、他人を殺すことは出来ません。C#ではごく希な場合において、このような危険な武器の使用を許可しますが、一般社会を脅かすようなことが出来ないように、プログラマの行動を制限しています。実のところ、現在のC#におけるunsafeコードは、かなり危険で、世界が崩壊するようなことすら出来てしまいますが、将来的には、説明したような特別に制限された環境(sandboxなどと呼ばれる)によって他の世界に影響を及ぼせないようになったり、あるいは、現在のOSのカーネルモードへの遷移のような動作に取って代わられたりすると考えられます。

C++ != 何でも出来る

C/C++プログラマの住んでいる世界は、ポインタがあるおかげで、ある意味では、何でも出来るアセンブラの世界ではあるのですが、そのことが逆にC/C++プログラマの発想を狭い範囲に閉じこめてしまっていることが多々あります。

JavaC#のようなポインタのない世界が実は、アセンブラのようなポインタというかアドレスに支配されている世界では想像しにくい新たなパラダイムや手法を与えてくれることもあります。たとえば、32bitのCPU上では、通常のプログラムは、4GBのメモリ空間に制限され、4GB以上のメモリを利用することは非常な困難を伴います。ところが、JavaC#といった言語は、原理上、そのような制限を受けません(バンク切り替えだとか、セグメントだとかの話はあえて無視)。あくまでも原理上の話ではあるので、現状がそうなっているわけではないのですが、ポインタといった概念が言語上に出現しないことによって、データをメモリ上の番地で表現する必要がないためです。そういった番地という概念が必要になるのは、外部のシステムと通信を行う場合だけであり、C#に関して言えば、それは、P/Invokeや、ポインタを利用した場合に限られるでしょう。こういった特徴は、もし、特定のシステムが、今のコンピュータのような狭い意味での機械語によらない、JavaC#によって構築される場合、現在のような形の仮想メモリのシステムを必要としなくなる可能性があるということです。現在の仮想メモリシステムでは、物理メモリと仮想メモリの変換のための変換テーブルが必要となるのですが、将来的には、似たような物が必要となることはあれ、仮想メモリの効率性を著しく改善できるかもしれません。
また、C/C++にはポインタが存在するが故の問題も多くあります。その一つが、エイリアスの存在という問題でしょう。C/C++では、ポインタが存在するが故に、常にすべての変数が書き換えられるのではないかという懸念がつきまといます。それは次のような場合です。

void calc_sum(double* data, size_t count, double* sum)
{
  for(size_t i = 0; i < count; i++)
    *sum += data[i];
}

このプログラムでは、dataという配列(個数はcount)の和をsumに返します。プログラムとしては非常にシンプルなのですが、実はC/C++のコンパイラは、このプログラムを最適化するに当たって、大きなジレンマを抱えることになります。それは、sumというポインタが、dataという配列のどこかを指し示している可能性を捨てきれないというものです*3。もし、sumがdataの項目を指し示していないということが明らかであれば、このプログラムは、

void calc_sum(double* data, size_t count, double* sum)
{
  size_t _sum = 0; // レジスタに割り当てられる
  for(size_t i = 0; i < count; i++)
    _sum += data[i];
  *sum = _sum;
}

のようなプログラムに変換することが出来ます。メモリへのアクセスが格段に少なくなるため、プログラムは高速に動く可能性が高くなりますし、さらに、マルチスレッドではない環境ならば、dataの配列の内容が変更されないことも保証されるので、読み込み時にもメモリよりも高速なキャッシュのデータを利用することが出来るでしょう。

このような問題のためにC/C++では、プログラムの最適化においては、プログラマによる明示的な指示*4あるいは、大胆な仮定*5なしには、思い切った最適化(高速化)ができないことも少なくありません。

C#Javaでは、そもそもポインタが存在しないか、あるいは滅多に使われないため、このような問題に悩まされることは格段に少なくなります。そのため、将来的には、C#Javaの方が、C/C++よりも高速になるといったことが起きることも十分に考えられるわけです。

デリゲート

C/C++プログラマが涙を流して欲しがるC#の機能としては、デリゲートがあるでしょう。デリゲートのおかげで、C#では、C++ではコールバックを用いて非常に上長にしか記述できない処理が非常にシンプルに記述できるのです。そして、マルチキャストデリゲートなんていう飛び道具が標準であることはC#の強みとしては十分な魅力を持つ物です。

ガベージコレクション

C/C++では、プログラマは借りた物をちゃんと借りた相手に返すことが要求されます。しかしながら、プログラマは時にこの約束を忘れたり、あるいは、間違った相手に返したりしてしまいます。いわゆるメモリリークやそれに類似する行為です。C#Javaでは、多少の誇張を以て言えば、このようなことはありません。貸した人は向こうから取りに来てくれます。僕のようなずぼらな人間にとって、ツタヤでCDを借りることはある意味では冒険なのですが*6、向こうから取りに来てくれるのならば話は別です。

関数型言語

C/C++プログラマには、昔からある種の憧れがあると思います。それは、Lispに代表されるような関数型言語への憧れです。C/C++を使っていると、Lispや、Rubyのような言語の美しさに魅了される部分も少なからずあるのですが、なんと、C#ではそれらの機能がすでに実装されているか、あるいは、将来のバージョンで実装されることがすでに決定したりしています。C++のような文法を維持したまま、これらの機能が利用できるということがどんなにすばらしいことかは、なかなか説明しにくいのですが、表現の幅が増える上(指向の幅も広がる)に、記述がシンプルになることはいろいろな意味での生産性を向上させます。

Mono

C#がいかにすばらしいかについてはここまででかなり説明できたような気がするんですが、C#がMicrosoftに閉じた言語である限り、その未来にはいろいろと障害があるかもしれません。面白いのは、C#は、Microsoft発の言語にしては珍しいぐらいにオープンソース陣営による動きが大きいことです。Monoは、NovellによるオープンソースCLIおよび、C#の実装で、LinuxMac OS Xをはじめとする様々なプラットフォームで動作します。そして、GTK#や、Cocoa#といった各プラットフォーム専用のフレームワークすら存在します。つまり、もはや、Windowsでしかつかえない言語ではないのです。確かに、こんなにすばらしい言語がWindowsプログラミングでしか使えないのはもったいないと思うのは自然なことだと思います。

未来のOS

すでにJava OSなるものは昔に提唱、実装されていますが、C# OS(CLI OSというべきか)も可能でしょう。そして、CLI OSでは、すべてのプログラムは危険な武器を持つことはないので、平和に共存でき、他のプログラムに悪い影響を与えることはなくなります。そのために、もはや、現在、ユーザーのプログラムとOSのカーネルとを隔てているカーネルモード遷移という概念は必要なくなってしまいます。逆にunsafeコードを実行できる権利を持つプログラムと、そうでないプログラムがカーネルとそうでない部分を区別する唯一の部分になるでしょう。このような環境では、プロセス間通信は、プロセス間を隔離するバリヤのような物が必要なくなるために現在存在するようなOSよりも高速になります。その結果、ファイルI/Oなどのスループットは現在のOSよりも高くなるでしょう。また、前述したように、現在のOSのような仮想メモリの仕組みも必要なくなることから、メモリ管理の面でもなんらかの性能向上が見込めます。このようなOSが完成したときには、C/C++などは、CLI環境の構築や、互換性以外の目的では必要性がなくなり、最終的には、現在のC/C++C#/Javaの関係は逆になると思われます。つまり、C/C++のプログラムがOSから呼び出される場合には、これまでのOSと同じように、他のプログラムとの隔離が必要になりますから、CLIでは必要のなかったバリアを作成するコストが発生することになります。
また、C#のコードは、実行中にその時代のJITによる最適化が行われますが、C/C++のコンパイル済みコードは、コンパイルされたが最後、最適化の対象にならなくなります。つまり、C/C++のコードは、C#のコードに比べて将来の最適化技術の進歩による恩恵を受けられない可能性が高くなります。

まとめ

以上、ほとんど妄想による未来像ばかりですが、時代がこのような方向に動いているんじゃないかなぁと考えると、C/C++なんて言語をいつまでも使っていると、時代において行かれる気がしてなりません。そんな焦りを抱く必要は一切ないとしても、このような可能性に満ちあふれた言語を使わない手はありません。皆さんはどうお考えでしょうか。

*1:そして、これはMicrosoftがつくるもの全般に言える、ある意味での現実に即した、完全な世界とはほど遠い泥臭い寛容さともいえるでしょう

*2:unsafeキーワードを利用して、アンセーフコードを記述する

*3:sumがdataの要素のどこかを指し示している場合、計算中にdataの配列が変更される可能性があり、最終的なsumの値に影響する

*4:C99で導入されたrestrictなど

*5:コンパイラによってはコンパイル時に特殊なオプションを指定することができる

*6:返しに行くのが面倒なので、借りたくありません