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 搞来的这段东西会帮助理解这一点:

/*
--------------------------------------------------------------------
                  The PHP License, version 3.01
Copyright (c) 1999 - 2008 The PHP Group. All rights reserved.
--------------------------------------------------------------------

     "This product includes PHP software, freely available from
     <http://www.php.net/software/>".
*/


#include <stdlib.h>
#include <string.h>
#include <iconv.h>
#include <errno.h>

//from PHP 4.4.9 ext/iconv/iconv.c
//为了便于在 blog 上表现,把返回简化成 3, 2, 1, 0, -1, -2, -3
//返回值非零表示转码失败,其中大于零则说明返回的 out 不需要 free
//这段代码只在 Linux 下测试
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)
{
    iconv_t cd;
    size_t in_left, out_size, out_left;
    char *out_p, *out_buf, *tmp_buf;
    size_t bsz, result = 0;
    int retval = 0;

    cd = iconv_open(out_charset, in_charset);
    if (cd == (iconv_t)(-1)) {
        if (errno == EINVAL) {
            return 1; // WRONG_CHARSET
        } else {
            return 2;
        }
    }

    in_left= in_len;
    out_left = in_len + 32; /* Avoid realloc() most cases */
    out_size = 0;
    bsz = out_left;
    out_buf = (char *)malloc(bsz+1);
    out_p = out_buf;

    while (in_left > 0) {
        result = iconv(cd, (char **) &in_p, &in_left, (char **) &out_p, &out_left);
        out_size = bsz - out_left;
        if (result == (size_t)(-1)) {
            if (errno == E2BIG && in_left > 0) {
                /* converted string is longer than out buffer */
                bsz += in_len;
                tmp_buf = (char*)realloc(out_buf, bsz+1);
                if (tmp_buf != NULL) {
                    out_p = out_buf = tmp_buf;
                    out_p += out_size;
                    out_left = bsz - out_size;
                    continue;
                }
            }
        }
        break;
    }
    if (result != (size_t)(-1)) {
        /* flush the shift-out sequences */
        for (;;) {
            result = iconv(cd, NULL, NULL, (char **) &out_p, &out_left);
            out_size = bsz - out_left;
            if (result != (size_t)(-1)) {
                break;
            }
            if (errno == E2BIG) {
                bsz += 16;
                tmp_buf = (char *)realloc(out_buf, bsz);
                if (tmp_buf == NULL) {
                    break;
                }
                out_p = out_buf = tmp_buf;
                out_p += out_size;
                out_left = bsz - out_size;
            } else {
                break;
            }
        }
    }
    iconv_close(cd);
    if (result == (size_t)(-1)) {
        switch (errno) {
            case EINVAL:
                retval = -1;
                break;
            case EILSEQ:
                retval = -2;
                break;
            case E2BIG:
                retval = -3;
                break;
            default:
                free(out_buf);
                return 3;
        }
    }
    *out_p = '\0';
    *out = out_buf;
    *out_len = out_size;
    return retval;
}

//qyb wrap _iconv_string
//由调用者负责 free 返回值
char *iconv_string(const char *string, const char *in_charset, const char *out_charset)
{
    char *result;
    int retval;
    size_t in_len, out_len;
    in_len = strlen(string);
    retval = _iconv_string(string, in_len, &result, &out_len, in_charset, out_charset);
    if (retval > 0) {
        return NULL;
    }
    return result;
}

GNU iconv

不在标准库里
没记错的话libiconv支持的平台不多……
也可以用mbtowc,wctomb,setlocale代替iconv

现在已经是最新的 POSIX (IEEE Std

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