std::fstream打开中文路径名失败的问题原因、解决方法以及注意事项

在VS2005、VS2008中,使用std::ifstream每每碰到中文路径名就出错, 据说这个问题在VS2003以及之前版本是没有的,不幸的是我现在用的是VS2005的版本。
如果你跟进去VC实现版的STL代码,你会发现,它有一个将传入的char字符串文件名转换为UNICODE的wchar_t字符串这样一个过程,
其代码如下:

_Fiopen(const char *filename, ios_base::openmode mode, int prot) 

    // open wide-named file with byte name wchar_t wc_name[FILENAME_MAX]; 
    if (mbstowcs_s(NULL, wc_name, FILENAME_MAX, filename, FILENAME_MAX - 1) != 0return (0); 
    return _Fiopen(wc_name, mode, prot); 
}

wbstowcs_s方法最终进入到了_mbstowcs_l_helper方法,如果得到的是C locale,则它认为传进来的字符串为ASCII码,也就是单字节字符,
它仅仅是进行了char到wchar_t指针的转换而已,那很显然第二个字节肯定为零,自然的字符就错了;如果不是的话,它认为是多字节字符,将会调用MultiByteToWideChar进行转码。在VC8里面,local默认是C locale,所以就出错了。
以下为摘抄的该段代码:

if (_loc_update.GetLocaleT()->locinfo->lc_handle[LC_CTYPE] == _CLOCALEHANDLE) 

    /* C locale: easy and fast */ 
    while (count < n) 
    { 
        *pwcs = (wchar_t) ((unsigned char)s[count]); 
        if (!s[count]) return count; 
        count++; pwcs++; 
    } 
    return count; 

else 

    int bytecnt, charcnt; 
    unsigned char *p; 
    /* Assume that the buffer is large enough */ 
   if ( (count = MultiByteToWideChar( _loc_update.GetLocaleT()->locinfo->lc_codepage,
      MB_PRECOMPOSED | MB_ERR_INVALID_CHARS, s, -1, pwcs, (int)n )) != 0 ) 
   return count - 1/* don't count NUL */
}

知道了问题的缘由,才能够更好的解决问题。
在VC6和VC7都没有经过这个步骤,好像是直接调用的SDK的CreateFile方法,因此就没有问题。而VC8这样根据locale来转码所以造成了问题。

 /*方法1,
 使用STL中的locale类的静态方法指定全局locale             
 使用该方法以后,cout可能不能正常输出中文,十分蹊跷                    
 我发现了勉强解决的方法:不要在还原区域设定前用cout或wcout输出中文,
 否则后果就是还原区域设定后无法使用cout wcout输出中文 (!>.<! 我小测试了一下却无此情况发生)          
 
*/
 locale::global(locale(""));//将全局区域设为操作系统默认区域
 file.open("d://测试//测试文本.txt");//可以顺利打开文件了
 locale::global(locale("C"));//还原全局区域设定
 cout<<file.rdbuf();
 file.close();
 
/* 方法2,使用C函数setlocale,不能用cout输出中文的问题解决方法同上 */
 setlocale(LC_ALL,"Chinese-simplified");//设置中文环境
 file.open("c://测试//测试文本3.txt");//可以顺利打开文件了
 setlocale(LC_ALL,"C");//还原
 cout<<file.rdbuf();
 file.close();

好吧,到这里并不是这篇随笔的结束,而是刚刚开始 ~.~、

当我用std::locale解决上述问题后,随之而来的问题来了。编译连接运行程序都没问题,但有时会发生弹框 [**指令引用**内存,该内存不能为“read”],crash产生了。调试中会发现时std::locale的问题,原因是我的程序多线程的,而std::locale在多线程中会产生线程安全问题,有大牛专门就此问题向MicroSoft提交了此问题,ms也给出了解决说是在vc11RTM版本中解决了。

内存不能为“read”,显示是指针访问越界了,下面的引用给出了说明。(参考[3])

Calling std::locale::global() in one thread can lead to crashes in another thread which uses iostreams (e.g. creates an ifstream).

The problem seems to be that the std::locale()-Constructor first stores a pointer to the global locale through _Getgloballocale and then increases the reference count on the global locale through another call to _Getgloballocale:
    locale() _THROW0()
        : _Ptr(_Init())
        {    // construct from current locale
        _Getgloballocale()->_Incref();
        }
Between these two calls, a call of std::locale::global() can change the global locale, so that the ref count of the wrong object is increased and the _Ptr object could become invalid.

There are some shell extensions which call std::locale::global so with enough bad luck, it is called when a file open dialog is created without any opportunity for us to prevent it. In effect, this means that we can't use iostreams in background threads at the same time as file open dialogs are used.

参考:

([1]. http://hi.baidu.com/thinkanddo1/item/2ad6d926b33eed3a95f62b69)

([2]. http://www.cnblogs.com/kevinGaoblog/archive/2012/07/20/2601236.html)

([3]. http://connect.microsoft.com/VisualStudio/feedback/details/738919#details)

原文地址:https://www.cnblogs.com/Totems/p/fstream_std_locale_thread_safety_issue.html