为什么在 C++ 中,多字节 UTF-8 字符串中的某些字符由负数表示?

Why in C++ are some characters in a multibyte UTF-8 string represented by negative numbers?

提问人:Ilya Chalov 提问时间:4/29/2023 更新时间:4/29/2023 访问量:163

问:

以下 C++ 源代码我在“Windows 10”和“Ubuntu”(通过“WSL 2”)中编译和运行:

#include <cstring>
#include <iostream>

int main()
{
    char str[] = "Hello, привет, 😎!";

    std::cout << str << "\n\n";

    for (int i = 0; i < std::strlen(str); i++) {
        std::cout << (int) str[i] << ' ';
    } std::cout << "\n\n";

    for (int i = 0; i < std::strlen(str); i++) {
        std::cout << std::hex << (int) str[i] << ' ';
    } std::cout << "\n\n";

    for (int i = 0; i < std::strlen(str); i++) {
        std::cout << std::hex << (str[i] & 0xff) << ' ';
    } std::cout << '\n';

    return 0;
}

我将此源代码保存在 UTF-8 编码的文件中,无需 BOM。在 Windows 10 中,我从命令行使用“Microsoft C++ Build Tools”中的 MSVC 编译器 (cl.exe)。在“Ubuntu”(通过“WSL 2”)中,我使用来自命令行设置的“GCC”的 g++ 编译器。chars.cppcl /EHsc /utf-8 "chars.cpp"g++ /mnt/c/Users/Илья/source/repos/test/chars.cpp -o chars

我得到了以下结果(在“Windows 10”中,您需要使用命令在控制台中配置代码页,在“Ubuntu”(通过“WSL 2”)中,这不是必需的):cmd.exechcp 65001

Hello, привет, 😎!

72 101 108 108 111 44 32 -48 -65 -47 -128 -48 -72 -48 -78 -48 -75 -47 -126 44 32 -16 -97 -104 -114 33

48 65 6c 6c 6f 2c 20 ffffffd0 ffffffbf ffffffd1 ffffff80 ffffffd0 ffffffb8 ffffffd0 ffffffb2 ffffffd0 ffffffb5 ffffffd1 ffffff82 2c 20 fffffff0 ffffff9f ffffff98 ffffff8e 21

48 65 6c 6c 6f 2c 20 d0 bf d1 80 d0 b8 d0 b2 d0 b5 d1 82 2c 20 f0 9f 98 8e 21

我很好奇为什么用负数来表示某些字符。我试图在 cppreference.com 中找到解释,并在那里阅读了两篇文章:

https://en.cppreference.com/w/cpp/language/types,引用:

char - 字符表示的类型,可以在目标系统上最有效地处理(具有与有符号字符或无符号字符相同的表示和对齐方式,但始终是不同的类型)。多字节字符字符串使用此类型来表示代码单元。对于范围 [0, 255] 中 unsigned char 类型的每个值,将该值转换为 char,然后再转换回 unsigned char 将生成原始值。(从 C++ 11 开始)char 的有符号取决于编译器和目标平台:ARM 和 PowerPC 的默认值通常为无符号,x86 和 x64 的默认值通常为有符号。

https://en.cppreference.com/w/cpp/string/multibyte

但我在那里没有找到直接的解释。

我的问题。某些字符用负数表示的目的是什么?它是在标准中还是在特定于系统的情况下?

C++ 字符串 UTF-8 char null 终止

评论

8赞 273K 4/29/2023
char在编译器中。signed char
0赞 273K 4/29/2023
某些字符用负数表示的目的是什么?1.因为有些旧电脑没有无符号数字。2. 7 位正位 0..127 (ASCII 7) 对于美国 IT 来说已经足够了。
0赞 Some programmer dude 4/29/2023
C++规范说可以是有符号的,也可以是无符号的。并且由实现(即编译器)来决定。如前所述,您的编译器已决定在您的系统上进行签名。这就是为什么 和 被认为是三种不同的类型,而不是像任何其他整数类型那样的两种。charcharcharsigned charunsigned char
0赞 yeputons 4/29/2023
UTF-8 是一种以 8 位字节定义的编码,在每个字节中,只有位的顺序很重要(从最高有效到最低有效),严格来说,字节不被视为“数字”。但仍然有位的顺序。当您查看几乎任何编程语言中的字节时,您通常会看到一个数字。通常为:无符号数 (0..255) 或双补码编码数 (-128..127)。程序员可以选择如何看待字节。 在C++中很可能是并使用后者。但是 UTF-8 不适用于“数字”。charsigned char
1赞 Remy Lebeau 4/29/2023
请注意,引入了 C++20(类似于 和 ,但与 不同)和 ,专门用于处理 UTF-8,使其与 UTF-16 的 C++11 的 / 和 UTF-32 的 / 相提并论。char8_tunsigned charstd::u8stringchar16_tstd::u16stringchar32_tstd::u32string

答:

3赞 Ilya Chalov 4/29/2023 #1

感谢人们的评论,我想我明白这里发生了什么。如果我错了,请纠正我。

据我了解,C++ 语言标准允许编译器解释为 or .charsigned charunsigned char

默认情况下,MSVC 和 g++ 编译器会解释为。因此,我的程序中的类型可以表示 范围内的值。考虑西里尔文小写字母的例子:在Unicode表中是;在 UTF-8 编码中,这是 2 个字节(十六进制)或 (dec)。charsigned charchar-128..127'п'U+043Fd0 bf208 191

由于这些数字不适合这个范围,它们被转换为(208 - 256, 191 - 256)。这就是所有字符的处理方式。事实证明,如果字符代码落入 ,则它不会改变(ASCII表)。208 191-128..127-48 -650..127

可以使用特殊开关(选项)更改 MSVC 和 g++ 编译器的此行为。MSVC 编译器有一个选项/J

cl /EHsc /utf-8 /J "chars.cpp"

对于 g++ 编译器,有一个选项-funsigned-char

g++ /mnt/c/Users/Илья/source/repos/test/chars.cpp -o chars -funsigned-char

因此,使用新选项编译和运行相同的源代码后将给出不同的结果:

Hello, привет, 😎!

72 101 108 108 111 44 32 208 191 209 128 208 184 208 178 208 181 209 130 44 32 240 159 152 142 33

48 65 6c 6c 6f 2c 20 d0 bf d1 80 d0 b8 d0 b2 d0 b5 d1 82 2c 20 f0 9f 98 8e 21

48 65 6c 6c 6f 2c 20 d0 bf d1 80 d0 b8 d0 b2 d0 b5 d1 82 2c 20 f0 9f 98 8e 21

使用新选项,编译器将解释为 (range ),因此字符串表示中没有负数。charunsigned char0..255

评论

0赞 RandomBits 4/29/2023
你说得完全正确。
1赞 BoP 4/29/2023
迂腐、、因此,type 的行为可以类似于有符号或无符号,但与其他两者中的任何一个都不同。在为 char 类型编写重载函数的几率下,您会注意到其中的差异。charsigned charunsigned charchar