Home > database >  Why G 12 vector insert optimization breaks code?
Why G 12 vector insert optimization breaks code?

Time:07-06

Last time I've compiled my project by new GCC12 and it gave me warning about bad memmove buffer size. The previous versions of the GCC had not report such warnings.

The simple code example which presents my situation:

#include <vector>                                                              
#include <cstdint>                                                             
#include <iostream>                                                            
                                                                               
struct [[gnu::packed]] S {                                                     
  uint32_t a;                                                                  
  uint8_t  b;                                                                  
  uint16_t c;                                                                  
};                                                                             
                                                                               
std::vector<uint8_t> s_to_bytes(const S &s) {                                  
  S x = s;                                                                     
                                                                               
  std::vector<uint8_t> res;                                                    
  res.insert                                                                   
    ( res.begin()                                                              
    , reinterpret_cast<uint8_t*>(&x)                                           
    , reinterpret_cast<uint8_t*>(&x)   sizeof(S)                               
    );                                                                         
  return res;                                                                  
}                                                                              
                                                                               
int main () {                                                                  
                                                                               
  S s{1,2,3};                                                                  
  auto bytes = s_to_bytes(s);                                                  
  for (auto &x : bytes) std::cout << static_cast<int>(x) << " ";               
  std::cout << std::endl;                                                      
                                                                               
  return 0;                                                                    
}   

And when I compile this code by G 12 by command

g   -Wall -Wextra -O2 -Wpedantic -std=c  20 invalid_optimization.cpp

it prints warning:

inlined from ‘std::vector<unsigned char> s_to_bytes(const S&)’ at invalid_optimization.cpp:16:5:
/usr/include/c  /12/bits/stl_algobase.h:431:30: warning: ‘void* __builtin_memmove(void*, const void*, long unsigned int)’ writing 1 or more bytes into a region of size 0 overflows the destination [-Wstringop-overflow=]
  431 |             __builtin_memmove(__result, __first, sizeof(_Tp) * _Num);
      |             ~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In member function ‘_Tp* std::__new_allocator<_Tp>::allocate(size_type, const void*) [with _Tp = unsigned char]’,
    inlined from ‘constexpr _Tp* std::allocator< <template-parameter-1-1> >::allocate(std::size_t) [with _Tp = unsigned char]’ at /usr/include/c  /12/bits/allocator.h:183:40,
    inlined from ‘static constexpr _Tp* std::allocator_traits<std::allocator<_Up> >::allocate(allocator_type&, size_type) [with _Tp = unsigned char]’ at /usr/include/c  /12/bits/alloc_traits.h:464:28,
    inlined from ‘constexpr std::_Vector_base<_Tp, _Alloc>::pointer std::_Vector_base<_Tp, _Alloc>::_M_allocate(std::size_t) [with _Tp = unsigned char; _Alloc = std::allocator<unsigned char>]’ at /usr/include/c  /12/bits/stl_vector.h:378:33,
    inlined from ‘constexpr std::_Vector_base<_Tp, _Alloc>::pointer std::_Vector_base<_Tp, _Alloc>::_M_allocate(std::size_t) [with _Tp = unsigned char; _Alloc = std::allocator<unsigned char>]’ at /usr/include/c  /12/bits/stl_vector.h:375:7,
    inlined from ‘constexpr void std::vector<_Tp, _Alloc>::_M_range_insert(iterator, _ForwardIterator, _ForwardIterator, std::forward_iterator_tag) [with _ForwardIterator = unsigned char*; _Tp = unsigned char; _Alloc = std::allocator<unsigned char>]’ at /usr/include/c  /12/bits/vector.tcc:787:40,
    inlined from ‘constexpr void std::vector<_Tp, _Alloc>::_M_insert_dispatch(iterator, _InputIterator, _InputIterator, std::__false_type) [with _InputIterator = unsigned char*; _Tp = unsigned char; _Alloc = std::allocator<unsigned char>]’ at /usr/include/c  /12/bits/stl_vector.h:1779:19,
    inlined from ‘constexpr std::vector<_Tp, _Alloc>::iterator std::vector<_Tp, _Alloc>::insert(const_iterator, _InputIterator, _InputIterator) [with _InputIterator = unsigned char*; <template-parameter-2-2> = void; _Tp = unsigned char; _Alloc = std::allocator<unsigned char>]’ at /usr/include/c  /12/bits/stl_vector.h:1481:22,
    inlined from ‘std::vector<unsigned char> s_to_bytes(const S&)’ at invalid_optimization.cpp:16:5:
/usr/include/c  /12/bits/new_allocator.h:137:55: note: at offset 7 into destination object of size 7 allocated by ‘operator new’
  137 |         return static_cast<_Tp*>(_GLIBCXX_OPERATOR_NEW(__n * sizeof(_Tp)));

This warning shows that insert into the vector was optimized to __builtin_memmove(__result, __first, sizeof(_Tp) * _Num); call, but with invalid size. The std::vector insert documentation tells that inserting is performed in range [first, last), so the the end of the structure pointer must point to the next byte after structure, just like iterator .end() works, but GCC optimization makes from this valid code invalid memmove call, which probably got structure size 1 instead of just structure size. Looks like __builtin_memmove got just difference of first and last pointers instead of last - first - 1.

My question is: is my vector::insert usage invalid or is it GCC12 optimization bug?

CodePudding user response:

My question is: is my vector::insert usage invalid or is it GCC12 optimization bug?

This is a warning bug in gcc as mentioned in the comments. Reduced

#include <vector>
#include <array>

std::vector<int> s_to_bytes() {
  std::vector<int> res;
  std::array<int, 4> s{};
  res.insert(res.begin(), s.begin(), s.end());
  return res;
}

The above code is undoubtedly well-formed, but gcc-12 gives an unexpected overflow warning for __builtin_memmove.

Bug filed 106199.

  • Related