文字 UTF-8 字符的数值

Numeric value of literal UTF-8 characters

我正在研究一个字符串转义函数,它将像 \uxxxx(其中 xxxx 是一个十六进制值)这样的文字序列转换为相应值的字节。我打算让函数获取 xxxx 序列的前两个字符,计算字节值,并与第二个序列相同。

但是我 运行 使用文字类型的 UTF-8 字符得到了意想不到的结果。以下说明了我的问题:

#include <stdio.h>

int main()
{
    unsigned char *str1 = "abcĢ";
    unsigned char *str2 = "abc\x01\x22";
    for (unsigned i = 0; i < 5; i++)
        printf ("String 1 character #%u: %x\n", i, str1[i]);
    for (unsigned i = 0; i < 5; i++)
        printf ("String 2 character #%u: %x\n", i, str2[i]);

    return 0;
}

输出:

String 1 character #0: 61
String 1 character #1: 62
String 1 character #2: 63
String 1 character #3: c4
String 1 character #4: a2
String 2 character #0: 61
String 2 character #1: 62
String 2 character #2: 63
String 2 character #3: 1
String 2 character #4: 22

Unicode 字符 Ģ 的十六进制值为 \x0122,因此我希望字节 #3 和 #4 分别为 \x01x22

c4a2从何而来?我想我不明白字符串中的多字节字符是如何在 C 中编码的。任何帮助将不胜感激。

Unicode character Ģ has e hex value of \x0122, so I would expect bytes #3 and #4 to be \x01 and \x22 respectively.

Where do c4 and a2 come from?

在 Unicode 中,Ģ 是代码点 U+0122 LATIN CAPITAL LETTER G WITH CEDILLA,在 UTF-8 中编码为字节 0xC4 0xA2

您的源文件保存为 UTF-8,或者您的编译器配置为以 UTF-8 保存字符串文字。无论哪种方式,在您的 str1 字符串中,文字 Ģ 都存储为 UTF-8。因此:

unsigned char *str1 = "abcĢ";

大致相当于:

unsigned char literal[] = {'a', 'b', 'c', 0xC4, 0xA2, '[=11=]'};
unsigned char *str1 = &literal[0];

在转义序列中,整个序列表示一个单个数值。因此,\x01\x22 分别表示单独的数值 0x01 十六进制(1 十进制)和 0x22 十六进制(34 十进制)。因此:

unsigned char *str2 = "abc\x01\x22";

大致相当于:

unsigned char literal[] = {'a', 'b', 'c', 0x01, 0x22, '[=13=]'};
unsigned char *str2 = &literal[0];

您只是输出 str1str2 指向的字符串的原始字节。

转义序列\u0122表示数值0x0122十六进制(290十进制),在Unicode中是代码点U+0122,因此在C4 A2中UTF-8。所以,如果你有这样的输入字符串:

const char *str = "abc\u0122"; // {'a', 'b', 'c', '\', 'u', '0', '1', '2', '2', '[=14=]'}

而你想解码为UTF-8,你需要检测"\u"前缀,提取下面的"0122"子串,将其作为十六进制数解析为整数,解释该整数作为 Unicode 代码点,并将其转换为 UTF-8(abc 已经是 UTF-8 中的有效字符)。

UTF-8 不能以将大值分解为单个字节的简单方式工作,因为它会产生歧义。怎么区分"\u4142" (䅂)和两个字符串"AB"

从 Unicode 代码点数字生成 UTF-8 字节字符串的规则非常简单,并且消除了歧义。给定任何字节值序列,它要么定义明确的代码点,要么是无效序列。

这是一个将单个 Unicode 代码点值转换为 UTF-8 字节序列的简单函数。

void codepoint_to_UTF8(int codepoint, char * out)
/* out must point to a buffer of at least 5 chars. */
{
    if (codepoint <= 0x7f)
        *out++ = (char)codepoint;
    else if (codepoint <= 0x7ff)
    {
        *out++ = (char)(0xc0 | ((codepoint >> 6) & 0x1f));
        *out++ = (char)(0x80 | (codepoint & 0x3f));
    }
    else if (codepoint <= 0xffff)
    {
        *out++ = (char)(0xe0 | ((codepoint >> 12) & 0x0f));
        *out++ = (char)(0x80 | ((codepoint >> 6) & 0x3f));
        *out++ = (char)(0x80 | (codepoint & 0x3f));
    }
    else
    {
        *out++ = (char)(0xf0 | ((codepoint >> 18) & 0x07));
        *out++ = (char)(0x80 | ((codepoint >> 12) & 0x3f));
        *out++ = (char)(0x80 | ((codepoint >> 6) & 0x3f));
        *out++ = (char)(0x80 | (codepoint & 0x3f));
    }
    *out = 0;
}

请注意,此函数不进行错误检查,因此如果您为其提供的输入超出 0 到 0x10ffff 的有效 Unicode 范围,它将生成不正确(但仍然有效)的 UTF-8 序列。