STL std::string class causes crashes and memory corruption on multi-processor machines

Article translations Article translations
Article ID: 813810 - View products that this article applies to.
Expand all | Collapse all

On This Page

SYMPTOMS

When you build applications in Microsoft Visual C++ 6.0 that use the supplied Standard Template Library (STL), memory corruption may occur, or your computer may stop responding. These symptoms occur more frequently on multi-processor computers. Previously, the same code may have worked without such issues on a single-processor computer. When you examine the faulting thread in a debugger, you typically see the failure in a memory management function. Frequently you see the basic_string<char...> class methods in the stack trace. Because memory corruption is also a symptom, failures may appear in areas that are unrelated to string processing.

The following are examples of stack traces where this problem was the cause of a crash:
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]
...

CAUSE

The Standard Template Library (STL) that is included with Microsoft Visual C++ 6.0 is not safe for multi-threaded applications. In particular, the implementations of the std::string class depend on the basic_string<...> template class. The basic_string<...> template class reference counts copies of a hidden character buffer. The basic_string<...> template class stores the count in an 8-bit unsigned char. The following general issues occur after this implementation:
  • The basic_string<...> template class does not protect the counting mechanism with the synchronization that is required for threads on multi-processor computers to run at the same time. Multi-threaded STL code that is running on single-processor computers avoids this issue because only one thread runs at a time, and memory reads or writes on integers are completed before another thread can interrupt.
  • Writing to an std::string class in one thread can corrupt the reading of a copy of the std::string class, such as one created by assignment, in another thread. The supposed copy shares the same hidden character buffer.
  • String corruption may occur where a pointer or reference to an std::string class is shared between threads. Typically, it is the responsibility of the programmer to avoid this situation.

RESOLUTION

You must rebuild the application after you make the STL thread-safe. The preferred method to obtain a thread-safe STL is to upgrade the STL to a newer version that is based on the current Visual C++ standard. However, the STL that is based on the current Visual C++ standard is not identical to the STL that was available at the time that Microsoft Visual C++ 6.0 was released as a new product. However, upgrading to a new version may be trivial depending on the STL functions that your application uses. To obtain new versions of the thread-safe STL, use one of the following methods:

Method 1: Use Microsoft Visual C++ .NET (versions 7.0 and later)

Open each Visual C++ project in your application, allow the project to automatically convert to the new project format, and then rebuild it. The std::string class implementation in this version is thread-safe for the described problem. If you use the DLL run-time library feature in your any one of the projects in your application, you must distribute the new Visual C++ run-time components (such as Msvci7x.dll, Msvcp7x.dll, and Msvcr7x.dll) with your rebuilt application.

Note You do not have to distribute the Microsoft .NET Framework to client computers to use Microsoft Visual C++ .NET.

Method 2: Use Microsoft Visual C++ 6.0 with a replacement STL from a third party

The details of integration vary by product, and the individual vendors provide support. One source for a successor STL version is Dinkumware, Ltd., the company where Microsoft licenses the Visual C++ 6.0 STL. It is claimed that it can integrate with existing build processes. For more information, and for a list of known bugs and workarounds, visit the following Dinkumware Web site:
www.dinkumware.com
Microsoft provides third-party contact information to help you find technical support. This contact information may change without notice. Microsoft does not guarantee the accuracy of this third-party contact information. The third-party products that this article discusses are manufactured by companies that are independent of Microsoft. Microsoft makes no warranty, implied or otherwise, regarding the performance or reliability of these products.

WORKAROUND

Work around the std::string class issue in Microsoft Visual C++ 6.0 STL

If you do not upgrade to a new version of the STL, you can try to correct the std::string class thread-safety issue in the standard Microsoft Visual C++ 6.0 installation. Although there are multi-threading issues with several of the classes in the Microsoft Visual C++ 6.0 STL, by far the most common and problematic class is the std::string class. The following steps and workarounds are stopgap measures to make sure that an application is working correctly, and the measures provide time to investigate other alternatives. Consider that these instructions will create new code paths and behavior perhaps throughout your whole application. Thoroughly test the rebuilt application in accordance with a company's or an individual's software policies before widespread deployment.

Disable string reference counting

Each of the workarounds that is documented in this section require that you first disable the reference-count mechanism. To disable reference counting, you must modify the <xstring> header file and set the _FROZEN enumeration constant to 0. In default installations, the <xstring> header file is in the following location:
C:\Program files\Microsoft Visual Studio\VC98\Include
Change the _FROZEN enumeration constant to 0 in the <xstring> header file at line 62 so that it looks similar to the following:
enum _Mref {_FROZEN = 0}; // set to zero to disable sharing; original value 255
If you follow this recommendation, and you rebuild all software that uses these header files, your std::string class code will be more thread-safe. There are some caveats to that statement. Therefore, read the following workaround instructions carefully. After you disable reference counting by setting the _FROZEN enumeration constant to 0 in the <xstring> header file, use one of the following methods to work around this problem.

Method 1: Use static CRT linkage only

Modify the project settings in all your projects that use the std::string class to link to the static version of the Microsoft run-time library (CRT). You cannot use this approach if your project also has the Use MFC in a Shared DLL setting enabled. For each project, follow these steps:
  1. Open the project.
  2. On the Project menu, click Settings.
  3. In the Configurations list, click Release.
  4. Click the C/C++ tab, and then click Code Generation in the Category list.
  5. In the Runtime library list, click Multi-thread (/MT).
  6. In the Configurations list, click Debug.
  7. In the Runtime library list, click Multi-thread debug (/MTd).
  8. If there are other configurations in the Configurations list, set the appropriate Runtime library option for them also.
  9. Click OK, and then rebuild the project.
This workaround makes sure that all your code uses the modified version of the <xstring> file by static linking to the whole multi-threaded run-time library, including MFC. One possible problem is that your final code size will be larger than a dynamically linked version, perhaps enormously so.

Method 2: Use dynamic CRT linkage

If your project code must link to the runtime library (CRT) as a DLL, you must take a different approach. Dynamic CRT linkage is the default setting for DLL projects. Dependencies on other components such as MFC or third-party libraries that are licensed for use with your application, typically require dynamic linkage to the CRT. If your only dependency is MFC, you can use the Use MFC in a Static Library option, and apply Method 1. By default, when you create a new project in Microsoft Visual C++ 6.0, the project uses the CRT from a DLL.

The dynamic CRT linkage project setting links your application to implementations for some std::string class methods in the pre-built Microsoft CRT DLL that is named Msvcp60.dll. Because Microsoft built that DLL by using the unmodified <xstring> header file, the change to the _FROZEN constant that you made to the local copy of <xstring> is not honored for functions that are called out of that library. These include functions such as _Tidy(), and assign() that are supplied in the Msvcp60.dll file for the <char> and <short> instantiations of the basic_string class. The basic_string class is the base for the std::string class.

To use static implementations of the std::string class in your modules instead of the Microsoft-supplied implementations in the Msvcp60.dll file, follow these steps:
  1. In the <xstring> file, comment out the following code that is found near the end of the file. To do this, you can enclose the code in a #if 0/#endif block:
    #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. Other string operators are defined in the <string> header file, and they are also included in the CRT through the Msvcp60.dll file. You notice at the end of the <string> file that there are a series of "extern template _CRTIMP..." definitions that are protected by the #ifdef _DLL clause just like in the <xstring> file. Comment all these definitions out also:
    #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. Save the modifications to these files, and then rebuild all the projects in the application that use the STL. If your project declares a class class __declspec(dllexport), and that class has members of the std::string type, you see C4251 warnings. Because all your code is built with the std::string class now statically linked, you can ignore these warnings. To explicitly disable those warnings, use the following notation:
    #pragma warning(disable: 4251)
This approach balances the use of MFC and CRT functions other than the std::string class from DLLs. There is a small increase in code size in each of your modules that use the std::string class.

Method 3: Using a clever hack to avoid linkage issues

Create a typedef for unsigned char, and use that instead of the existing std::string class typedef. The typedef may take a form that is included in a header file in the source files of the application that use the std::string class. The typedef may appear similar to the following:
typedef std::basic_string<unsigned char> MyString;
#define string MyString
Any string literals that are used with this class must be cast or processed as unsigned char. There may be an increase in code size versus ease of implementation, and there are less linkage side effects.

Method 4: Using a custom std::string DLL

This option gains you the benefit of smallest code size by putting the std::string class implementations in a single DLL. Create a DLL project that exports the std::string class. Link to that DLL instead of to the standard Msvcp60.dll file. You must redistribute this new DLL together with your application. This is an advanced option.

MORE INFORMATION

The following C++ code examples demonstrate one scenario that may occur when there is a lack of synchronization:
...
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";
}
In this example, Thread1 makes a copy of the input argument, and raises the reference count on the shared character buffer to 1. While it is working, Thread2 also makes a copy of its input argument, and raises the reference count to 2. Meanwhile, the main thread assigns a new value to A, creates a new character buffer, and drops the reference count on the original shared buffer to 1.

Thread1 starts to create a new character buffer for the new assignment to A1, recognizes a positive reference count on its previous shared character buffer, and then decrements that count by 1 to 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. The reference count is now 0 instead of 1. The reference count would have been 0 if access to the reference counter had been synchronized.

When Thread2 assigns a new value to A2, Thread2 sees the reference count of 0 and discards the original shared character buffer that B still references. The memory that held the character buffer is now available for other uses in the application. However, std::string B still holds a pointer to the character buffer. The following scenarios cause corruption and crashes:
  • B tries to free the character buffer.
  • B tries to read the contents of the character buffer that have been overwritten with live data by other application code.
  • There are attempts to extend or to modify the string.

REFERENCES

For more information about Visual C++ language and compiler issues, see the Is the STL included with VC++ thread-safe? topic at the following Microsoft Most Valuable Professional (MVP) Web site:
http://www.mvps.org/vcfaq

Properties

Article ID: 813810 - Last Review: June 1, 2004 - Revision: 1.0
APPLIES TO
  • Microsoft Visual C++ 6.0 Service Pack 5
Keywords: 
kbthreadsync kbprb KB813810
Retired KB Content Disclaimer
This article was written about products for which Microsoft no longer offers support. Therefore, this article is offered "as is" and will no longer be updated.

Give Feedback

 

Contact us for more help

Contact us for more help
Connect with Answer Desk for expert help.
Get more support from smallbusiness.support.microsoft.com