iconv(3)

iconv 可能是我遇过的最让人困惑的C标准库函数了——因为它竟然一次性的返回 5 个值,如果算上全局变量 errno 的话,那就是 6 个(如果你知道还有哪个 libc 函数能超过它的,请告诉我)。而作为调用者,必须对返回的 6 个值逐一检查,然后,OMG,可能还需要再调用 iconv 继续转码...如此反复。

所以每当我需要在 C 里面 iconv 一个字符串的话,我会去找 php 4.x 的源代码(现在最新应该是 4.4.9),从 ext/iconv/iconv.c 里面 Copy&Paste 一段代码,再自己简单包装一下使用。

在上周我帮一个程序员提供这个函数包装的时候,我不禁开始思考怎么会有一个如此违背 UNIX 设计哲学的函数存在,后来想到两种可能。一个是字符编码转换这件事情,本来就是一个不那么 UNIX 的操作,再牛的天才,也只能如此设计接口;另一个可能是这个函数完全是 UNIX 商业化过程中为了向非 ANSI 字符语种国家推广,而由一个标准化委员会搞出来的怪物。

如果对 iconv 使用的复杂程度还没有一个感性认识的话,从 PHP 搞来的这段东西会帮助理解这一点:

  1. /*
  2. --------------------------------------------------------------------
  3.                   The PHP License, version 3.01
  4. Copyright (c) 1999 - 2008 The PHP Group. All rights reserved.
  5. --------------------------------------------------------------------
  6.  
  7.      "This product includes PHP software, freely available from
  8.      <<a href="http://www.php.net/software/>".
  9. */
  10. #include">http://www.php.net/software/>".
  11. */
  12. #include</a> <stdlib.h>
  13. #include <string.h>
  14. #include <iconv.h>
  15. #include <errno.h>
  16.  
  17. //from PHP 4.4.9 ext/iconv/iconv.c
  18. //为了便于在 blog 上表现,把返回简化成 3, 2, 1, 0, -1, -2, -3
  19. //返回值非零表示转码失败,其中大于零则说明返回的 out 不需要 free
  20. //这段代码只在 Linux 下测试
  21. int _iconv_string(const char *in_p, size_t in_len, char **out, size_t *out_len, const char *in_charset, const char *out_charset)
  22. {
  23.     iconv_t cd;
  24.     size_t in_left, out_size, out_left;
  25.     char *out_p, *out_buf, *tmp_buf;
  26.     size_t bsz, result = 0;
  27.     int retval = 0;
  28.  
  29.     cd = iconv_open(out_charset, in_charset);
  30.     if (cd == (iconv_t)(-1)) {
  31.         if (errno == EINVAL) {
  32.             return 1; // WRONG_CHARSET
  33.         } else {
  34.             return 2;
  35.         }
  36.     }
  37.  
  38.     in_left= in_len;
  39.     out_left = in_len + 32; /* Avoid realloc() most cases */
  40.     out_size = 0;
  41.     bsz = out_left;
  42.     out_buf = (char *)malloc(bsz+1);
  43.     out_p = out_buf;
  44.  
  45.     while (in_left > 0) {
  46.         result = iconv(cd, (char **) &in_p, &in_left, (char **) &out_p, &out_left);
  47.         out_size = bsz - out_left;
  48.         if (result == (size_t)(-1)) {
  49.             if (errno == E2BIG && in_left > 0) {
  50.                 /* converted string is longer than out buffer */
  51.                 bsz += in_len;
  52.                 tmp_buf = (char*)realloc(out_buf, bsz+1);
  53.                 if (tmp_buf != NULL) {
  54.                     out_p = out_buf = tmp_buf;
  55.                     out_p += out_size;
  56.                     out_left = bsz - out_size;
  57.                     continue;
  58.                 }
  59.             }
  60.         }
  61.         break;
  62.     }
  63.     if (result != (size_t)(-1)) {
  64.         /* flush the shift-out sequences */
  65.         for (;;) {
  66.             result = iconv(cd, NULL, NULL, (char **) &out_p, &out_left);
  67.             out_size = bsz - out_left;
  68.             if (result != (size_t)(-1)) {
  69.                 break;
  70.             }
  71.             if (errno == E2BIG) {
  72.                 bsz += 16;
  73.                 tmp_buf = (char *)realloc(out_buf, bsz);
  74.                 if (tmp_buf == NULL) {
  75.                     break;
  76.                 }
  77.                 out_p = out_buf = tmp_buf;
  78.                 out_p += out_size;
  79.                 out_left = bsz - out_size;
  80.             } else {
  81.                 break;
  82.             }
  83.         }
  84.     }
  85.     iconv_close(cd);
  86.     if (result == (size_t)(-1)) {
  87.         switch (errno) {
  88.             case EINVAL:
  89.                 retval = -1;
  90.                 break;
  91.             case EILSEQ:
  92.                 retval = -2;
  93.                 break;
  94.             case E2BIG:
  95.                 retval = -3;
  96.                 break;
  97.             default:
  98.                 free(out_buf);
  99.                 return 3;
  100.         }
  101.     }
  102.     *out_p = '\0';
  103.     *out = out_buf;
  104.     *out_len = out_size;
  105.     return retval;
  106. }
  107.  
  108. //qyb wrap _iconv_string
  109. //由调用者负责 free 返回值
  110. char *iconv_string(const char *string, const char *in_charset, const char *out_charset)
  111. {
  112.     char *result;
  113.     int retval;
  114.     size_t in_len, out_len;
  115.     in_len = strlen(string);
  116.     retval = _iconv_string(string, in_len, &result, &out_len, in_charset, out_charset);
  117.     if (retval > 0) {
  118.         return NULL;
  119.     }
  120.     return result;
  121. }
Topic: 技术

评论

不在标准库里
没记错的话libiconv支持的平台不多……

也可以用mbtowc,wctomb,setlocale代替iconv

现在已经是最新的 POSIX (IEEE Std 1003.1, 2004) 的一部分了