使用iconv进行简单的UTF8-> UTF16字符串转换

我想写一个函数将UTF8字符串转换为UTF16(little-endian)。 问题是, 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, ie 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)

这是使用iconv的正确方法。

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

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

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

真的没有别的办法。 要么你事先知道输入中有多少个字符(而不是字节),哪些字符不在BMP中; 或者你没有,你必须计算它们。

在这种情况下,您碰巧知道输入和输出编码将提前是什么。 如果你在开始之前在输入字符串上做一些UTF-8体操,你可以更好地猜测你需要的输出缓冲空间量。 这有点像上面的第二个选项,但更加优化,因为必要的UTF-8体操并不像完整的iconv那样昂贵。

但是,我建议您不要这样做。 你仍然要对输入字符串进行两次传递,这样你就不会节省那么多,这将是你编写更多的代码,并且它引入了一个错误的可能性,如果缓冲区可能会变小体操不太对劲。

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

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

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

顺便说一下,确保为空终止留出空间,这不会被strlen计算。