Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

emplace_back() issue under VS2013

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.4is 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.

like image 963
zangw Avatar asked Jan 04 '16 06:01

zangw


2 Answers

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).

like image 127
1201ProgramAlarm Avatar answered Sep 22 '22 21:09

1201ProgramAlarm


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 array

So, 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.

like image 38
zangw Avatar answered Sep 21 '22 21:09

zangw