1

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?

Shadasviar
  • 466
  • 5
  • 15
  • Looks like a bug to me. But don't use `[[gnu::packed]]`, it will lead to horrible code. Copy the 3 members of S individually. – Goswin von Brederlow Jul 05 '22 at 09:46
  • Even if `[[gnu::packed]]` is removed, compiler repots the warning. Please fill a bug report and add the link to it here please! – Klaus Jul 05 '22 at 09:54
  • 1
    Looks like such bug report already exists: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100366 – Shadasviar Jul 05 '22 at 10:04
  • 1
    I did not agree, because your code did not result in a warning in gcc 11.x. As the bug you reference on is valid for gcc 11.x you should open a new one. Even if it will fixed with the referenced one, there is a good chance that is not the same reasoning. Thank you! – Klaus Jul 05 '22 at 10:13

1 Answers1

1

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.

康桓瑋
  • 33,481
  • 5
  • 40
  • 90
  • Thanks for you bug report to GCC, I found existing one with same problem: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100366 – Shadasviar Jul 05 '22 at 10:05
  • In the 100366 problem occures only after `clear` called on the vector, but looks like same optimization issue with different methods of optimization forcing – Shadasviar Jul 05 '22 at 10:14
  • According @Klaus comment under my question, can you unmark your bug report as duplicate because different compiler versions are affected? – Shadasviar Jul 05 '22 at 10:16
  • No, it's not an optimization bug, it's just a warning bug. – Jonathan Wakely Jul 05 '22 at 16:29
  • There is no problem with `-O1`, so at least it has something to do with the *optimization* flag, right? By the way, the libstdc++ tag of that bug was not added by me. – 康桓瑋 Jul 05 '22 at 16:34