Re: Possible source of memory corruption
On Tue, 10 Jun 2008 21:01:01 -0700, WhiteFang
<WhiteFang@discussions.microsoft.com> wrote:
May I ask if you have any strings in those vectors, if so where does the
string value come from?
I don't get the question.
Whether or not this is your problem, you may want to be aware of a potential
problem with strings in a multithreaded environment - definitely with STL
strings, and I suspect with CStrings, though I am not sufficiently familiar
with the implementation of CString to know for sure whether the issue exists
with them.
I'll illustrate a problem situation by use of globally declared string that
is read (but not written) by multiple threads:
For example, say there is a global string such as:
std::string g_strBlurb;
Suppose that multiple threads do something like:
void CThingamajig::vDoStuff()
{
std::string strZing = g_strBlurb; // CAUSES MEMORY CORRUPTION!
.
.
}
The above code can cause memory corruption (even if vDoStuff() does nothing
except return after the string assignment statement) .
How does this cause memory corruption? After all, the above code doesn't
modify the shared string (g_strBlurb).
Or does it?
In order to be memory efficient, the above assignment statement doesn't
allocate memory for, and copy the text of the string (g_strBlurb).
Instead, a reference counter is incremented (it is located in memory just
before the string text)
The last version of VC to use reference-counting for std::string was VC6,
and it ceased to be a problem in VC.NET 2002.
What you've shown should not be a problem for CString, which uses
InterlockedXXX to manage reference counts. However, read this whole thread
for a discussion on problems with CString's copy-on-write method:
http://groups.google.com/group/comp.os.ms-windows.programmer.tools.mfc/browse_frm/thread/7d24ab155565bb6d/fe87c85bf1590ebe?#fe87c85bf1590ebe
I think the memory leak I posited is still possible, though it appears they
fixed the problem I described with MFC 4.1's CopyBeforeWrite function in
its MFC9 counterpart, Fork, and I would bet much earlier than that.
Think of the string assignment code as doing something like this:
1) Read string reference count from memory into Register A
2) Add 1 to Register A
3) Write Register A to the string reference count in memory
.
.
Suppose that the string has a reference count of 1 (say it's been assigned
to at program startup, and not referenced since).
Now suppose thread 1 performs step 1, reading a ref count of 1 into Register
A. Supose thread 1 is then suspended, and thread 2 begins executing. Thread 2
then performs steps 1 thru 3, storing a ref count of 2 into memory.
Suppose thread 2 is then suspended before returning from vDoStuff(), and
thread 1 continues.
Thread 1 continues at step 2 above, also storing a ref count of 2 into
memory for the string (the correct value is 3, but thread one was interrupted
during the ref count increment process).
Eventually, vDoStuff() is exited in each thread, causing the destructor for
strZing to decrement the string ref count, causing it to become zero (in
whichever thread last exits vDoStuff()). When the ref count becomes zero, the
string self destructs (and the allocated memory is deallocated).
Subsequent access of g_strBlurb will access memory that has been returned to
the heap.
One way to get around this (other than using access control entities, such
as critical sections) is to create a function such as this:
inline std:string strThreadSafeStrCopy(const string& str)
{
std:string _str = str.c_str();
return _str;
}
Then, you can use a statement such as:
std:string strZing = strThreadSafeStrCopy( g_strBlurb );
By making it a function (rather than just using directly invoking the
c_str() method everyplace you want a thread safe copy):
* it is very easy to locate every line of code where a thread safe string
copy has been coded for
* the implementation of thread safety (such as using the c_str() method)
could be changed
* need/non-need for thread safe copy might be detected at compile time, so
that either efficient or thread safe code could be generated appropriately
* it's more obvious to other programmers why a string copy is being done in
a particular manner
Again, reference-counting std::string has been recognized for a long time
as a losing idea due to the difficulty in doing it in a thread-safe way,
and the fact that the C++ Standard requires string::reference to be a real
reference, not a proxy class, which makes it impossible to use real
copy-on-write. The standard committee tried to please everybody, and they
ended up with a badly flawed specification, which besides failing to
describe a genuine copy-on-write string class, imposed lots of weird little
rules that were necessary to make it almost sorta kinda work. You don't
need to do the sorts of things represented in your strThreadSafeStrCopy in
VC.NET 2002 and later, which abandoned reference counting for a "short
string" optimization that doesn't have these problems. As for VC6, see
"Fixes to <xstring>" here:
http://www.dinkumware.com/vc_fixes.html
As for MFC's CString, it avoids the atomicity problem you described by
using Interlocked(Increment|Decrement). However, because MFC9 continues to
do things like check the refcount and make copy-on-write decisions without
synchronizing the whole operation from checking to forking, it appears it
remains susceptible to the memory leak I described in the thread I linked
to above. See my last message in that thread for a couple of suggestions
from an MS guy that should still be valid for dealing with that.
--
Doug Harrison
Visual C++ MVP