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

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在不同代码页控制台能正确输出中文字符的根源。

参考

微软代码页标识符

相关文章:

  • 算法打卡第六天
  • C++:auto自动类型推导
  • 【算法】枚举右,维护左与滑动窗口对比理解(知识点详解提升思维)5.25
  • JAVA线程的几种状态
  • arxml文件
  • SpringBoot+MyBatis
  • 杠杆零件机械加工工艺规程设计与优化
  • 低功耗蓝牙BLE之LE Controller Package CRC校验
  • 9:OpenCV—模板匹配
  • Java面向对象 二
  • ABP VNext + Webhook:订阅与异步回调
  • HTTP 与 HTTPS
  • 【实战教程】基于 React Flow 搭建智能体组件:从环境配置到核心节点开发指南
  • Tool-Star新突破!RL赋能LLM多工具协同推理,性能全面超越基线方法
  • 符合Python风格的对象(覆盖类属性)
  • 从 0 到 1:Spring Boot 与 Spring AI 深度实战(基于深度求索 DeepSeek)
  • 怎么判断股指期货空头增仓和多头增仓呢?
  • leetcode3-无重复字符的最长子串
  • (1-6-1)Java 集合
  • JavaWeb:SpringBootAOP切面实现统计方法耗时和源码解析
  • 广西网站建设渠道/建立网站需要什么技术
  • 公司官网的作用/潍坊百度快速排名优化
  • 网络公司什么意思/安卓手机性能优化软件
  • 如何自己动手做网站/长沙专业做网站公司
  • 游戏网站搭建需要多少钱/百度权重什么意思
  • 影视公司需要的许可证/上海专业优化排名工具