跨平台C应用程序支持UTF8

时间:2010-12-21 16:38:48

标签: c utf-8 internationalization cross-platform

我正在开发一个跨平台C(C89标准)应用程序,它必须处理UTF8文本。我只需要基本的字符串操作函数,如substrfirstlast等。

问题1

是否有实现上述功能的UTF8库?我已经看过ICU了,这对我的要求来说太大了。我只需要支持UTF8。

我找到了一个UTF8解码器here。以下函数原型来自该代码。

void utf8_decode_init(char p[], int length);

int utf8_decode_next();

初始化函数采用字符数组,但utf8_decode_next()返回int。这是为什么?如何使用printf等标准函数打印此函数返回的字符?该函数正在处理字符数据以及如何将其分配给整数?

如果上述解码器不适合生产代码,您有更好的推荐吗?

问题2

我也很困惑,阅读的文章说,对于unicode,你需要使用wchar_t。根据我的理解,这不是必需的,因为普通的C字符串可以保存UTF8值。我通过查看SQLite和git的源代码验证了这一点。 SQLite具有以下typedef。

typedef unsigned char u8

我的理解是否正确?另外为什么需要unsigned char

6 个答案:

答案 0 :(得分:4)

  1. utf_decode_next()函数返回下一个Unicode代码点。由于Unicode是一个21位字符集,因此它不能返回小于int的任何内容,并且可以认为从技术上讲,它应该是long,因为int可能是16位数量。实际上,该函数会返回一个UTF-32字符。

    您需要查看C89的C94宽字符扩展名以打印宽字符(wprintf()<wctype.h><wchar.h>)。但是,单独的宽字符不能保证是UTF-8甚至是Unicode。您很可能无法以utf8_decode_next()方式打印字符,但这取决于您的可移植性要求。您必须移植的系统范围越广,所有工作的可能性就越小。如果您可以移植地编写UTF-8,您可以将UTF-8字符串(不是从utf8_decode_next()获得的UTF-32字符数组)发送到常规打印功能之一。 UTF-8的优点之一是它可以被很大程度上无知的代码操纵。

  2. 您需要了解一个4字节wchar_t可以在一个单元中保存任何Unicode代码点,但UTF-8可能需要一到四个8位字节(1-4个单位)存储)保存单个Unicode代码点。在某些系统上,我认为wchar_t可以是16位(short)整数。在这种情况下,您被迫使用UTF-16,它使用两个存储单元和代理编码基本多语言平面(BMP,代码点U + 0000 .. U + FFFF)之外的Unicode代码点。

    使用unsigned char让生活更轻松;普通char经常签名。负数会让生活变得比我需要的更困难(而且,相信我,如果不增加复杂性就很难)。

答案 1 :(得分:4)

使用UTF-8进行字符或子字符串搜索时,不需要任何特殊的库例程。 strstr可以完成您需要的一切。这就是UTF-8的全部要点以及它为满足而发明的设计要求。

答案 2 :(得分:2)

GLib具有quite a few相关功能,可以独立于GTK +使用。

答案 3 :(得分:1)

Unicode中有超过100,000个字符。在大多数C实现中,char有256个可能的值。

因此,UTF-8使用多个char来编码每个字符,解码器需要一个大于char的返回类型。

wchar_t是一个比char更大的类型(嗯,它不会 更大,但它通常是)。它表示实现定义的宽字符集的字符。在某些实现中(最重要的是,Windows使用代理对用于“基本多语言平面”之外的字符),它仍然不足以表示任何Unicode字符,这可能是您引用的解码器使用{{1}的原因}。

您无法使用int打印宽字符,因为它处理printfchar处理wprintf,因此如果宽字符集是unicode,并且系统上wchar_twchar_t(就像在linux上一样),那么{{1}和朋友将打印解码器输出而无需进一步处理。否则就不会。

在任何情况下,您都无法移植打印任意unicode字符,因为无法保证终端可以显示它们,甚至宽字符集与Unicode有任何关联。

SQLite可能使用了int,因此:

  • 他们知道签名 - 它的实现定义了wprintf是否已签名。
  • 他们可以进行右移并分配超出范围的值,并在所有C实现中获得一致且定义的结果。实施unsigned char的行为比char更为自由。

答案 4 :(得分:0)

普通C字符串适用于存储utf8数据,但您无法在utf8字符串中轻松搜索子字符串。这是因为使用utf8编码编码为字节序列的字符可以是1到4个字节的任何位置,具体取决于字符。即“字符”不等同于utf8的“字节”,就像它是ASCII一样。

为了进行子字符串搜索等,您需要将其解码为用于表示Unicode字符的某种内部格式,然后对其进行子字符串搜索。由于远远超过Unicode 256个字符,因此字节(或字符)是不够的。这就是您找到的库使用整数的原因。

至于你的第二个问题,可能只是因为谈论负面字符没有意义,所以它们也可以被指定为“无符号”。

答案 5 :(得分:0)

我已经实施了substr&amp;支持UTF8字符的length个函数。此代码是SQLite使用的修改版本。

以下宏循环输入文本并跳过所有多字节序列字符。 if条件检查这是一个多字节序列,并且其中的循环递增input,直到它找到下一个头字节。

#define SKIP_MULTI_BYTE_SEQUENCE(input) {              \
    if( (*(input++)) >= 0xc0 ) {                       \ 
    while( (*input & 0xc0) == 0x80 ){ input++; }       \
  }                                                    \
}

substrlength是使用此宏实现的。

typedef unsigned char utf8;

<强> SUBSTR

void *substr(const utf8 *string, 
             int start, 
             int len, 
             utf8 **substring)
{
    int bytes, i;
    const utf8 *str2;
    utf8 *output;

    --start;
    while( *string && start ) {
        SKIP_MULTI_BYTE_SEQUENCE(string);
        --start;
    }

    for(str2 = string; *str2 && len; len--) {
        SKIP_MULTI_BYTE_SEQUENCE(str2);
    }

    bytes = (int) (str2 - string);
    output = *substring;
    for(i = 0; i < bytes; i++) {
        *output++ = *string++;
    }
    *output = '\0';
}

<强>长度

int length(const utf8 *string)
{
    int len;
    len = 0;
    while( *string ) {
        ++len;
        SKIP_MULTI_BYTE_SEQUENCE(string);
    }
    return len;
}