Windows 上 C `char **argv` 的实际类型是什么

What actually is the type of C `char **argv` on Windows

通过阅读 MSDN 或 n1256 委员会草案中的文档,我的印象是 char 始终正好是 <limits.h> 中定义的 CHAR_BIT 位]. 如果 CHAR_BIT 设置为 8,则一个字节为 8 位长,char.

也是如此

测试代码

给定以下 C 代码:

int main(int argc, char **argv) {
    int length = 0;
    while (argv[1][length] != '[=11=]') {
        // print the character, its hexa value, and its size
        printf("char %u: %c\tvalue: 0x%X\t sizeof char: %u\n",
                length,
                argv[1][length],
                argv[1][length],
                sizeof argv[1][length]);
        length++;
    }
    printf("\nTotal length: %u\n", length);
    printf("Actual char size: %u\n", CHAR_BIT);
     
    return 0;
}

鉴于包含非 ASCII 字符的参数,例如 çà.

,我不确定行为会是什么

这些字符应该是 UTF-8,所以每个都写成多个字节。我希望它们作为单独的字节进行处理,这意味着 ça 的长度为 3(如果计算 [=22=] 则为 4)并且在打印时,我会得到每个字节一行,所以3 行而不是 2 行(这将是实际的拉丁字符数)。

输出

$ gcc --std=c99 -o program.exe win32.c
$ program.exe test_çà
char 0: t       value: 0x74      sizeof char: 1
char 1: e       value: 0x65      sizeof char: 1
char 2: s       value: 0x73      sizeof char: 1
char 3: t       value: 0x74      sizeof char: 1
char 4: _       value: 0x5F      sizeof char: 1
char 5: τ       value: 0xFFFFFFE7        sizeof char: 1
char 6: α       value: 0xFFFFFFE0        sizeof char: 1

Total length: 7
Actual char size: 8

问题

幕后可能发生的事情是 char **argv 变成了 int **argv。这可以解释为什么第 5 行和第 6 行有一个写入 4 个字节的十六进制值。

  1. 真的是这样吗?
  2. 这是标准行为吗?
  3. 为什么字符 5 和 6 不是输入的内容?
  4. CHAR_BIT == 8sizeof(achar) == 1somechar = 0xFFFFFFE7。这似乎违反直觉。发生什么事了?

环境

从您的代码和系统的输出来看,似乎是:

  • type char确实有8位。根据定义,它的大小为 1。 char **argv 是指向 C 字符串指针数组的指针,char(8 位字节)的空终止数组。
  • char 类型已为您的编译器配置签名,因此对于超过 127 的值,输出 0xFFFFFFE70xFFFFFFE0char 值作为 intprintf,它将 %X 转换的值解释为无符号。该行为在技术上未定义,但实际上负值在用作无符号时会偏移 232。您可以配置 gcc 使 char 类型默认无符号 -funsigned-char,这是一个更安全的选择,也更符合 C 库行为。
  • 2 个非 ASCII 字符 çà 被编码为单个字节 E7 和 E0,这对应于 Microsoft 的专有编码,它们的代码页 Windows-1252,而不是您假设的 UTF-8。

情况最终令人困惑:命令行参数被传递给使用 Windows-1252 代码页编码的程序,但终端使用旧的 MS/DOS code page 437 来与历史内容兼容。因此,您的程序输出它接收到的字节作为命令行参数,但终端显示来自 CP437 的相应字符,即 τα.

Microsoft 就非 ASCII 字符的编码做出了历史性的决定,这些决定在今天的标准看来已经过时,令人遗憾的是,他们似乎坚持其他供应商出于充分理由而避开的繁琐选择。在这种环境下用C编程是一条坎坷的道路。

UTF-8 于 1992 年 9 月由 Unix 团队负责人 Kenneth Thomson 和 Rob Pike 发明。他们一夜之间在 plan-9 中实现了它,因为它具有许多与 C 语言字符串兼容的有趣属性。微软已经在他们自己的系统上投入了数百万美元,而忽略了这种更简单的方法,这种方法如今在网络上已经无处不在。

不,它不是作为 int 的数组接收的。

但这与事实相去不远:printf 确实收到 char 作为 int

当将小于 int 的整数类型传递给像 printf 这样的可变参数函数时,它会被提升为 int。在您的系统上,char 是有符号类型。[1] 给定一个值为 -25 的 char,一个值为 -25 的 int的 -25 被传递给 printf%u 需要一个 unsigned int,因此它将值为 -25 的 int 视为 unsigned int,打印 0xFFFFFFE7.

一个简单的修复:

printf("%X\n", (unsigned char)c);   // 74 65 73 74 5F E7 E0

但是为什么你一开始就得到了 E7 和 E0?

每个处理文本的 Windows 系统调用都有两个版本:

  • 处理使用系统的活动代码页编码的文本的“ANSI”(A) 版本。[2] 对于 en-us 安装Windows,这是cp1252.
  • 还有一个 Wide (W) 版本,它处理使用 UTF-16le 编码的文本。

正在使用 GetCommandLineA 从系统获取命令行,GetCommandLineA 版本。您的系统使用 cp1252 作为其 ACP。使用cp1252编码,ç为E7,à为E0。

GetCommandLineW will provide the command line as UTF-16le, and CommandLineToArgvW 将解析它。


最后,为什么E7和E0显示为τα

终端编码与ACP不同!在你的机器上,它似乎是 437。(这可以更改。)使用 cp437 编码,τ 是 E7,α 是 E0。

发出 chcp 1252 会将终端的编码设置为 cp1252,与 ACP 匹配。 (UTF-8 为 65001。)

您可以使用GetConsoleCP (for input) and GetConsoleOutputCP查询终端的编码(用于输出)。是的,显然他们可以不同?我不知道这在实践中会如何发生。


  1. char 是有符号类型还是无符号类型由编译器决定。
  2. 从 Windows 10,版本 1903(2019 年 5 月更新)开始,每个程序都可以 changed