当前位置: 首页 > ops >正文

Windows端的C函数setlocale、printf与wprintf打印中文字符谜局小解

Windows端的C函数setlocale、printf与wprintf打印中文字符谜局小解

背景:区域(locale)设置

在现代操作系统中,为了适配各国用户的文化传统,包括语言字符集、时间日期和货币表达方式等方面的差异,都存在locale设置。用户在安装操作系统时便会选择自己所在国家/地区,作为系统的默认区域设置。在中国,Windows系统的区域设置便是:Chinese (Simplified)_China.936,包括“936代码页+GB2312字符集+年月日时分日期顺序+货币单位为¥”。顺带一提,在Linux系统上,默认的区域设置是“C.UTF-8”,使用Unicode字符集和UTF-8编码。

对于Windows开发者来说,在Visual Studio中编译好的程序默认的区域设置(“C”,仅支持ASCII字符和编码)和操作系统并不一致,因此在控制台I/O中文字符时通常需要进行区域设置。特别是使用wchar_t类型的字符时,需要调用wprintf函数打印字符串,这种场景下调用setlocale函数是必需的。

现象观察:调用setlocale前后打印中文字符串效果

我们这里在讨论仅Windows端的状况

在Windows平台上,标准库函数printf用于输出常规字符串(char*),标准库函数wprintf用于输出宽字符串(wchar*)。有时,开发者想在控制台输出中文信息,而又苦于在在不同代码页的控制台中难以统一正常输出。我们以默认的936代码页和65001代码页为例,进行实验。测试代码如下:

#include<stdio.h>
#include<stdlib.h>int main()
{printf("1.中文测试printf\n");wprintf(L"2.中文测试wprintf\n");setlocale(LC_CTYPE, "");/// Windows中文平台默认为 GBKprintf("3.中文测试printf\n");wprintf(L"4.中文测试wprintf\n");return 0;
}

将编译生成的exe在两个代码页的控制台运行,根据实验观察,打印含中文的字符串时有以下两个现象:

  1. 调用setlocale前:
  • printf可以正常打印含中文的字符串。将默认代码页936切换到代码页65001(utf-8编码)时,不能正常打印。
  • wprintf不能正常打印含中文的字符串。如果字符串首个字符就是中文字符,整个字符串都不会被输出;如果首个字符是ASCII字符,则可以正常输出、直到遇到中文字符。将默认代码页936切换到代码页65001(utf-8编码)时,不能正常打印。
  1. 调用setlocale(LC_TYPE,"");后(即将程序进程的区域设置为系统默认值,例如Windows中文系统的locale是Chinese (Simplified)_China.936):
  • printf依然可以正常打印含中文的字符串。将默认代码页936切换到代码页65001(utf-8编码)时,可以正常打印
  • wprintf也可以正常打印含中文的字符串。将默认代码页936切换到代码页65001(utf-8编码)时,可以正常打印

这是为什么?

现象解释

1. 为何调用setlocale后wprintf能正常输出中文字符?

printfwprintf打印字符串时,都会调用common_vfprintf函数,并且在完成字符串格式化后,最终都调用_write_nolock函数将多字节编码的字符串输出到控制台。而_write_nolock函数会首先检测当前进程是否设置了locale:

  • wprintf按wchar类型处理每个中文字符,首先调用wctomb_s将其转换为多字节字符编码;再根据是否调用了setlocale,对转换结果作进一步处理,最终将字节流输出到标准流。而wctomb_s函数需要设置正确的locale,默认的区域设置"C"不支持ASCII以外的字符,该函数无法正常转换。因此,未设置locale时无法正常输出中文内容。
  • 如果设置了locale,wctomb_s函数可以正常工作,将每个Unicode编码的中文字符(L’')转为多字节编码,不会报错。

2. 为何调用setlocale之前printf就能正常输出中文字符?

未调用setlocale时,字符串的默认编码是多字节编码(GBK),对于printf函数而言,用户传入的char*指针指向的内容相当于原样传递给最终的WriteFile函数,输出代码页是936(GBK编码)时可以正常显示中文字符,而65001代码页就不行。因为65001代码页假定传入的字节流是UTF-8编码的。

3. 为何调用setlocale之后wprintf/printf在不同代码页都能正常输出中文字符?

设置了locale后,printfwprintf都将字符串的多字节编码(ANSI)字节流先转为Unicode编码,再获取控制台的代码页信息,随后调用WideCharToMultiByte,将Unicode编码的字符串转换到输出终端对应代码页的多字节编码,将字节流输出到标准流。

因此,只要是支持中文的控制台代码页,调用setlocale之后wprintf/printf都可以正常输出中文字符。

总结

wprintf对于输入的字符串一般用wctomb_s转换成多字节编码,若未调用setlocale则直接将转换后的多字节编码字节流输出到标准流。

调用了setlocale之后,wprintf会再额外转换一次,将多字节编码的字节流先转为Unicode编码(UTF-16 LE),然后再根据输出控制台代码页转为多字节编码,将字节流输出到标准流。这次额外的转换正是printf和wprintf在不同代码页控制台能正确输出中文字符的根源。

参考

微软代码页标识符

http://www.xdnf.cn/news/8729.html

相关文章:

  • 深入浅出IIC协议 - 从总线原理到FPGA实战开发 -- 第六篇:AXI4-Lite桥接设计
  • 金众诚业财一体化解决方案如何提升项目盈利能力?
  • 国际荐酒师(香港)协会亮相新西兰葡萄酒巡展深度参与赵凤仪大师班
  • 《数据结构》系列笔记|附扫描手写笔记 1.0开篇-数据结构在学什么?
  • 【八股战神篇】操作系统高频面试题
  • Markdown 到 LaTeX:Overleaf 学习笔记
  • 华为OD机试真题——欢乐周末 (2025B卷:200分)Java/python/JavaScript/C/C++/GO最佳实现
  • 《深入探秘:从底层搭建Python微服务之FastAPI与Docker部署》
  • 在Linux下用GPIO模拟I2C通信(软件)
  • 前端流行框架Vue3教程:26. 异步组件
  • [医学影像 AI] 使用 PyTorch 和 MedicalZooPytorch 实现 3D 医学影像分割
  • xss-labs第15关
  • 历年华中科技大学保研上机真题
  • 【数据结构】图论探秘:广度优先遍历(BFS)与生成树的构建艺术
  • DAY35
  • JVM 的内存模型
  • 【MySQL系列】SQL 分组统计与排序
  • Vue-数组操作方法技术解析大纲
  • 【爬虫学习】Python数据采集进阶:从请求优化到解析技术实战
  • 解决论文中字体未嵌入的问题
  • Q2:如果 Channel 没有关闭,读取会一直阻塞吗?
  • leetcode654.最大二叉树:递归分治下的最大值索引定位与树构建
  • 显示docker桌面,vnc远程连接docker
  • Android应用中设置非系统默认语言(使用Kotlin)
  • 机械师安装ubantu双系统:三、GPT分区安装Ubantu
  • 【医学影像 AI】医学影像 AI 入门:PyTorch 基础与数据加载
  • 并发编程艺术--AQS底层源码解析(一)
  • 计算机视觉---YOLOv2
  • [特殊字符] Function Calling 技术详解与 Qwen 模型实践指南
  • mqtt数据包举例