2012-11-08 64 views
3

我想写一个函数来将UTF8字符串转换为UTF16(小端)。问题是,iconv函数似乎并未让您事先知道需要多少字节来存储输出字符串。简单的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; 
} 

这真的是做到这一点的最好方法是什么?重复的realloc看起来很浪费,但是不知道utf8中的字符序列是什么,以及它们在utf16中会产生什么样的结果,我不知道我能否比2*strlen(utf8)更好地猜测初始缓冲区大小。

回答

4

这是使用iconv的正确方法。

请记住,iconv被设计为能够从任意字符编码重新编码为另一个任意字符编码。它支持任何组合。鉴于此,基本上只有两种方法可以知道输出需要多少空间:

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

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

真的没有其他办法。要么你事先知道输入中有多少字符(不是字节),有多少字符不在BMP中;或者你没有,你必须数它们。

在这种情况下,您碰巧知道输入和输出编码会提前。如果您在开始之前自己在输入字符串上执行一些UTF-8体操操作,则可以更好地猜测您需要的输出缓冲区空间量。这有点像上面的第二种选择,但更加优化,因为必要的UTF-8体操并不像全面的iconv那样昂贵。

但是,让我建议您不要这样做。你仍然会对输入字符串进行两次传递,所以你不会节省那么多,它会为你编写更多的代码,并且它会引入一个bug的可能性,如果体操并不完全正确。

我甚至不会去描述体操,因为它实际上或多或少的实现了一个UTF-8解码器,尽管它的核心只是一些简单的位掩码和位移的例子,有一些细节涉及拒绝无效序列,这些序列很容易出错,并且具有安全隐患。所以不要这样做。

5

将UTF-8转换为UTF-16不会超过数据大小的两倍。最坏的情况是ASCII(1-> 2字节)。 UTF-8中的所有其他BMP代码点需要2或3个字节(因此保持相同大小或在转换为UTF-16时变得更小)。非BMP代码点正好是UTF-8或UTF-16中的4个字节。

因此,您可以消除用于放大缓冲区的浪费,复杂且容易出错的逻辑realloc

顺便说一下,请确保您留出空位终止空间,这将不会被strlen计算在内。

+0

好点重新'strlen',但在我的情况下,我想要一个空终止的输入字符串和输出字符串的非终止缓冲+长度。我没有说清楚。 – craig65535