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;
}

这真的是最好的方法吗?重复reallocs 似乎很浪费,但不知道 utf8 中可能包含哪些字符序列,以及它们在 utf16 中会产生什么结果,我不知道是否可以对初始缓冲区大小做出比2*strlen(utf8).

4

2 回答 2

6

将 UTF-8 转换为 UTF-16 不会使数据大小增加一倍以上。最坏情况是 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它旨在能够从任意字符编码重新编码为另一种任意字符编码。它支持任意组合。鉴于此,基本上只有两种方法可以知道输出需要多少空间:

  1. 猜一下。进行转换,并在必要时增加您的猜测。
  2. 进行两次转换。第一次,只是计数,丢弃输出。分配您计算的空间总量,然后再次进行转换。

首先是你做什么。第二个显然有你必须做两次工作的缺点。(顺便说一句,您可以iconv通过使用局部变量中的暂存器缓冲区作为第一遍的输出缓冲区来执行第二种方法。)

真的没有别的办法了。要么您事先知道输入中有多少个字符(不是字节),有多少是/不在 BMP 中;或者你没有,你必须数一数。

在这种情况下,您碰巧提前知道输入和输出编码是什么。如果您在开始之前自己对输入字符串执行一些 UTF-8 操作,您可以更好地猜测所需的输出缓冲区空间量。这有点像上面的第二个选项,但更优化,因为必要的 UTF-8 体操并不像成熟的iconv.

不过,我建议您不要这样做。您仍然需要对输入字符串进行两次传递,因此您不会节省那么多,您需要编写更多代码,并且它引入了一个错误的可能性,即如果体操不太对劲。

我什至不打算描述体操,因为它实际上或多或少是实现一个 UTF-8 解码器,虽然它的核心只是一些简单的位掩码和移位案例,但有一些相关的细节以具有安全隐患的方式拒绝容易出错的无效序列。所以不要这样做。

于 2012-11-08T21:27:51.243 回答