C++的字符串转换函数mbstowcs使用时容易产生bug。。。

rapidxml_utils.hpp 的file(const char*filename)函数内会异常宕机。。。

需要在函数最开始添加

locale::global(locale(""));

from  http://blog.sina.com.cn/s/blog_55c1b83b0100wbah.html

1 问题

在 Windows XP 多语言简中环境下,用 VC2005 中的 std::fstream 打开中文名文件,系统报错找不到此文件。

std::ifstream file("\xd6\xd0.txt"); // GBK 编码的 "中.txt" if (!file) { std::cerr <<"Cannot open file!"; // Oops! }

2 原因

在 VC2005 中 std::fstream 的打开文件的函数实现里,传入的 char const* 文件名作为多字节首先被mbstowcs 转换成宽字节后,再转发给 Unicode 版本的 API 进行实际的打开文件操作。见 fiopen.cpp:

_MRTIMP2_NCEEPURE FILE *__CLRCALL_PURE_OR_CDECL _Fiopen(const char *filename, ios_base::openmode mode, int prot) { // open wide-named file with byte name wchar_twc_name[FILENAME_MAX];   if (mbstowcs_s(NULL, wc_name, FILENAME_MAX, filename,FILENAME_MAX - 1) != 0) return (0); return _Fiopen(wc_name, mode, prot); }

问题的关键在于,对于 mbstowcs 函数来说,它需要知道多字节的编码类型才能正确的将其转换成宽字节的 unicode,很可惜这个编码类型并没有体现在函数的参数列表里,而是隐含依赖全局的 locale 。更加不幸的是,全局 locale 默认没有使用系统当前语言,而是设置为没什么用处的 "C" locale 。于是 GBK 编码的文件名在 "C" locale 下转换错误,悲剧发生了……

3 解

知道了原因,解就很简单了。在调用 mbstowcs 或使用它的函数之前,先用 setlocale 将全局默认 locale 设为当前系统默认 locale :

setlocale(LC_ALL, "");

如果是在非中文系统上转 GBK 编码,就需要指定中文 locale :

setlocale(LC_ALL, "chs"); // chs 是 VC 里简中的 locale 名字

还有一种方法,直接使用宽字节版本的API,之前的编码由自己转换好,避免系统语言环境设置的影响。在 VS2005 中 fstream 有个扩展,可以直接打开宽字节文件名:

std::ifstream file(L"\u4E2D.txt"); // UCS2 编码的“中.txt”

4 引申

API 中隐藏依赖关系是不好的,这种隐藏总意谓着外部环境能通过潜规则来影响 API 的功能。这影响了该API的复用性,可测性,也容易让用户出现意外错误。进一步设想一下,如果环境原来的 locale 是被其它代码块故意设置的,如果为了修正打开中文名文件的 Bug 而冒冒然修改当前全局的 locale ,很可能会让依赖于原 locale 工作的代码出现 bug 。在这样的 API 设计下,如果要尽量避免顾此失彼的发生,我们可以在修改前保存当前的 locale ,用完后再恢复回原来的 locale 。在 C++ 里,最好是将这样的逻辑用 RAII 来封装:

class scoped_locale { public: scoped_locale(std::string const&amp; loc_name) :_new_locale(loc_name) , _setted(false) { try { char const* old_locale =setlocale(LC_CTYPE, _new_locale.c_str());   if (NULL != old_locale) { _old_locale =old_locale; _setted = true; } } catch (...) { } }   ~scoped_locale() { try { if(_setted) { char const* pre_locale = setlocale(LC_CTYPE, _old_locale.c_str());   if(pre_locale) { assert(pre_locale == _new_locale); _setted = false; } } } catch (...){ } }   private: std::string _new_locale; std::string _old_locale; bool _setted; };

原代码可以改为:

{ scoped_locale change_locale_to(""); std::ifstream file("\xd6\xd0.txt"); // GBK 编码的“中.txt” if (!file) { std::cerr << "Cannot open file!"; // Oops! } }

当然,如果是多线程环境的话,还需要查明 locale 的全局性是进程级的还是线程级的。如果是前者,那还是会有潜在的相互影响的风险。从这点上来看,C/C++ 标准库中 mbstowcs 的设计是有瑕疵的。这也从反面体现了 Dependency Injection 思想的重要性。在 Win32 API 有个类似的函数 WideCharToMultiByte() ,它的作用也是进行多字节到宽字节的编码转换,但在API设计上,它就将 code page 作为第一个入参显示传入,而不是默认使用全局系统的某个状态。用它来写一个通用的转换函数就可以避免 mbstowcs 的问题了:

std::wstring native_to_utf16(std::string const& native_string) { UINT const codepage= CP_ACP; DWORD const sizeNeeded = MultiByteToWideChar( codepage, 0, native_string.c_str(), -1, NULL, 0);   std::vector<wchar_t> buffer(sizeNeeded, 0);  if (0 == MultiByteToWideChar(codepage, 0, native_string.c_str(), -1, &buffer[0], buffer.size())) { throw std::runtime_error("wrong convertion from native string to utf16"); }   return std::wstring(buffer.begin(), buffer.end()); }

setlocale 与 mbstowcs 的问题的更多相关文章

  1. setlocale同mbstowcs函数的关系(VS2008下setlocale(LC_ALL, "chs")可以执行成功,BCB使用setlocale(LC_ALL, "Chinese (Simplified)_People's Republic of China"),linux上locale别名表大概在 /usr/lib/X11/locale/locale.alias)

    序中,如果要将ASCII码字符串转换为宽字符(Unicode),可以利用标准C的mbstowcs函数. 微软在MSDN中有示例,如下: 然而,这段代码在处理含有汉字的字符串时就会出现问题.比如将: w ...

  2. mbstowcs 和ifstream 前为何要setlocale

    最近看公司的一些代码,发现一些地方调用了std::locale::global(locale("")); (c++) 和 setlocale(LC_ALL, "" ...

  3. 几个字符串的误区,以及setlocale函数的使用

    转自 http://www.blogjava.net/baicker/archive/2007/08/09/135642.html 转自 http://witmax.cn/character-enco ...

  4. setlocale(LC_ALL, ""); 取值为空字符串" "(注意,不是NULL),则locale与本地环境所使用的编码方式相同(在本地化时,应该很有用);

    在C运行库提供的多字节字符-宽字符转换函数:mbstowcs()/wcstombs()中,需要用到全局变量locale( locale encoding ),以指定多字节字符的编码类型 1. 功能: ...

  5. 为什么一定要调用 setlocale 呢? 因为在 C/C++ 语言标准中定义了其运行时的字符集环境为 "C" ,也就是 ASCII 字符集的一个子集。使用setlocal改变整个应用程序的字符集编码方式(wcstombs使用前要设置 setlocale (LC_ALL, "chs"); )

    setlocale 配置地域化信息. 语法: string setlocale(string category, string locale); 返回值: 字符串 函数种类: 操作系统与环境   内容 ...

  6. 宽字节 多字节 mbstowcs wcstombs

    函数 size_t wcstombs(char *dest, const wchar_t *src, size_t n); //wide-character to a multibyte n:被写入到 ...

  7. bash: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8)

    bash: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8) Q: hubery@roaster:~$ locale loc ...

  8. [Ubuntu] bash: warning: setlocale: LC_ALL: cannot change locale

    问题症状 -bash: warning: setlocale: LC_ALL: cannot change locale (en_US.utf8) 解决方法 本地化是指不同地区用户在键盘上输入不同语言 ...

  9. 新发现的mbstowcs, mbstowcs_s函数,转换多字节到宽字符

    http://en.cppreference.com/w/c/string/multibyte/mbstowcs https://msdn.microsoft.com/fr-fr/library/ey ...

随机推荐

  1. WebApi 跨域

    http://www.cnblogs.com/lori/p/3557111.html http://bbs.csdn.net/topics/391020576

  2. C 语言 习题 1-14

    练习 1-14 编写一个程序,打印输入中各个字符出现频度的直方图. #include <stdio.h> /* count digits, white space, others */ i ...

  3. 【清华集训】小Y和地铁

    图已挂,前往luogu 题目: 小 $\rm Y$ 是一个爱好旅行的 $\rm OIer$.一天,她来到了一个新的城市.由于不熟悉那里的交通系统,她选择了坐地铁.她发现每条地铁线路可以看成平面上的一条 ...

  4. 南邮部分wp

    MYSQL 打开robots.txt 鍒お寮€蹇冿紝flag涓嶅湪杩欙紝杩欎釜鏂囦欢鐨勭敤閫斾綘鐪嬪畬浜嗭紵 鍦–TF姣旇禌涓紝杩欎釜鏂囦欢寰€寰€瀛樻斁鐫€鎻愮ず淇℃伅 这一看乱码,放到新建tx ...

  5. sql2005 和sql2008 同时安装

    Hkey_local_machine\Software\Wow6432Node\Microsoft\Microsoft SQL Server\90\Tools\ShellSEM 中的ShellSEM重 ...

  6. sed处理大txt文件(1G) 比如替换某一串字符串,或者删除一行

    1.将11.sql文件中"prompt"替换为"--prompt",然后保存为111.sql文件 sed -e "s,prompt,--prompt, ...

  7. 【bzoj3555】[Ctsc2014]企鹅QQ 字符串hash

    题目描述 PenguinQQ是中国最大.最具影响力的SNS(Social Networking Services)网站,以实名制为基础,为用户提供日志.群.即时通讯.相册.集市等丰富强大的互联网功能体 ...

  8. BZOJ-3190 [JLOI2013]赛车

    转成二元一次不等式组,然后半平面交. #include <cstdlib> #include <cstdio> #include <cmath> #include ...

  9. [CQOI2014][bzoj3504] 危桥 [最大流]

    题面 传送门 思路 这道题中惟一的特别之处,就在于"危桥"这一个只能走两次的东西 我的第一想法是做一个dp,但是这道题只需要能不能走,也没有必要 网络流?貌似是个很好的选择 我们把 ...

  10. 个人环境搭建——搭建JDK环境

    搭建JDK环境 开始之初先提醒两点: ①java是在bash环境下面的,虽然我也在.cshrc下面添加了环境变量,好像有点问题,需要继续改进: ②查看linux版本信息命令:cat /etc/issu ...