【问题标题】:What actually is the type of C `char **argv` on WindowsWindows 上的 C `char **argv` 实际上是什么类型
【发布时间】:2022-01-28 04:25:03
【问题描述】:

通过阅读 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] != '\0') {
        // 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(例如,如果计算 \0,则为 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。这似乎违反直觉。发生了什么事?

环境

  • Windows 10
  • 终端:Alacritty 和 Windows 默认 cmd(均已尝试以防万一)
  • Mingw-w64 下的 GCC

【问题讨论】:

  • 您误解了您的程序正在做什么和结果。 printf 正在打印提升为 intchar。您的字符在127 ASCII 以上,因此被解释为负数,然后符号扩展为负数ints。然后您使用%x 打印它们,并获得这些的十六进制 2 的补码表示。与其他无关
  • 所以你是对的,char 在某些时候会变成int。但这一点在printf 调用中。由于它是一个可变参数函数,它的参数正在进行默认提升(对于char,它将是int)。
  • 代码中也有一些bug。 (1) 0x%X 应该是0x%zX,因为对应的参数的类型是size_t。 (2) printf("\nTotal length: %u\n", argv[1][length], length); 参数过多。
  • @ValentinO。你可能想看看stackoverflow.com/questions/4101864/…
  • @ValentinO。其实,我错了。 0x%X 可以,但 sizeof char: %u 应该是 sizeof char: %zu。这假设您使用的是 MingW 的替换格式标准 I/O 例程,而不是来自 msvcrt.dll 的例程。

标签: c windows gcc unicode argv


【解决方案1】:

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

但事实并非如此:printf 确实将char 作为int 接收。

当将小于int 的整数类型传递给像printf 这样的可变参数函数时,它会被提升为int。在您的系统上,char 是有符号类型。[1] 给定一个值为 -25 的 char,一个值为 -25 的 int 被传递给 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]对于 Windows 的 en-us 安装,这是 cp1252
  • 还有一个 Wide (W) 版本,用于处理使用 UTF-16le 编码的文本。

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

GetCommandLineW 将命令行提供为 UTF-16le,CommandLineToArgvW 将解析它。


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

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

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

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


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

【讨论】:

  • 好总结!你似乎对这些痛苦的问题有第一手的经验。有趣的是,微软如何改变其操作系统和产品的用户界面,并迫使用户多次重新学习新的 UI,另一方面,从不放弃系统 API 中的错误选择:代码页、16 位 wchar编码、CR/LF、笨拙的长文件名、32 位 LONG 类型……不胜枚举。
  • @chqrlie,Unix 也有可定制的编码。它只是将它们称为语言环境而不是代码页。不同之处在于您必须使用 Windows 一次处理多种编码。这太可怕了。我在脚注 1 中提到的更改使这一切变得更好。 // 当然,使用可变长度编码 (UTF-16) 却没有提供任何工具来使用它,因为可变长度编码很糟糕。 // 我不知道有任何程序不只接受 LF // 您可以使用清单表明您有能力安全地处理长路径。 // 如果要 64 位类型,请使用 uint64_tWindows 或不使用。
  • @chqrlie,大多数编码问题实际上是由于强加在 Windows 上的 unix 问题造成的。 (也就是说,C 的以 unix 为中心的标准库。)如果您只使用 W 函数,您只需处理一种编码 (UTF-16le)。自Win95以来几乎就是这种情况。需要处理系统特定编码的是 unix。
  • 这个解释得很好,谢谢。正如其他人所提到的,我应该使用 windows API 或 windows 自定义主签名以可预测的编码取回输入。
【解决方案2】:

从您的代码和系统上的输出看来:

  • 类型char确实有8位。根据定义,它的大小为 1。 char **argv 是指向 C 字符串指针数组的指针,以空结尾的 char 数组(8 位字节)。
  • char 类型是为您的编译器配置签名的,因此对于超过 127 的值输出 0xFFFFFFE70xFFFFFFE0char 值作为 int 传递给 printf,它将值解释为无符号 %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 由 Unix 团队负责人 Kenneth Thomson 和 Rob Pike 于 1992 年 9 月发明。他们在一夜之间在 plan-9 中实现了它,因为它具有许多与 C 语言字符串兼容的有趣属性。微软已经在他们自己的系统上投资了数百万美元,却忽略了这种更简单的方法,这种方法在今天的网络上已经无处不在。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-05-22
    • 2011-08-14
    • 2011-10-24
    • 2019-05-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多