为什么 printf 在使用区域设置时可以显示非 ASCII 字符"C"?

Why printf can display non-ASCII characters when "C" locale is used?

本文关键字:显示 ASCII 字符 printf 区域 设置 为什么      更新时间:2023-10-16

注意:我问的是在Microsoft Visual c++ 2008上实现定义的行为(可能在2005+上相同)。操作系统:简体中文安装Win7.

当我执行非ascii I/O w/printf时,我感到惊讶。例如

   // This won't be necessary as it's the system default code page.
   //system("chcp 936");
   
   // NULL to show current locale, which is "C"
   printf ("%sn", setlocale(LC_ALL, NULL));
   printf ("中n");
   printf ("%sn", setlocale(LC_ALL, "English"));
   printf ("中n");
输出:

Active code page: 936
C
中
English_United States.1252
?D

调试器中的内存占用显示,"中"被编码为两个字节:0xD6, 0xD0,这是代码页936中该字符的码点,对于简体中文。它不应该在"C" locale的代码点范围内,最有可能的是0x0 ~ 0x7F

问题:

为什么在"C"语言环境吗?所以我猜地点和printf没有关系?但是,我要问,为什么它不能显示当改变到"English"区域设置,这也不同于936?有趣吗?

编辑:

我将标准输出重定向到一个文件并进行了一些测试。它表明,无论设置了什么语言环境,正确的字符"中"都会保存在文件中。它表明setlocale()连接到控制台显示字符的方式,这与我对它如何工作的理解相矛盾:printf将字节/代码点放入控制台的输入缓冲区,它使用自己的代码页(chcp返回)解释这些字节。

936是相当棘手的代码页,它允许2个符号字符(类似于UTF-8)。例如Cyrillic(866) -不允许双字节字符,其行为将与"English"相同。

因此,当您使用默认(936)代码页时,它知道如何处理2符号字符,而"English"仅处理0x0 ~ 0x7f

让我也回答为什么wprintf(L"中")失败。控制台应用程序和windows窗口应用程序有很大的区别,它们使用不同的代码页下面是控制台和窗口之间的匹配:

DOS   |   Windows
------+----------
850   |  1252
936   | 54936
866   |  1251

所以如果你想在控制台看到正确的符号首先使用WideCharToMultiByte -这提供了预期的转换,以允许控制台工作在936

C语言环境完全按照给定的方式打印出字符串这一事实并不奇怪。这就是我所期望的。令人惊讶的是,英语语言环境会做一些不同的事情。

根据MSDN上的语言环境文档,语言环境对printf的唯一影响应该是确定数字值的基数字符(即小数点)。

我怀疑这可能是微软编译器的一个bug。或者至少它是未记录的行为。

值得注意的是,在我的编译器(Borland)上,区域设置对这些字符串的输出没有影响。但是它确实影响基数

OK。对于默认的"C"区域设置,CRT假定传递给printf的字符不需要任何转换。这是有原因的,因为ASCII字符几乎总是属于执行系统的基本字符集(在不同的Windows代码页之间共享)。当切换到"English"时,它假设输入是在代码页1252中编码的,因此尝试执行从"English"到"Chinese"的转换,这是控制台使用的区域设置。但是CRT无法在代码页1252中找到字符。这就是为什么它输出一个问号。

当重定向到一个文件时,CRT知道它并且不会进行转换,因为不再使用控制台代码页。它只是按原样传递字节。如何解释这些字节取决于您使用的程序(例如。(是否关心BOM),当您打开文件时

参考这个MSDN论坛链接:为什么当使用"C"区域设置时,printf可以显示非ascii字符?