D 1.0 FAQ
何度も訊かれる質問が増えてきので、 FAQ を用意しました。
- The D wiki FAQ page でもっと多くの質問への回答があります。
- なぜ D という名前なのですか?
- Dコンパイラはどこで手に入りますか?
- DはLinuxへ移植されていますか?
- GNU版のDはありますか?
- ××というCPU向けの自作のDコンパイラを書くにはどうしたらよいですか?
- DのGUIライブラリはどこで入手できますか?
- DのIDEはどこで入手できますか?
- template はどうなっていますか?
- 何故、実装の簡単さを強調しているのですか?
- printf という [-放送禁止-] なものを残したのは何故ですか?
- D はオープンソースになるのでしょうか?
- switch文がfall throughなのは何故ですか?
- JavaではなくDを選ぶ理由としては何があるでしょう?
- C++になくてDにある機能は何でしょう?
- C++でも、文字列は STLでサポートされているのですが…
- C++でも、ガベージコレクションはライブラリを使えば可能なのではないでしょうか?
- C++でも、単体テストはライブラリを使えば可能なのではないでしょうか?
- 移植性を重視した言語なのにasm文があるのは何故ですか?
- 80bit実数型の利点は?
- D で無名構造体/共用体を使うには?
- 文字列に対して printf() を動作させるには?
- なぜ浮動小数点数は0ではなくNaNに初期化されるのですか?
- なぜ代入演算子のオーバーロードに対応していないのですか?
- '~' は私のキーボードにないのですが…
- 他のコンパイラで作ったCのオブジェクトファイルをリンクできますか?
- 正規表現リテラルを /foo/g のような構文でサポートしないのはなぜですか?
- なぜDのフロントエンドはDではなくC++で書かれているのですか?
- Digital Mars のプログラムを全てDで書き直したらどうでしょう?
- どのような時にforよりもforeachループを使うとよいですか?
- Dに、CとのインターフェイスはあってもC++とのは無いのは何故?
- なぜDはガベージコレクションに参照カウント方式を使わないのですか?
- ガベージコレクションは遅くて予測不能なんでしょ?
なぜ D という名前なのですか?
元々は プログラミング言語 Mars という名前でした。けれど友人が D と呼び続けたので、私も影響されて D と呼ぶようになったのです。 C の後継の D言語、という案は、 少なくとも1988年のこの スレッド までさかのぼります。
Dコンパイラはどこで手に入りますか?
ここです。
DはLinuxへ移植されていますか?
はい。DigitalMarsのDコンパイラはLinux版を含んでいます。
GNU版のDはありますか?
はい。David Friedman によって、 DフロントエンドとGCC が統合されました。
××というCPU向けの自作のDコンパイラを書くにはどうしたらよいですか?
Burton Radons が バックエンド を公開しています。参考となるでしょう。
DのGUIライブラリはどこで入手できますか?
D からは C の関数を呼び出せますので、Cのインターフェイスをもった GUI ライブラリなら、全てDからも使えます。D言語用の様々なGUIライブラリや移植版が、 AvailableGuiLibraries で紹介されています。
DのIDEはどこで入手できますか?
Elephant, Poseidon, LEDS などがあります。
template はどうなっていますか?
現在の D は先進的なtemplate機能を備えています。
何故、実装の簡単さを強調しているのですか?
重要なのは、言語のユーザー側にとっての簡単さではないのでしょうか? はい、 その通りです。しかし、前宣伝だけで完成しない言語には何の意味もありません。 実装が簡単であれば、より多くの頑強な処理系が登場するでしょう。Cについて言えば、 なんとまあ、IBM PC向けだけで30もの商用コンパイラがリリースされています。 しかし、C++へと移行したものは多くありません。 C++コンパイラの市場に目を向けると… それぞれ何年かけて開発されているのでしょう。最低でも10年くらいでしょうか? しかし、世のプログラマ達はもう何年も、 規格にあるC++言語の様々な機能が 実際に実装されるのを待ち続けています。 もし仮にこれほどC++ユーザが多くなければ、 多重継承やテンプレートなどの複雑な機能が実装されていたか、 怪しいものです。
私の意見では、実装が簡単な言語は、理解するのもまた簡単です。 言語の秘技を極めたりするよりは、よりよいプログラムの書き方を 学ぶのに時間を費やす方がずっとよいのではないでしょうか? もし10分の1の複雑さで C++ の9割の機能が得られるなら、これは価値のあるトレードオフではないかと思います。
D になぜ printf が残っているのですか?
printf は実際はDの一部ではなく、 しかしDからアクセスできるCの標準ライブラリです。 D の標準ライブラリは std.stdio.writefln を含んでいて、 これは printf 並に強力でしかも使いやすくなっています。
D はオープンソースになるのでしょうか?
Dのフロントエンドはオープンソースで、 ソースは コンパイラ に付属しています。 ランタイムライブラリは完全にオープンソースです。 David Friedman は、 DフロントエンドとGCCを統合 して gdc を作っており、これは完全にオープンソースなDの実装です。
switch文がfall throughなのは何故ですか?
沢山の人に、switch 文のcaseとcaseの間には必ずbreakが入る、 という仕様にならないかと要求されました。C の、いわゆる "fall through" は沢山のバグの原因となってきたからです。
Dでこれを変えなかった理由は、整数の昇格や演算子の優先順位を同じにしたのと、 同様の理由 - Cとして読めるコードはCと同様に振る舞うべき - です。 潜在的に違う意味をもっていたりしたら、 余計わかりにくいバグを生むでしょうから。
JavaではなくDを選ぶ理由としては何があるでしょう?
D と Java では、目的も哲学も実現も異なっています。この 比較 をご覧下さい。Java は 'write once, run everywhere' を目的に設計されました。対して D は、 効率的なネイティブアプリを書くために設計されています。DもJavaも ガベージコレクションは善で多重継承は悪で…(^^; というあたりで共通点はありますが、 目的の違いから、言語自体の感じも大きく違うものになっています。
C++では、文字列はSTLでサポートされているんですが…
C++の標準ライブラリには、 文字列や動的配列に連想配列、 境界チェック付き配列や複素数などが存在します。
もちろん、これらの機能は全てライブラリや、 何らかのコーディング方針を守ることなので実現できます。 しかしそれを言ったら、Cでオブジェクト指向だってできるのです (実際に目にしたことがあります)。どんな簡単な BASIC インタプリタでもサポートしている文字列というものために、 巨大で複雑な機構が必要というのはどうも不釣り合いです。 STLの文字列型は、実装に二千行以上のコードが費やされ、 高度なtemplateの機能を縦横に使っています。 これら全てが正しく動いているということをどれほど確信できるでしょう? もしまずい点があったらどうやってそれを直すのでしょう。 使っていてコンパイルエラーが出たときの猛烈に読みにくいエラーメッセージは どうすればよいのでしょう?自分の使い方が正しい(メモリリークが起きないとか…) ことをどうやって確かめればよいでしょうか?
Dの文字列実装は簡単で、直接的です。どう使えばいいか迷うようなことは ほとんどありませんし、メモリリークの心配もありません。 エラーメッセージは的を射ていて、期待通りに動作しているかどうかは 見ればすぐにわかります。
C++でも、ガベージコレクションはライブラリを使えば可能なのではないでしょうか?
はい、私も使っています。しかし、言語の一部でないという特性上、 これを使うにはいくつか制約が生じます。また一般のC++プログラマにとっては、 C++でgcを使うという選択肢はないようです。 Dのように言語内にGCを組み込むのは、 日々のプログラミングにとってとても実用的です。あまり先進的なことをしなければ、GCの実装は難しくありません。 簡単なものでも言語は100%動くという点で、より高度なGCを書くというのは、 より高度な最適化ルーチンを書く、ということに似ています。 規格をどの程度実装しているかというような点ではなく、 どの程度良いコードを生成するか、という点で複数の実装を比べられる方が、 プログラミングコミュニティにとってより良い状態です。
C++でも、単体テストはライブラリを使えば可能なのではないでしょうか?
もちろん。そのライブラリを使ってみて、Dと比べてみてください。 言語に組み込まれているというのがどんなに便利か、 すぐに明らかに感じるでしょう。移植性を重視した言語なのにasm文があるのは何故ですか?
asm文は、Dの関数内に直接アセンブリのコードを書き込むことを可能にします。 アセンブラのコードは自明に移植性のないものとなりますが、しかし、 システムアプリの開発には非常に便利なものです。システムアプリは 大なり小なりシステム依存のコードを含むことになるので、インラインアセンブラは 大した問題にはなりません。インラインアセンブラは、CPUの特殊命令や フラグビットへのアクセスによって、特別な処理の場合や、 限界までコードを最適化したいときに役立ちます。Cコンパイラがインラインアセンブラ機能を持つ前は、 私は外部のアセンブラを使っていました。アセンブラは 沢山の、本当に沢山のバージョンがあって、各バージョン毎に微妙に 構文が違っていたりバグがあったりコマンドラインの文法まで 変わっていたりして、非常に残念な思いをしました。 つまり、アセンブラの必要なコードは、ユーザーには安心してビルドはできなかったのです。 インラインアセンブラができてからは、そんなことはなくなりました。
80bit実数型の利点は?
有効桁数を増やせば増やすだけ、 正確に浮動小数点数の計算ができます。 これは特に大きな数と小さな数を足すときに顕著です。この話題については、 Intel の FPU をデザインした Kahan博士による、説得力のある 論文 があります。D で無名構造体/共用体を使うには?
import std.stdio; struct Foo { union { int a; int b; } struct { int c; int d; } } void main() { writefln( "Foo.sizeof = %d, a.offset = %d, b.offset = %d, c.offset = %d, d.offset = %d", Foo.sizeof, Foo.a.offsetof, Foo.b.offsetof, Foo.c.offsetof, Foo.d.offsetof); }
文字列に対して printf() を動作させるには?
Cでは、文字列をprintfするには通常、 書式 %s を使います:char s[8]; strcpy(s, "foo"); printf("string = '%s'\n", s);D で次のようにこれを実行してみると:
char[] s; s = "foo"; printf("string = '%s'\n", s);大抵の場合、ゴミが表示されるかアクセス違反になります。 原因は、Cでは文字列は0終端であるためです。 %s は 0 が現れるまで文字を出力しようとします。 Dでは、文字列は0終端ではなく、長さは別に保持された値で決定されます。 そこで、printfを文字列に使うには 書式 %.*s を使うことになります:
char[] s; s = "foo"; printf("string = '%.*s'\n", s);
今度は期待したとおりに動作するでしょう。 しかし注意が必要なのは、printf の %.*s は、長さ分もしくは 0 が現れるまで 出力する、ということです。このため、中に0を含むDの文字列は、 最初の0までしか出力されません。
もちろん、もっと簡単な解決策は D の文字列に対して正しく動作する std.stdio.writefln を使うことです。
なぜ浮動小数点数は0ではなくNaNに初期化されるのですか?
浮動小数点数は、明示的に初期化子が指定されていない限り、 NaN (Not A Number) へと初期化されます:double d; // d は double.nan に設定されるNaNは、演算のオペランドに使われると常に結果もNaNになるという、 素晴らしい特徴を備えています。従って、 NaNが計算課程にひとたび現れると、 これは伝搬し、出力としてNaNが得られることになります。 このことは、出力でのNaNの出現は、 どこかで未初期化変数が使われていることの証拠となることを示しています。
仮に 0.0 が浮動小数点数のデフォルト初期化子であったならば、 その結果は出力からは簡単には気づけなくなり、 そのデフォルト初期化値が意図したものではなかったときでも、 そのバグが見過ごされてしまうかもしれません。
デフォルト初期化値は、有用な値であることは意図されていません。 バグの発見がその用途です。そして、NaNはこの役割を適切に果たしています。
しかし、初期化されていない変数は当然コンパイラが見つけて、 エラーメッセージを出せるのではないでしょうか? ほとんどの場合確かにそれは可能ですが、常にではありません。この可能性は、 コンパイラ内部のデータフロー解析がどれだけ洗練されているかにかかっています。 それゆえ、そんな機能に頼るのは可搬性・信頼性に欠けます。
CPUがそう設計されたため、整数にはNaN値がありません。 そこで D では代わりに 0 を使います。 これは NaN のようなエラー検知には役立ちませんが、 少なくとも、意図しないデフォルト初期化に起因する結果は常に一定になり、 デバッグがより楽になります。
なぜ代入演算子のオーバーロードに対応していないのですか?
構造体に対する代入演算子のオーバーロードは D 2.0 でサポートされました。
'~' は私のキーボードにないのですが…
PCのキーボードでは、[Alt]を押しながらテンキーの 1, 2, 6 を順に押してAltを離すと、 '~'が入力されます。
他のコンパイラで作ったCのオブジェクトファイルをリンクできますか?
DMDはOMF形式 (Microsoft Object Module Format) のオブジェクトファイルを生成しますが、他のコンパイラ、例えば VC++ はCOFF形式のオブジェクトファイルを生成します。 DMD の出力は、同じくOMF形式のオブジェクトファイルを生成する DMC (Digital Mars C コンパイラ) と共に動作するように設計されています。DMDの使うOMF形式は、かつてIntelが設計した形式に基づいて、Microsoft が定義したものに基づいています。Microsoftはある時から、 OMFの使用をやめて新しく定義したCOFFのMicrosoft版を使うようになりましたが。
同じオブジェクトファイル形式を採用しているからと言って、その形式のCライブラリが 正しくリンクできて実行できるとは限りません。実際にはもっと様々な互換性が必要です - 例えば、呼び出し規約や、名前マングリング、コンパイラの補助関数、 細かい動作に関する暗黙の仮定など -。例えDMDがMicrosoftのCOFFを出力できたとしても、 VCで使うために作られテストされたオブジェクトファイルを、 そのまま正しくリンクして実行できる可能性はほとんどありません。 Microsoftのコンパイラが実際にOMFを出力していた頃から、 この手の問題は多数存在していました。
相異なるオブジェクトファイル形式の存在は、DMD での動作がチェックされていないライブラリであることを示す役目を果たしています。 もしこうなっていなければ、リンクはどうにかできても、もっと見つけにくい問題の 素になってしまいます。あるコンパイラでビルドされたバイナリを、 別のベンダのコンパイラの出力と合わせて使えるようにするのは、 エキスパートの技が必要な作業なのです。
とは言えLinux版のDMDは、Linuxでの標準形式である ELF 形式のオブジェクトファイルを生成しますから、LinuxのCコンパイラとして標準的な gcc とは特にうまく協調できるようになっています。
既存のCライブラリを使うことができる場合も一つだけあります。それは、 そのライブラリがCのABIインターフェイスに適合したDLLとして提供されている場合です。 この場合ライブラリのリンク可能部分は "インポートライブラリ" と呼ばれ、 Microsoft の COFF形式のインポートライブラリは、 coff2omf を使えば DMD の OMF 形式へと正しく変換できます。
正規表現リテラルを /foo/g のような構文でサポートしないのはなぜですか?
理由は二つあります:
- / は除算トークンでもあるため、/foo/g 構文を導入すると、 字句解析器と構文解析器を分離することが不可能になります。
- 現在、すでに3種類の文字列リテラルが存在し、正規表現リテラルを追加すると さらに3種類増えることになります。これは、コンパイラやデバッグ情報、 ライブラリなど多くの部分に波及しますが、それだけの価値がある変更ではありません。
なぜDのフロントエンドはDではなくC++で書かれているのですか?
フロントエンドがC++なのは、既存のgccやdmdのバックエンドとの整合性を とるためです。 他の既存のコンパイラバックエンドも多くはC++で書かれているので、 これは他のバックエンドへも簡単に移植できることにつながります。 DMDScript の D による実装 (C++版 よりもパフォーマンスが優れています) の存在は、 100% D でプロ品質のコンパイラを書くのに何の問題もないことを示しています。
Digital Mars のプログラムを全てDで書き直したらどうでしょう?
複雑でデバッグ済みで既に完成しているアプリケーションをわざわざ他言語に 移植することにあまり利点はありません。新しい Digital Mars のアプリは Dで実装されています。
どのような時にforよりもforeachループを使うとよいですか?
どのような時にforよりもforeachループを使うとよいですか?
foreachを使えば、プログラマが自分で考えなくても、 最適化のための選択を コンパイラに全て任せることができます。 例えば -- ポインタと添字のどちらを使うか? 終了条件をキャッシュしておくべきか否か? ループを回すべきか否か? これらの選択肢に対する最適解は自明ではありませんし、そもそもマシンによって 変わってきます。レジスタ割り当てなどと同じように、 こんな仕事はコンパイラに任せましょう。
for (int i = 0; i < foo.length; i++)や:
for (int i = 0; i < foo.length; ++i)や:
for (T* p = &foo[0]; p < &foo[length]; p++)
や:
T* pend = &foo[length];
for (T* p = &foo[0]; p < pend; ++p)
や:
T* pend = &foo[length]; T* p = &foo[0]; if (p < pend) { do { ... } while (++p < pend); }や、もちろん、size_t を使うべきか int を使ったほうがいいのか?
for (size_t i = 0; i < foo.length; i++)
こういった選択はコンパイラに選ばせましょう!
foreach (v; foo)
...
型Tが何であるべきかすら考えなくていいことにも注目してください。 これによって、Tが変化したときのバグの原因が減ります。fooが配列なのか、 連想配列なのか、あるいは構造体やコレクションクラスなのかすら気にする必要はありません。 これによって、ありがちな終了条件まわりのバグも避けられます:
for (int i = 0; i <= foo.length; i++)
さらに、fooが関数呼び出しだった場合に一時変数を手で作る手間も これによって回避できています。
forループを使う唯一の理由は、そのループが通常のループ形式に 合わない場合…つまり例えば その場で終了条件が変化する場合…であるときのみです。
Dに、CとのインターフェイスはあってもC++とのは無いのは何故?
D の C++ に対するインターフェイスを作るのは C++ のコンパイラを書くのとほとんど同じくらい複雑で、これは Dを十分実装しやすい言語とするという目標と矛盾してしまいます。 既存のC++コードを元に開発を続けなければならない人は、残念ながら C++から離れることはできないでしょう (D以外の言語であっても移ることは不可能です。)
変更できないC++のコードを任意にDから呼び出せるようにしようと思うと、 さまざまな困難が待ちかまえています。 以下のリストはもちろん完全ではなく、 いかに難しいかを示す例として挙げてあります。
- DのソースコードはUnicodeですが、C++はASCIIか、 あるいは指定されていません。これは文字列リテラルの内容に影響します。
- std::string はマルチバイトのUTFを適切に処理できません。
- C++のタグは個別の名前空間を持っていますが、Dにはありません。 従ってある種のリネームが必要になります。
- C++のコードはコンパイラの独自拡張に依存していることが多いです。
- C++ には名前空間があります。Dにはモジュールがあります。 この二つの対応関係は単純ではありません。
- C++ はソースコードを(プリプロセス後の)巨大なファイルと見なして処理します。 Dはモジュールとパッケージの階層として処理します。
- enumの名前スコープ規則が異なっています。
- C++のコードは、マクロをできるだけ使わないようにという長年の試みにもかかわらず、 マクロにマクロを重ねた層に依存しています。 Dには、トークン結合や 文字列化のようなマクロに対応するものがありません。
- マクロ名は #include を超えてグローバルなスコープを持ちますが、 巨大ソースファイルと見なすとローカルスコープです。
- C++ には多重継承や仮想継承があります。 Dにはありません。
- C++ はin,out,ref(inout)引数を区別しません。
- C++ の名前マングリング方式はコンパイラ毎に異なります。
- C++ は任意の型の例外(Objectの派生に限らない) を送出する可能性があります。
- C++ には const と volatile によるオーバーロードがありますが、Dにはありません。
- C++ の演算子オーバーロード方式はだいぶDと違っています。例えば、 operator[]() の左辺値と右辺値としてのオーバーロードは、 const によるオーバーロードとプロキシクラスを使って実現されます。
- C++ の例えば < の演算子オーバーロードは 完全に > と独立しています。
- C++ は参照外し (operator*) をオーバーロードできます。
- C++ はクラスと構造体を区別しません。
- 仮想関数テーブルの位置とレイアウトは C++ と D とで異なります。
- RTTI の実現方式もまったく異なります。 C++にはclassinfoはありません。
- D は代入演算子のオーバーロードを許可しません。
- D には構造体のコンストラクタやデストラクタはありません。
- D には two phase lookup や Koenig (ADL) lookup はありません。
- C++ はクラス同士を 'friend' で関連づけますが、D ではパッケージとモジュールに基づいて行われます。
- C++ のクラスデザインは、明示的なメモリ割り当ての問題を解決することを 念頭においておこなわれがちですが、Dではそうではありません。
- D のテンプレートシステムはだいぶ違っています。
- C++ には '例外仕様' があります。
- C++ にはグローバルな演算子オーバーロードがあります。
- C++ の名前マングリングは、const と volatile が型修飾子であることに依存しています。 D には型修飾子としての const や volatile は存在しないので、 D の型から C++ のマングル済み識別子を推論する簡単な方法がありません。
問題の根底にあるのは、言語設計はその上で書かれるコードに影響する、 ということです。C++の言語設計はDにフィットしていません。 たとえ二者を自動的に繋ぐ方法を発見できたとしても、 その結果は左半分ホンダで右半分カマロのような大変なものになってしまうでしょう。
なぜDはガベージコレクションに参照カウント方式を使わないのですか?
参照カウント方式には利点もありますが、 いくつか重大な欠点があります:
- 循環データ構造が解放されません
- ポインタのコピーのたびに毎回参照カウントの上げ下げが必要です。 単に関数に参照を渡すだけのときであっても。
- マルチスレッド環境では、カウントの上げ下げに同期処理まで必要です。
- メモリリークを避けるために、カウントを減らす例外ハンドラ (finally) を挿入する必要があります。他の部分でどうがんばろうとも、 "オーバーヘッド無し例外" はありえないことになります。
- スライスや配列内部へのポインタに対応するには、あるいは同様に 非オブジェクトの任意のメモリ割り当てを参照カウントで管理するには、 独立した"ラッパー"オブジェクトを参照カウントしたいメモリ領域毎に 用意しなければなりません。これは本質的に、 必要なメモリ割り当て回数を倍増させます。
- ラッパオブジェクトが必要と言うことは、ポインタ経由のデータへの参照には、 常に2回ずつの参照外しが必要ということを意味します。
- コンパイラを調整してこれらすべてをプログラマから見えずに澄むようにしてしまうと、 Cとの綺麗なインターフェイスを整えるのが難しくなります。
- 典型的なケースでは総合的なメモリ消費はGCの方が大きくなりますが、 参照カウントはヒープを断片化させ、従って GC 以上にメモリを消費する可能性があります。
- 参照カウントはGCによる停止時間の問題をなくすわけではなく、 単に抑えるにすぎません。
C++標準として提案されている shared_ptr<> は参照カウントを実装し、 以上すべての欠点を抱えています。shared_ptr<> と Mark&Sweep を厳密に比較したベンチマークはまだ見たことがありませんが、 shared_ptr<> が速度とメモリ消費の両面で大負けしたとしても 私は驚きません。
とは言ったものの、D でも将来のオプションとしてある種の参照カウントを サポートする可能性があります。例えばファイルハンドルのような 個数の少ないリソースを管理するのには参照カウントは悪くない方式です。
ガベージコレクションは遅くて予測不能なんでしょ?
その通りです。でも、malloc/free も含めてどんな動的メモリ管理も、 遅くて、実行時間は予測不能なものです。 実際にリアルタイム系のソフトを作っている人に聞くと、 正確性が必要な場面では彼らはmalloc/freeは使わないと言います。 全てのデータをあらかじめアロケートしておくのです。 しかしながら、mallocの代わりにGCを使うことでより進んだ言語機能 (とりわけ、より強力な配列操作) が利用可能になり、 結果として必要となるメモリ割り当ての回数も減少します。 このため、明示的メモリ管理よりも GC の方が現実には高速になることを意味しています。