std::map access violation (or not)

— richardwb on Saturday, October 11, 2008 @ 18:42

I just spent some time tracking down this fun little bug:

I use a mix of std::map and Boost.Function to reduce the ugliness of the Windows message loop. Everything is working just fine, until I make a couple of changes and all of a sudden one of my std::maps is throwing an exception in the destructor:

Access violation reading location 0xccccccd4.

In this code in xtree:

#if _HAS_ITERATOR_DEBUGGING
    void _Orphan_ptr(_Myt& _Cont, _Nodeptr _Ptr) const
    {       // orphan iterators with specified node pointers
        _Lockit _Lock(_LOCK_DEBUG);
        const_iterator **_Pnext = (const_iterator **)&_Cont._Myfirstiter;
        while (*_Pnext != 0)
        {
            if ((*_Pnext)->_Ptr == _Myhead
                || _Ptr != 0 && (*_Pnext)->_Ptr != _Ptr)
            {
                _Pnext = (const_iterator **)&(*_Pnext)->_Mynextiter;
            }
            else
            {       // orphan the iterator
                (*_Pnext)->_Mycont = 0;
                *_Pnext = (const_iterator *)(*_Pnext)->_Mynextiter;
            }
        }
    }
#endif /* _HAS_ITERATOR_DEBUGGING */

Of course, that is suspiciously close to 0xcccccccc, which is used by the debugging runtimes to mark uninitalized stack. (This will be important later.) In any case I go through the usual debugging motions, making sure I'm not putting anything funny into my map container and that values inside the container are what they should be.

So, what was the cause of this bug? It was a result of the change I was making earlier; this is the original code:

LVITEM itemText;
ZeroMemory(&itemText, sizeof(itemText));
itemText.cchTextMax = MAX_PATH;
char buff[MAX_PATH];
itemText.pszText = buff;
SendMessage(listViewHandle, LVM_GETITEMTEXT, index, reinterpret_cast<LPARAM>(&itemText));

And I changed it to use the ListView_GetItemText macro, which is far more succinct:

char itemText[MAX_PATH];
ListView_GetItemText(listViewHandle, index, 0, itemText, _countof(itemText));

No problem yet, right? Ready for the punchline?

I left this line in at the bottom:

SendMessage(listViewHandle, LVM_GETITEMTEXT, index, reinterpret_cast<LPARAM>(&itemText));

For those lucky folks who aren't familiar with all the wonderful intricacies of Windows programming:

SendMessage() is a magical "do all" function that sends a message to a particular window, with WPARAM and LPARAM values tagging along. In this case, we're sending a message of LVM_GETITEMTEXT to the control denoted by listViewHandle. The MSDN documentation for LVM_GETITEMTEXT states that it wants WPARAM and LPARAM to be:

WPARAM : iItem - Index of the list-view item
LPARAM : pitem - Pointer to an LVITEM structure.

My LPARAM, itemText, is no longer an LVITEM structure (it is now a char array of size MAX_PATH). There is absolutely no way that the LVM_GETITEMTEXT code can know that I've made a colossal (yet tiny!) mistake, so I assume that it dutifully casts itemText into an LVITEM structure and does the right stuff with horrendously wrong data. Tada, stack corrupted.

Notice that the source of this bug appears nowhere near an std::map. It is, in fact, in a different function (and a different class and a different file...) entirely! This is why memory corruption errors are one of the most feared bugs around.

Conclusion: if std::map is dying in clear() (or ever, frankly), you may very well be doing something bad, memory wise.

comments powered by Disqus