c++linuxvectorg++valgrind

Valgrind complain after modifing reference return variable from a vector in c++


I am trying to modify an std::vector through the use of references, the vector lifetime is designed in such a way modifications through the reference always are valid, in other words the the reference's value is never freed in the lifetime of the reference.

Here is the "minimal, reproducible example":

#include <vector>
class B {
public:
  B() { m_b = 1; }
  int &getInt() { return m_b; }

private:
  int m_b;
};
class A {
public:
  B &getB() {
    m_vec.push_back(B());
    return m_vec.back();
  }
  std::vector<B> getVec() { return m_vec; }

private:
  std::vector<B> m_vec;
};
int main() {
  A a;
  B &b1 = a.getB();
  int i = b1.getInt();
  B &b2 = a.getB();
  b1 = B();

  return 0;
}

Here is the valgrind's output:

==22925== Memcheck, a memory error detector
==22925== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==22925== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==22925== Command: ./test
==22925== 
==22925== Invalid write of size 4
==22925==    at 0x1092B5: main (test.cpp:27)
==22925==  Address 0x4deec80 is 0 bytes inside a block of size 4 free'd
==22925==    at 0x484BB6F: operator delete(void*, unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==22925==    by 0x109E28: __gnu_cxx::new_allocator<B>::deallocate(B*, unsigned long) (new_allocator.h:145)
==22925==    by 0x109B6F: std::allocator_traits<std::allocator<B> >::deallocate(std::allocator<B>&, B*, unsigned long) (alloc_traits.h:496)
==22925==    by 0x1098D9: std::_Vector_base<B, std::allocator<B> >::_M_deallocate(B*, unsigned long) (stl_vector.h:354)
==22925==    by 0x109A97: void std::vector<B, std::allocator<B> >::_M_realloc_insert<B>(__gnu_cxx::__normal_iterator<B*, std::vector<B, std::allocator<B> > >, B&&) (vector.tcc:500)
==22925==    by 0x1096E5: B& std::vector<B, std::allocator<B> >::emplace_back<B>(B&&) (vector.tcc:121)
==22925==    by 0x1094ED: std::vector<B, std::allocator<B> >::push_back(B&&) (stl_vector.h:1204)
==22925==    by 0x1093F4: A::getB() (test.cpp:14)
==22925==    by 0x10929D: main (test.cpp:26)
==22925==  Block was alloc'd at
==22925==    at 0x4849013: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==22925==    by 0x109FED: __gnu_cxx::new_allocator<B>::allocate(unsigned long, void const*) (new_allocator.h:127)
==22925==    by 0x109EAC: std::allocator_traits<std::allocator<B> >::allocate(std::allocator<B>&, unsigned long) (alloc_traits.h:464)
==22925==    by 0x109D65: std::_Vector_base<B, std::allocator<B> >::_M_allocate(unsigned long) (stl_vector.h:346)
==22925==    by 0x1099C0: void std::vector<B, std::allocator<B> >::_M_realloc_insert<B>(__gnu_cxx::__normal_iterator<B*, std::vector<B, std::allocator<B> > >, B&&) (vector.tcc:440)
==22925==    by 0x1096E5: B& std::vector<B, std::allocator<B> >::emplace_back<B>(B&&) (vector.tcc:121)
==22925==    by 0x1094ED: std::vector<B, std::allocator<B> >::push_back(B&&) (stl_vector.h:1204)
==22925==    by 0x1093F4: A::getB() (test.cpp:14)
==22925==    by 0x10927C: main (test.cpp:24)
==22925== 
==22925== 
==22925== HEAP SUMMARY:
==22925==     in use at exit: 0 bytes in 0 blocks
==22925==   total heap usage: 3 allocs, 3 frees, 72,716 bytes allocated
==22925== 
==22925== All heap blocks were freed -- no leaks are possible
==22925== 
==22925== For lists of detected and suppressed errors, rerun with: -s
==22925== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

The question is: why the error is happening in valgrind?

In the original program (Not in the minimal reproducible example), it runs for some miliseconds after the bug and then it crashes printing malloc(): unsorted double linked list corrupted, is that related to this memory bug?


Solution

  • Error is reported since you have Undefined Behavior in your code (so Valgrind did a good job).

      A a;
      B &b1 = a.getB(); // here b1 is valid reference
      int i = b1.getInt(); // here b1 is valid reference
      B &b2 = a.getB(); // here b2 is valid reference, but b1 becomes invalid
      b1 = B(); // here you are using invalid reference
    

    When you do a push_back on a vector two thing may happen.

    Here is demo to prove this problem: https://godbolt.org/z/6j3c3xoGs

    #include <print>
    #include <source_location>
    #include <vector>
    #include <iostream>
    
    class B {
    public:
        B() { m_b = 1; }
        int& getInt() { return m_b; }
    
    private:
        int m_b;
    };
    
    class A {
    public:
        A()
        {
    #ifdef USE_RESERVE
            m_vec.reserve(4);
    #endif
        }
        B& getB()
        {
            m_vec.push_back(B());
            return m_vec.back();
        }
        std::vector<B> getVec() { return m_vec; }
    
        void logStatus(const std::source_location l = std::source_location::current())
        {
            std::println("{} size:{} capacity: {}", l.line(), m_vec.size(), m_vec.capacity());
        }
    
    private:
        std::vector<B> m_vec;
    };
    int main()
    {
        A a;
        a.logStatus();
        B& b1 = a.getB();
        a.logStatus();
        [[maybe_unused]] int i = b1.getInt();
        a.logStatus();
        [[maybe_unused]] B& b2 = a.getB();
        a.logStatus();
        std::cout << std::flush;
        b1 = B();
    
        return 0;
    }