引用MS网站上翻译的一篇文章:
《STL std::string 类导致崩溃和内存损坏多处理器计算机上》
http://support.microsoft.com/default.aspx?scid=kb%3Bzh-cn%3B813810
英文原文:
http://support.microsoft.com/default.aspx?scid=kb;en-us;813810
---------摘要--------------
症状
原因
解决方案
方法 1: 使用 Microsoft Visual C++ .NET (7.0 和更高版本
方法 2: 使用 Microsoft Visual C++ 6.0 以 STL 从第三方
替代方法
解决 Microsoft Visual C++ 6.0 STL 中 Std::String 类问题
禁用字符串引用计数
方法 1: 仅使用静态 CRT 链接
方法 2: 使用动态 CRT 链接
方法 3: 使用聪明黑客来避免链接问题
方法 4: 使用自定义 Std::String DLL
更多信息
参考
-------------------正文------------------------------------------------------
症状
当
您构建在 Microsoft Visual C++ 6.0 应用程序使用提供标准模板库 (STL), 内存损坏可能发生, 或计算机可能停止响应。
多处理器计算机上更经常发生这些症状。 相同代码以前, 可能使用过没有这样的问题单处理器计算机上。 当检查错误线程在调试器,
通常看到内存管理功能中失败。 经常看到堆栈跟踪中 basic_string < char. 按钮 > 类方法。 由于内存损坏也症状,
方面都与字符串处理无关会失败。
堆栈跟踪属于其中问题已是崩溃原因如下:01 0012ebc4 77fb4014 0246ffd0 00000027 02531000 ntdll!RtlpDphReportCorruptedBlock+0x8c
02 0012ebec 77fb2cb1 02531000 01001002 0246ffd0 ntdll!RtlpDphNormalHeapFree+0x46
03 0012ec10 77fb5653 02530000 01001002 0246ffd0 ntdll!RtlpDebugPageHeapFree+0xa6
04 0012ec88 77fa760a 02530000 01001002 0246ffd0 ntdll!RtlDebugFreeHeap+0x203
05 0012ed28 77fcba9e 02530000 01001002 0246ffd0 ntdll!RtlFreeHeapSlowly+0x4d
06 0012edcc 004065a6 02530000 00000000 0246ffd0 ntdll!RtlFreeHeap+0x53
07 0012ee14 0041353a 0246ffd0 00404198 0246ffd0 main!free+0xda
08 0012ee1c 00404198 0246ffd0 0012eecc 004e9b70 main!operator delete+0x9 (FPO: [1,0,0]) (CONV: cdecl) [afxmem.cpp @ 349]
09
0012ee38 00402a71 02477fe0 00000011 004e9ce0
main!basic_string<char,char_traits_char,allocator<char>
>::append_helper+0x68 (FPO: [EBP 0x0012eecc] [2,1,4]) (CONV:
thiscall)
...
NTDLL! 77f97710()
NTDLL! 77fb5721()
NTDLL! 77fa760a()
NTDLL! 77fcba9e()
MSVCRT! 78001d92()
operator delete(void * 0x00c266f8) line 6 + 10 bytes
std::basic_string<char,std::char_traits<char>,std::allocator<char>
>::_Tidy(std::basic_string<char,std::char_traits<char>,std::allocator<char>
> * const 0x0000000f {???}, unsigned char 1) line 591 + 6 bytes
...
00 0184fb9c 60f3abc3 main!__sbh_free_block+0x173
01 0184fbb4 60f2aa93 main!free+0x28
02 0184fbbc 60f2423c main!operator delete+0x9
03
0184fce8 60f244b0
main!function(std::basic_string<char,std::char_traits<char>,std::allocator<char>
> var =
std::basic_string<char,std::char_traits<char>,std::allocator<char>
>)+0x79c
...
...
5ed 0198de20 77fac5f4 0198dec0 0198e3f8 0198dedc ntdll!ExecuteHandler+0x26
5ee 0198dea8 77f91a96 0198dec0 0198dedc 0198dec0 ntdll!RtlDispatchException+0x76
5ef 0198df14 77b22546 2cb01468 47ac0008 00000008 ntdll!KiUserExceptionDispatcher+0xe
5f0 0198e340 1001b22c 00ed0000 00000000 00000080 ole32!SyncStubInvoke+0x61
5f1 0198e37c 1001b123 00000080 1001a4ef 00000080 main!_heap_alloc+0xed
5f2 0198e384 1001a4ef 00000080 00000001 100022f1 main!_nh_malloc+0x10 (FPO: [2,0,0])
5f3 0198e390 100022f1 00000080 0000007c 0198f430 main!operator new+0xb (FPO: [1,0,0])
5f4
0198e3b0 10002207 0000003c 0000007d 0198f42c
main!std::basic_stringbuf<unsigned short,std::char_traits<unsigned
short>,std::allocator<unsigned short> >::overflow+0x83
(CONV: thiscall) [C:\Program Files\Microsoft Visual
Studio\VC98\INCLUDE\sstream @ 60]
5f5 0198e3cc 10003194 00000000
0000006b 0198f6e0 main!std::basic_streambuf<unsigned
short,std::char_traits<unsigned short> >::xsputn+0x6a (CONV:
thiscall) [C:\Program Files\Microsoft Visual
Studio\VC98\INCLUDE\streambuf @ 166]
5f6 0198e404 10005621 0198f42c
010113b2 1003573c main!std::operator<<+0xb0 (CONV: cdecl)
[C:\Program Files\Microsoft Visual Studio\VC98\INCLUDE\ostream @ 305]
...
原因
标
准模板库 (STL) 是以 Microsoft Visual C++ 中是不安全的多线程应用程序。 特别, std::string 类的实现依赖
basic_string < 按钮 > 模板类。 basic_string < 按钮 >
模板类引用计数隐藏字符缓冲区的副本。 在一个 8 位无符号 char basic_string < 按钮 > 模板类存储计数。
实现后发生下列常规问题: • 不 basic_string < 按钮 > 模板类不保护 counting
机制以所需有关线程要同时运行多处理器计算机上。 因为只有一个线程运行在时间,
和内存读取或者整数上写入完成之前可中断其他线程单处理器计算机上运行多线程代码避免此问题。
• 写入一个 std::string 一个线程中类可能破坏是一份 std::string 类, 如赋值, 其他线程中创建一个读取。 supposed 副本共享同一隐藏字符缓冲区。
• 字符串损坏可能发生指针或引用到一个 std::string 类是线程之间共享。 通常, 它负责对程序员可以避免这种情况。
解决方案
必
须进行线程 STL 后重建应用程序。 要获取线程 STL 首选方法是将 STL 升级到新版本是基于当前 VisualC++ 标准。 是当前
VisualC++ 标准基于 STL 但是, 不等同于 STL, Microsoft VisualC++6.0 作为新产品发行时, 无法。
但是, 升级到新版本可能会根据 STL 函数, 应用程序使用普通。 以获取新版本的线程 STL, 使用下列方法之一:
方法 1: 使用 Microsoft Visual C++ .NET (7.0 和更高版本
在
应用程序中打开每个 VisualC++ 项目、 允许项目将自动转换成新项目格式, 和然后重建它。 此版本中 std::string
类实现是线程安全对描述问题。 如果您任何一种项目中使用 DLL 运行时库功能, 应用程序中您必须分发新 VisualC++ 运行时库组件 (如
Msvci7x.dll Msvcp7x.dll, 和 Msvcr7x.dll) 随重建应用程序。
注意 您不需要分发到客户端计算机以使用 Microsoft Visual C++ .NET Microsoft.NET 框架。
方法 2: 使用 Microsoft Visual C++ 6.0 以 STL 从第三方
细
节的集成依产品, 和单个厂商提供支持。 对于后续 STL 版本一个源是 Dinkumware, Ltd., 其中 Microsoft 许可证
Visual C++ 6.0 STL 公司。 它是占用, 它与现有构建进程集成。 有关详细信息, 和有关的已知缺陷和变通, 列表请访问以下
Dinkumware Web 站点:
www.dinkumware.com (http://www.dinkumware.com)
Microsoft
提供第三方联系信息旨在帮助您查找技术支持。 此联系人信息可能更改, 恕不另行通知。 Microsoft 不保证该第三方联系信息的准确性。
第三方产品, 本文讨论由程序是独立于 Microsoft 公司制造。 Microsoft 使任何默示或其他, 形式不保证,
有关性能或可靠性对这些产品。
替代方法
解决 Microsoft Visual C++ 6.0 STL 中 Std::String 类问题
如
果执行不升级到新版的 STL, 您可以尝试纠正 std::string 类线程安全问题标准 Microsoft VisualC++6.0
安装中。 虽然有是与几个 Microsoft Visual C++ 6.0 STL, 中类 multi-threading
问题为止最常见和问题均为 std::string 类。 以下步骤和变通办法是 stopgap 措施以确保应用程序是否正常,
并提供时间来研究其他备选方案。 考虑将这些指令创建新代码路径和也许整个整个应用程序行为。
彻底测试应用重建程序依照公司或个人软件策略之前广泛部署软件策略之前广泛部署。
禁用字符串引用计数
每个变通本节中介绍要求您先禁用引用计数机制。 要禁用引用计数, 您必须修改 < xstring > 头文件, _FROZEN 枚举常量设置为 0 。 此外默认安装, < xstring > 头文件位于以下位置:
C:\ProgramFiles\Microsoft files\Microsoft Visual Studio\VC98\Include
将
_FROZEN 枚举常数更改为 0 在行 62 页 < xstring > 头文件中以便它与以下类似: enum _Mref
{_FROZEN = 0}; // set to zero to disable sharing; original value 255
如
果您按照此建议, 并重建所有软件使用这些头文件 std::string 类代码将是多线程。 有一些警告到该语句。 因此,
仔细阅读以下变通办法说明。 禁用引用计数通过在 < xstring > 头文件, _FROZEN 枚举常量设置为 0
后,使用下列方法之一来解决此问题。
方法 1: 使用静态 CRT 链接
在所有项目, 使用 std::string 类来链接到静态版本的 Microsoft 运行时库 (CRT) 修改项目设置。 如果项目也有启用 共享 DLL 中使用 MFC 设置无法使用此方法。 对于每个项目, 请按照下列步骤操作: 1. 打开项目。
2. 在 项目 菜单上, 单击 设置 。
3. 在 配置 列表, 单击 发布 。
4. " 分类 " 列表中 C/C++ 选项卡, 依次 代码生成 。
5. 在 运行时库 列表, 单击 Multi-thread MT) / (
6. 在 配置 列表, 单击 调试 。
7. 在 运行时库 列表, 单击 Multi-thread 调试 (MTd) /
8. 如果还有其他配置, 配置 列表中设置适当的 运行库 还为这些选项。
9. 单击 确定 , 然后重建该项目。
此变通确保通过静态链接到整个多线程运行时库, 包括 MFC, 所有代码使用 < xstring > 文件的修改版本。 一个可能问题是最终代码大小会大于一个动态链接版本, 也许 enormously 这样。
方法 2:使用动态 CRT 链接
如
果项目代码必须链接到运行库 (CRT) 作为 DLL, 则必须采取其他方法。 动态 CRT 链接是默认设置对 DLL 项目。 依赖其他组件 (如
MFC 或第三方库用于与应用程序, 授权通常需要动态链接到 CRT。 如果您只依赖是 MFC, 可使用 静态库中使用 MFC 选项,
并应用方法 1。 默认情况下, 在 VisualC++6.0, Microsoft 中创建新项目时项目使用 CRT 从 DLL。
动
态 CRT 链接项目设置链接应用程序以对某些 std::string 类方法预置 Microsoft CRT DLL, 名为
Msvcp60.dll 中实现。 更改到 _FROZEN 常量, 您对本地副本是 < xstring > 因为通过 <
xstring > 修饰头文件, Microsoft 内置 DLL 是不遵守对于函数调用超出该库。 这些包括如 _Tidy() , 和
assign() 函数都提供用于 < char 和 < 短 > instantiations basic_string 类的
Msvcp60.dll 文件中。 basic_string 类是基础为 std::string 类。
要代替 Msvcp60.dll 文件, 中 Microsoft 提供实现模块中使用 std::string 类的静态实现请按照下列步骤:
1. < xstring > 文件, 中注释掉附近文件末尾发现以下代码。此外您可加入把代码加入 # if 0 / # endif 块中来注释.
#ifdef _DLL
#pragma warning(disable:4231) /* the extern before template is a non-standard extension */
extern template class _CRTIMP basic_string<char, char_traits<char>, allocator<char> >;
extern template class _CRTIMP basic_string<wchar_t, char_traits<wchar_t>, allocator<wchar_t> >;
#pragma warning(default:4231) /* restore previous warning */
#endif // _DLL
2.
<string> 头文件, 中定义其他字符串运算符, 它们通过 Msvcp60.dll 文件还包括 CRT 中。
发现在结尾处有一系列 … " extern 模板 _CRTIMP " < 字符串 > 文件, 就像受 #ifdef _DLL 子句
< xstring > 文件中定义,同样, 注释掉下面所有这些定义:
#ifdef _DLL
#pragma warning(disable:4231) /* the extern before template is a non-standard extension */
extern template class _CRTIMP
basic_string<char, char_traits<char>, allocator<char> > __cdecl operator+(
const basic_string<char, char_traits<char>, allocator<char> >&,
const basic_string<char, char_traits<char>, allocator<char> >&);
extern template class _CRTIMP
basic_string<char, char_traits<char>, allocator<char> > __cdecl operator+(
...
extern template class _CRTIMP
basic_ostream<wchar_t, char_traits<wchar_t> >& __cdecl operator<<(
basic_ostream<wchar_t, char_traits<wchar_t> >&,
const basic_string<wchar_t, char_traits<wchar_t>, allocator<wchar_t> >&);
#pragma warning(default:4231) /* restore previous warning */
#endif // _DLL
3.
保存对这些文件然后再重建所有应用程序中使用 STL 项目。 如果项目声明类 _ _ declspec (dllexport) 类 ,
并且类有属于 std::string 类型, 看到 C4251 警告。 因为所有代码与静态链接, 现在 std::string
类生成可忽略这些警告。 要明确禁用这些警告, 使用下列批注:#pragma warning(disable: 4251)
此方法平衡使用 MFC 和 CRT 函数以外 std::string 类从 DLL。 没有小代码大小在每个模块, 使用 std::string 类中增加。
方法 3: 使用聪明黑客来避免链接问题
创
建为 无符号 char 类型和使用, 而不是现有 std::string 类类型。 类型可能需要是应用程序的源文件, 使用
std::string 类中头文件中包含窗体。 类型可能会出现类似如下:typedef
std::basic_string<unsigned char> MyString;
#define string MyString
任何与此类使用字符串必须转换或者作为无符号字符处理。 可能存在的代码大小与简化的实现, 增加并且有链接少副作用。
方法 4: 使用自定义 Std::String DLL
小
代码大小的好处通过单个 DLL 中放置 std::string 类实现该选项获得您。 创建 DLL 项目, 导出 std::string 类。
链接到 DLL 代替到标准 Msvcp60.dll 文件。 一起随应用程序重新您必须分发此新 DLL。 这是一个高级选项。
更多信息
以下 C++ 代码示例演示一个方案的同步不足时可能出现:...
std::string A;
A = "Init";
_beginthread(Thread1, 0, (void*)&A);
_beginthread(Thread2, 0, (void*)&A);
A = "";
...
void Thread1(void* arg)
{
std::string A1 = *(std::string*)arg;
...
A1 = "newval";
}
void Thread2(void* arg)
{
std::string A2 = *(std::string*)arg;
...
std::string B = A2;
A2 = "newval2";
}
在
本示例, Thread1 创建副本的输入参数, 并引发共享字符缓冲区以 1 上引用计数。 它是使用, 时 Thread2
还创建副本是其输入参数, 并引发引用计数为 2时。 同时, 主线程将新值赋给 A、 创建新字符缓冲区, 和除去原始共享缓冲区以 1 上引用计数。
Thread1 来创建新字符缓冲区用于新指派到 A 1 开始, 其以前共享字符缓冲区, 和然后由 1 到 0,
计数减少上识别正引用计数。 At the same time, Thread2 is also in the process of
assignment to B. B shares the character buffer of A2, and raises the
reference count on the character buffer of A2, trying to increment it to
2 just before Thread1 writes a 0 to the reference count. 引用计数现代替 1 0。
如果访问引用计数器已被同步引用计数将已经 0。
当 Thread2 将新值赋给 A2, Thread2 看到引用计数为 0
并丢弃原始共享字符缓冲区, 仍引用 B。 内存, 保留字符缓冲区可立即用于其他应用程序中使用。 但是, B std::string
仍将指针放到字符缓冲区。 下列情况导致损坏和故障:• B 试图释放字符缓冲区。
• B 试图读取字符缓冲区的内容已被覆盖与实时数据由其他代码。
• 没有尝试到扩展或修改字符串。
参考
有关 VisualC++ 语言和编译器问题, 的详细信息请参阅 是 STL 附带 VC++ 线程 ? 主题下列 Microsoft 最有价值专家 (MVP) Web 站点上:
http://www.mvps.org/vcfaq (http://www.mvps.org/vcfaq)
-------------
偶使用的是替代方案的第2种方法, 禁用string引用计数并使用CRT动态链接库,修改注释<string>、<xstring>这两个头文件的#ifdef _DLL / #endif 代码块。
发表于 @ 2006年09月07日 17:00:00