3-a1. D言語の落とし穴

初出: 2007/03/07
最新: 2009/01/30
[3. D言語各論] に戻る

目次 / 概要

ソースの文字コード

D は、全てUnicodeを前提に実装されています。 ソースに使える文字コードもUnicodeのみです。例えば、

import std.stdio;

void main()
{
    writeln( "こんにちは!" );
}

こういう日本語が混じったソースは、UTF-8, UTF-16, UTF-32 のいずれか で保存しないとコンパイル通りません。Shift_JIS や EUC-JP は扱えないのでご注意ください。 間違った文字コードを使うと "invalid UTF-8 sequence" というエラーになります。

プログラム内部では、char 型を使った文字列は常に UTF-8 で表現されるということになっています。

文字化け!

一方で、D のプログラムを動かす実際の環境は、UTF-8 とは限りません。例えば日本語版 Windows だったら、 基本は Shift_JIS で動いていると思います。Dの標準ライブラリは今のところ、その橋渡しをあんまりやってくれないので、現状、プログラマ側が注意する必要があります。 例えば上のコードを、ちゃんと UTF-8 で保存してコンパイルして実行しても

文字化けの図

こういう風に文字化けします。writefln は UTF-8 をそのまま出力するけれど、コマンドプロンプトは それを Shift_JIS と思って描画するので、こんなことになっちゃうわけですね。 英語だけなら問題ないのですが、日本語などを扱い始めるとやっかいです。

正しくは、標準ライブラリが画面出力の直前に、その環境の文字コードへと変換をかけるべきです。 (「利用者がwritelnする直前に変換する」のは間違いです、念のため…)。 べきですが現状そうなっていないので、応急処置的に tx というライブラリを作ってみました。これ経由で writefln すると、環境に合わせた文字コードへ 内部で変換するようになっています。

import tx.all;

void main()
{
    stdout.writefln( "こんにちは!" );
}

これがちゃんと「こんにちは!」と出ます。

ちょっと残念な現状ではありますが、次期標準ライブラリとして開発されている Tango は一応ある程度文字コード変換してくれるようなので、徐々に改善されていくんじゃないかなーと思います。

C言語との文字列受け渡し

※ この項は直接OSのAPIを呼ぶようなネイティブなことをする人向けの 落とし穴解説です。普通にDのみでプログラム組むときには特に気にしなくてよいです。

C言語では、文字列は「\0 終端」で表現します。

char* str = "abc";  // [a][b][c][\0] の4バイト領域の先頭アドレス

一方 D 言語では、\0 はつけずに、長さとアドレスの二つの値で文字列を表現します。

string str = "abc";  // 整数3 と、[a][b][c] の3バイト領域の先頭アドレスの組

直接OSのAPIを呼び出すときや、元々Cで書かれたライブラリを単純にラップしただけのライブラリを 使う時など、D言語の値をCの値に直したり、その逆変換をする必要があります。整数などは何も変換は 要りませんが、文字列に関してだけ、注意が必要です。

変換方法

というわけで、D の文字列を C の関数に渡すときは、後ろに \0 を足してやる必要があります。 この変換を行う関数は、標準ライブラリに用意されています。std.string.toStringz です。

import std.string;
import std.c.stdio : puts;

void main()
{
    string str = "abc";
    puts( toStringz(str) );  // Cのputs関数に渡すときは toStringz する
}

逆に C の関数から文字列を受け取るときは std.string.toString を使います。

import std.string;
import std.c.stdlib : getenv;

void main()
{
    char* p = getenv(toStringz("PATH"));
    string path = toString(p);
}

// クラスの中で変換する場合の注意!
// 
// Object.toString() と名前が重なっているため、
// 単にtoString(p)と書くとObjectのメソッドの方を呼ぼうとしてエラーになります。
// .toString または std.string.toString と完全修飾して呼び出してください
class Foo
{
    void foo()
    {
        char* p = getenv(toStringz("PATH"));
        // string path = toString(p);
        string path = .toString(p);
    }
}

こちらでバッファを確保してそこに文字列を書き込んでもらう形式の場合、new でメモリを確保してから、 .ptr プロパティでアドレスを渡します。toString で D 文字列に変換するのは先ほどの例と同じです。

import std.string;
import std.c.stdio : fgets, stdin;

void main()
{
    char[] buf = new char[100];
    fgets( buf.ptr, buf.length, stdin );
    string data = toString(buf.ptr);
}

さらに、上記のコードでは省きましたが、Dのchar[]は常にUTF-8ですが、Cの関数が期待する文字コードは 違うかもしれません。その場合は、文字コードの変換も必要です。 tx にも、 UTF-8⇔環境ネイティブの文字コードの変換クラスが一応実装されてます。

obj is null

オブジェクト変数が null かどうかチェックするのに

if( obj == null ) { ... }

と書くと、本当にnullだった時には実行時エラーで例外が飛んでしまいます。正しくは

if( obj is null ) { ... }

と書きます。D の == は Java でいう .equals() で、Java でいうところの == は、D では is を使います。

※最近のバージョンのコンパイラでは、obj == null 形式の比較はコンパイルエラーとなるようになりました。

[3. D言語各論] に戻る

presented by k.inaba   under NYSDL.