Consider the following codes
std::vector<int> nums{21, 22, 23, 24};
nums.emplace_back(nums[0]);
nums.emplace_back(nums[1]);
for (auto n : nums) {
std::cout << n << std::endl;
}
Output of VS2013
21
22
23
24
-17891602
22
Why the -17891602
is here?
Output of GCC 4.8.4
is correct as following
21
22
23
24
21
22
Then I compare the implementation of emplace_back
between VS2013
and GCC
VS2013
template<class... _Valty>
void emplace_back(_Valty&&... _Val)
{ // insert by moving into element at end
if (this->_Mylast == this->_Myend)
_Reserve(1);
_Orphan_range(this->_Mylast, this->_Mylast);
this->_Getal().construct(this->_Mylast,
_STD forward<_Valty>(_Val)...);
++this->_Mylast;
}
GCC
template<typename _Tp, typename _Alloc>
template<typename... _Args>
void
vector<_Tp, _Alloc>::
emplace_back(_Args&&... __args)
{
if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage)
{
_Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
std::forward<_Args>(__args)...);
++this->_M_impl._M_finish;
}
else
_M_emplace_back_aux(std::forward<_Args>(__args)...);
}
It seems the weird _Reserve(1);
is used in VS2013
. Why?
Edit:
The hex
value of -17891602
is 0xFEEEFEEE
, which means
Used by Microsoft's debug HeapFree() to mark freed heap memory
refer to magic number
Then I debugged the above codes line by line and found the 0XFEEEFEEE
caused by _Reserve(1);
invoked.
This is a problem in VS2013 and VS2015 when emplacing an element into a vector that contains the element. If the vector resizes, the reference to the element being inserted is invalid. The work around is to create a copy of the element in insert, then insert that.
auto n = nums[0];
nums.emplace_back(n);
The _Reserve call is there to ensure there is some memory allocated for the vector (so it doesn't have to be checked for in later operations).
The emplace issue
Objects bound to the function parameter pack of the
emplace
member function shall not be elements or sub-objects of elements of the container.
The emplace_back()
is called in the emplace()
function under VS2013
.
template<class... _Valty>
iterator emplace(const_iterator _Where, _Valty&&... _Val)
{ // insert by moving _Val at _Where
size_type _Off = _VIPTR(_Where) - this->_Myfirst;
#if _ITERATOR_DEBUG_LEVEL == 2
if (size() < _Off)
_DEBUG_ERROR("vector emplace iterator outside range");
#endif /* _ITERATOR_DEBUG_LEVEL == 2 */
emplace_back(_STD forward<_Valty>(_Val)...);
_STD rotate(begin() + _Off, end() - 1, end());
return (begin() + _Off);
}
I found one good post, which describe some details of emplace_back()
implementation under VS2013
.
std::vector
class has different instance members (both regular and internal) and among them are the following:
_Myfirst
- points to the beginning of the data array_Mylast
- points to the first uninitialized element in the data array. If equals to _Myend, next insertion will cause reallocation. You get this guy on end()
call_Myend
- points to the end of the data arraySo, in terms of memory addresses, the following inequality takes place:
_Myfirst <=<= _Mylast <=<= _Myend
See that line with _Reserve(1)
in it? This function call causes our bug to reveal itself.
Let's work through step-by-step (refer to previous example function).
nums.emplace_back(nums[0]);
First we get a reference to the item because operator[]
returns a reference
reference operator[](size_type _Pos)
{ ... }
Then we move into emplace_back
method, passing fresh and valid reference to the item we want to insert. What we immediately see at the beginning is a check on vector's size exceeding. As long as our insertion causes a vector to grow its size, we get reference invalidated just after reallocation happens. That's the reason of such interesting but expected (once we got into implementation) behavior.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With