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

[C++] cout、wcout无法正常输出中文字符问题的深入调查(1):各种编译器测试

作者:zyl910

  C++标准为C++标准IO库设计了十分完善的国际化文本处理机制。但在实际使用中,却发现各种编译器对它的支持性存在较大的差异,很多时候无法正确的输出字符。于是我对此进行了深入的调查。


一、说明

1.1 测试程序

  下面有一段很简单的程序,分别利用cout、wcout、printf输出字符串。具体代码为——

#include <stdio.h>
#include <locale.h>
#include <wchar.h>

#include <string>
#include <iostream>

using namespace std;


const char* psa = "A汉字ABC";
const wchar_t* psw = L"W汉字ABC";

int main(int argc, char* argv[])
{
    // init.
    //ios::sync_with_stdio(false);    // Linux gcc.
    locale::global(locale(""));
    //setlocale(LC_CTYPE, "");    // MinGW gcc.
    wcout.imbue(locale(""));

    // C++
    cout << psa;    cout.clear();    cout<<endl;
    wcout << psw;    wcout.clear();    wcout<<endl;

    // C
    printf("\nC:\n");
    printf("\t%s\n", psa);
    printf("\t%ls\n", psw);

    return 0;
}

 


  大家猜一猜这段程序的运行结果是什么?


1.2 理论结果

  先根据C++标准,分析一下这段程序的理论结果。

  在main函数中,首先执行了这两行代码对地区环境进行了初始化——

    locale::global(locale(""));
    wcout.imbue(locale(""));

 

  细节解释——
1. locale(""):调用构造函数创建一个local,其中的空字符串具有特殊含义:使用客户环境中缺省的locale(《C++标准程序库—自修教程与参考手册》P697)。例如在简体中文系统上,会返回简体中文的locale。
2. locale::global(locale("")):将“C++标准IO库的全局locale”设为“客户环境中缺省的locale”。注意它还会设置C标准库的locale环境,造成与“setlocale(LC_ALL, "")”类似的效果(《C++标准程序库—自修教程与参考手册》P698)。
3. wcout.imbue(locale("")):使wcout使用“客户环境中缺省的locale”。

  就这样,使C标准库、C++标准IO库(尤其是wcout)均正确的设置了地区环境,与客户环境中缺省环境完全匹配。

  随后,使用C++标准IO库的cout、wcout分别输出窄字符串和宽字符串——

    // C++
    cout << psa;    cout.clear();    cout<<endl;
    wcout << psw;    wcout.clear();    wcout<<endl;

 

  细节解释——
1. 调用cout、wcout的clear成员函数是为了清除错误状态,使后续输出能正常运行。
2. 使用“cout<<endl”或“wcout<<endl”时,不仅会使输出文本换行,而且还会执行flush成员函数,提交缓冲区中的数据。使得cout、wcout的输出文本不会发生冲突。

  最后,使用C标准库的printf函数输出窄字符串和宽字符串——

    // C
    printf("\nC:\n");
    printf("\t%s\n", psa);
    printf("\t%ls\n", psw);

 

  所以,测试程序的运行结果应当为——

A汉字ABC
W汉字ABC

C:
    A汉字ABC
    W汉字ABC

 

  注意为了更好区分C++标准IO库与C标准库的输出结果,这里给printf加了个TAB字符。


二、测试VC2005

  因VC2005是VC系列中第一个对C++03标准支持性较好的编译器,先来测测它。


2.1 Debug版

  在VC2005中以Debug模式编译测试程序,执行结果为——

A
W

C:
    A汉字ABC
    W汉字ABC

 

  可见C++的cout、wcout均无法正常输出中文字符。
  而C的printf都能正常输出含中文字符的窄字符串与宽字符串。


2.2 Release版

  将编译配置改为“Release”模式,再编译运行,神奇的事情发生了。执行结果为——

A汉字ABC
W汉字ABC

C:
    A汉字ABC
    W汉字ABC

 

  Release版下全部通过,cout、wcout、printf均能正常输出。


三、测试VC2008及更高版本的VC

  在VC2008中编译测试程序,执行结果为——

A汉字ABC
W汉字ABC

C:
    A汉字ABC
    W汉字ABC

 

  全部通过,cout、wcout、printf均能正常输出。然后测试了Release版,也是全部通过。看来VC2005的Bug已经被修正了。
  随后又测试了VC2010、VC2012,均是全部通过。


四、测试Windows中的MinGW

4.1 测试

  使用GCC 4.6.2(MinGW(20120426))编译测试程序,执行结果为——

A汉字ABC
W

C:
    A汉字ABC
    W

 

  窄字符串都能正常输出,但宽字符串都不能正常输出。


4.2 修改代码,使MinGW能正常显示

  将初始化代码增加一行——

    // init.
    locale::global(locale(""));
    setlocale(LC_CTYPE, "");    // MinGW gcc.
    wcout.imbue(locale(""));

 

  再用MinGW编译运行,执行结果为——

A汉字ABC
W汉字ABC

C:
    A汉字ABC
    W汉字ABC

 

  全部通过了,cout、wcout、printf均能正常输出。看来MinGW中的“locale::global(locale(""))”不会设置“setlocale(LC_ALL, "")”,必须手动调用。
  用VC2008编译刚才修改后的代码,也是全部通过。多调用一次“setlocale(LC_ALL, "")”并不会造成破坏。


五、测试Linux下的gcc

5.1 测试

  使用Linxu中的GCC编译测试程序,执行结果为——

A汉字ABC
WIWABC

C:
    A汉字ABC
    W汉字ABC

 

  cout、printf均能正常输出,但wcout不能正常输出。


5.2 修改代码,使Linux下能正常显示

  将初始化代码增加一行——

    // init.
    ios::sync_with_stdio(false);    // Linux gcc.
    locale::global(locale(""));
    wcout.imbue(locale(""));

 

 

  再用gcc编译运行,执行结果为——

A汉字ABC
W汉字ABC

C:
    A汉字ABC
    W汉字ABC

 

  全部通过了,cout、wcout、printf均能正常输出。


5.3 第2次修改代码,使MinGW能正常显示

  切换回Windows,使用MinGW编译刚才修改后的代码,执行结果为——

A汉字ABC

C:
    A汉字ABC
    W

 

  宽字符串又不能正常显示了。
  根据上次的经验,将初始化代码增加“setlocale”——

    // init.
    ios::sync_with_stdio(false);    // Linux gcc.
    locale::global(locale(""));
    setlocale(LC_CTYPE, "");    // MinGW gcc.
    wcout.imbue(locale(""));

 

  再用MinGW编译运行,执行结果为——

A汉字ABC
W汉字ABC

C:
    A汉字ABC
    W汉字ABC

 

  终于全部通过了。


5.4 在Linux中测试第2次修改代码

  在Linux中测试第2次修改代码,全部通过。

  再用VC2008编译刚才修改后的代码,也是全部通过。

  看来终于找到VC、MinGW、Linux下均有效的初始化方法了。可惜“ios::sync_with_stdio(false)”禁用同步后需要手动进行同步,会造成某些旧代码工作不正常,该方法实用性不大。


六、测试Mac OSX下的gcc

  使用Linxu中的GCC编译测试程序,执行结果为——

  这么简单的程序,居然运行时报错了。这是什么原因呢?
  用gdb调试该程序。r运行,where显示调用栈,list显示源码——

  可以看出,是在执行“locale("")”时报错的。
  “locale("")”不是C++标准中规定的吗,怎么连它都会报错?
  在网上搜索了一下,发现有人查过mac下的gcc源码,它在注释中明确写了"Currently, the generic model only supports the "C" locale."——
http://stackoverflow.com/questions/1745045/stdlocale-breakage-on-macos-10-6-with-lang-en-us-utf-8
std::locale breakage on MacOS 10.6 with LANG=en_US.UTF-8


七、总结

  虽然C++标准的设想十分完善,可惜各种编译器的实现程度存在不少差异。甚至某些平台上连“locale("")”都不支持。
  为了保证跨平台,慎用C++标准IO库,最好尽可能的使用兼容性非常好的C标准库。

 

参考文献——
《ISO/IEC 9899:1999》(C99). ISO/IEC,1999. www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf
《C++ International Standard - ISO IEC 14882 Second edition 2003》(C++03). ISO/IEC,2003-10-15.
《C++标准程序库—自修教程与参考手册》. Nicolai M.Josuttis 著,侯捷、孟岩 译. 华中科技大学出版社,2002-09.
《std::locale breakage on MacOS 10.6 with LANG=en_US.UTF-8》. http://stackoverflow.com/questions/1745045/stdlocale-breakage-on-macos-10-6-with-lang-en-us-utf-8
《[C] 跨平台使用TCHAR——让Linux等平台也支持tchar.h,解决跨平台时的格式控制字符问题,多国语言的同时显示》. http://www.cnblogs.com/zyl910/archive/2013/01/17/tcharall.html

 


源码下载——
http://files.cnblogs.com/zyl910/wchar_crtbug.rar

相关文章:

  • 老说技术更迭快,可十年到底可以淘汰多少知识?
  • 统计登录人数
  • 【Android游戏开发二十一】Android os设备谎言分辨率的解决方案!以及简单阐述游戏引擎如何使用!...
  • 图片翻转动画效果
  • “Incorrect Architecture” when trying to install iPhone app onto my development device
  • 邮件营销整体解决方案
  • java 字符串操作大全2 split 详解
  • cocos2d在iOS5sdk编译时警告的解决方法
  • oracl 中两种临时表的创建
  • #162 (Div. 2)
  • oracle开启归档模式
  • 本报记者 何泉峰 摄
  • Centos6安装桌面小记
  • 黑马程序员--小结asp.net中get、post用法区别
  • 什么时候单点集也可以是开集?
  • 分享一款快速APP功能测试工具
  • [分享]iOS开发 - 实现UITableView Plain SectionView和table不停留一起滑动
  • canvas 五子棋游戏
  • conda常用的命令
  • MySQL常见的两种存储引擎:MyISAM与InnoDB的爱恨情仇
  • React Native移动开发实战-3-实现页面间的数据传递
  • supervisor 永不挂掉的进程 安装以及使用
  • Web标准制定过程
  • 回流、重绘及其优化
  • 快速构建spring-cloud+sleuth+rabbit+ zipkin+es+kibana+grafana日志跟踪平台
  • 前端临床手札——文件上传
  • 使用Gradle第一次构建Java程序
  • 想写好前端,先练好内功
  • 写给高年级小学生看的《Bash 指南》
  • 【运维趟坑回忆录】vpc迁移 - 吃螃蟹之路
  • LevelDB 入门 —— 全面了解 LevelDB 的功能特性
  • Nginx惊现漏洞 百万网站面临“拖库”风险
  • Redis4.x新特性 -- 萌萌的MEMORY DOCTOR
  • #define用法
  • #快捷键# 大学四年我常用的软件快捷键大全,教你成为电脑高手!!
  • (4) openssl rsa/pkey(查看私钥、从私钥中提取公钥、查看公钥)
  • (Git) gitignore基础使用
  • (Redis使用系列) Springboot 实现Redis消息的订阅与分布 四
  • (solr系列:一)使用tomcat部署solr服务
  • (附源码)spring boot基于Java的电影院售票与管理系统毕业设计 011449
  • (附源码)springboot社区居家养老互助服务管理平台 毕业设计 062027
  • (附源码)springboot太原学院贫困生申请管理系统 毕业设计 101517
  • (排序详解之 堆排序)
  • (原創) 如何動態建立二維陣列(多維陣列)? (.NET) (C#)
  • (转)利用ant在Mac 下自动化打包签名Android程序
  • (自适应手机端)响应式新闻博客知识类pbootcms网站模板 自媒体运营博客网站源码下载
  • *上位机的定义
  • .MyFile@waifu.club.wis.mkp勒索病毒数据怎么处理|数据解密恢复
  • .net 7 上传文件踩坑
  • .NET Core WebAPI中封装Swagger配置
  • .NET 发展历程
  • .net 微服务 服务保护 自动重试 Polly
  • .NET和.COM和.CN域名区别
  • .net项目IIS、VS 附加进程调试
  • .net知识和学习方法系列(二十一)CLR-枚举