3

UTF8文字列をUTF16(リトルエンディアン)に変換する関数を記述したいと思います。問題は、iconv関数が出力文字列を格納するために必要なバイト数を事前に通知していないように見えることです。

私の解決策は、を割り当てることから始めて、ループで2*strlen(utf8)実行し、必要iconvに応じてそのバッファのサイズを増やすことです。realloc

static int utf8_to_utf16le(char *utf8, char **utf16, int *utf16_len)
{
    iconv_t cd;
    char *inbuf, *outbuf;
    size_t inbytesleft, outbytesleft, nchars, utf16_buf_len;

    cd = iconv_open("UTF16LE", "UTF8");
    if (cd == (iconv_t)-1) {
        printf("!%s: iconv_open failed: %d\n", __func__, errno);
        return -1;
    }

    inbytesleft = strlen(utf8);
    if (inbytesleft == 0) {
        printf("!%s: empty string\n", __func__);
        iconv_close(cd);
        return -1;
    }
    inbuf = utf8;
    utf16_buf_len = 2 * inbytesleft;            // sufficient in many cases, i.e. if the input string is ASCII
    *utf16 = malloc(utf16_buf_len);
    if (!*utf16) {
        printf("!%s: malloc failed\n", __func__);
        iconv_close(cd);
        return -1;
    }
    outbytesleft = utf16_buf_len;
    outbuf = *utf16;

    nchars = iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
    while (nchars == (size_t)-1 && errno == E2BIG) {
        char *ptr;
        size_t increase = 10;                   // increase length a bit
        size_t len;
        utf16_buf_len += increase;
        outbytesleft += increase;
        ptr = realloc(*utf16, utf16_buf_len);
        if (!ptr) {
            printf("!%s: realloc failed\n", __func__);
            free(*utf16);
            iconv_close(cd);
            return -1;
        }
        len = outbuf - *utf16;
        *utf16 = ptr;
        outbuf = *utf16 + len;
        nchars = iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
    }
    if (nchars == (size_t)-1) {
        printf("!%s: iconv failed: %d\n", __func__, errno);
        free(*utf16);
        iconv_close(cd);
        return -1;
    }

    iconv_close(cd);
    *utf16_len = utf16_buf_len - outbytesleft;

    return 0;
}

これは本当にそれを行うための最良の方法ですか?sを繰り返すreallocのは無駄に思えますが、utf8に含まれる可能性のある文字シーケンスと、それらがutf16にどのような結果になるかを知らなければ、初期バッファーサイズをより適切に推測できるかどうかわかりません2*strlen(utf8)

4

2 に答える 2

6

UTF-8をUTF-16に変換しても、データのサイズが2倍を超えることはありません。最悪の場合はASCII(1-> 2バイト)です。UTF-8の他のすべてのBMPコードポイントは2バイトまたは3バイトかかります(したがって、UTF-16に変換すると、同じサイズのままになるか、小さくなります。非BMPコードポイントは、UTF-8またはUTF-16のいずれかで正確に4バイトです。

したがって、バッファを拡大するための無駄で複雑でエラーが発生しやすいreallocロジックを排除できます。

ちなみに、。でカウントされないnull終了用のスペースを残しておいてくださいstrlen

于 2012-11-08T21:30:46.803 に答える
5

それが正しい使い方iconvです。

iconvこれは、任意の文字エンコードから別の任意の文字エンコードに再コーディングできるように設計されていることを忘れないでください。任意の組み合わせをサポートします。これを考えると、出力に必要なスペースの量を知るには、基本的に2つの方法しかありません。

  1. 当ててみて。変換を行い、必要に応じて推測を増やします。
  2. 変換を2回行います。初めて、ただ数えて、出力を破棄します。カウントしたスペースの合計量を割り当ててから、変換を再実行してください。

最初はあなたがすることです。2つ目は、明らかに2回作業を行う必要があるという欠点があります。(ちなみに、iconv最初のパスの出力バッファーとしてローカル変数のスクラッチパッドバッファーを使用することで、2番目の方法でそれを行うことができます。)

他に方法はありません。入力に含まれる文字数(バイトではない)と、BMPに含まれる/含まれない文字数を事前に知っているか。またはあなたはそうしません、そしてあなたはそれらを数えなければなりません。

この場合、入力と出力のエンコーディングが事前にわかっているはずです。開始する前に自分で入力文字列に対してUTF-8体操を行うと、必要な出力バッファスペースの量を推測するためのより良い仕事をすることができます。これは上記の2番目のオプションに少し似ていますが、必要なUTF-8体操は本格的なものほど高価ではないため、より最適化されていiconvます。

ただし、そうしないことをお勧めします。入力文字列に対して2つのパスを作成しているので、それほど節約することはできません。作成するコードがはるかに多くなり、次の場合にバッファのサイズが小さくなる可能性があります。体操は完全に正しくありません。

体操については、多かれ少なかれUTF-8デコーダーを実装することであるため、ここでは説明しません。その核となるのは、ビットマスキングとシフトのいくつかの単純なケースですが、関連する詳細があります。セキュリティに影響を与える方法で間違えやすい無効なシーケンスを拒否すること。だからそれをしないでください。

于 2012-11-08T21:27:51.243 に答える