Re: std::move and vc10: bug or misunderstanding?
On May 24, 3:29 am, Daniel <danielapar...@gmail.com> wrote:
class A
{
public:
void value(std::string s)
{
s_ = s;
}
std::string s_;
};
class B
{
public:
void f()
{
for (size_t i = 0; i < 10; ++i)
{
buffer_ = "Hello world";
a.value(std::move(buffer_));
}
}
A a;
std::string buffer_;
};
BOOST_AUTO_TEST_CASE( test1 )
{
B b;
b.f();
}
When run, boost reports 9 occurrences of memory leaks. These leaks vanish when the signature of the value method is changed to value(std:;string&& s); But the original program shouldn't leak, should it? Or am I misunderstanding something?
This looks a bug in MS' implementation of std::string.
The leaked thing is allocated in this function:
file xstring line 1981
void _Copy(size_type _Newsize, size_type _Oldlen)
{ // copy _Oldlen elements to newly allocated buffer
size_type _Newres = _Newsize | this->_ALLOC_MASK;
if (max_size() < _Newres)
_Newres = _Newsize; // undo roundup if too big
else if (this->_Myres / 2 <= _Newres / 3)
;
else if (this->_Myres <= max_size() - this->_Myres / 2)
_Newres = this->_Myres
+ this->_Myres / 2; // grow exponentially if possible
else
_Newres = max_size(); // settle for max_size()
_Elem *_Ptr;
_TRY_BEGIN
_Ptr = this->_Alval.allocate(_Newres + 1);
_CATCH_ALL
_Newres = _Newsize; // allocation failed, undo roundup and retry
_TRY_BEGIN
_Ptr = this->_Alval.allocate(_Newres + 1);
_CATCH_ALL
_Tidy(true); // failed again, discard storage and reraise
_RERAISE;
_CATCH_END
_CATCH_END
if (0 < _Oldlen)
_Traits::copy(_Ptr, _Myptr(), _Oldlen); // copy existing elements
_Tidy(true);
this->_Bx._Ptr = _Ptr;
this->_Myres = _Newres;
_Eos(_Oldlen);
}
By the allocate() call in the middle. The pointer is stored in the
union that holds the sting state -- the 16 byte directly or the
pointer to the allocated external storage. It is immediately trampled
over by the last line, calling _Eos, that looks at the length of 11
and treats the union as directly holding the material, applying the 0
terminator at front. The bug is that we should not be here in the
first place.
The caller is
xstring:1957
bool _Grow(size_type _Newsize,
bool _Trim = false)
{ // ensure buffer is big enough, trim to size if _Trim is true
if (max_size() < _Newsize)
_Xlen(); // result too long
if (this->_Myres < _Newsize)
_Copy(_Newsize, this->_Mysize); // reallocate to grow
else if (_Trim && _Newsize < this->_BUF_SIZE)
_Tidy(true, // copy and deallocate if trimming to small string
_Newsize < this->_Mysize ? _Newsize : this->_Mysize);
else if (_Newsize == 0)
_Eos(0); // new size is zero, just null terminate
return (0 < _Newsize); // return true only if more work to do
}
the second if.
I'm lazy to look further, my best guess is that a previous move-from
breaks the internal state, setting the "capacity" to zero instead of
16, that triggers this Grow.
The bug happens only if the content fits the small-string buffer (15+1
chars), with bigger one it is okay.
Good addition to my list of why we should avoid this crapbag called
std::string. :-(
I guess MS will not release any corrections to VS2010 (I recall a deal
of serious bugs closed with nofix), so if you want to actually use
something like this, find the source problem in the move assignment,
then install the modified xstring file to all your machines. Or use
CString or some other sensible string implementation.