0

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?

4
  • 1
    When you use push_back all iterators and references to the contents of the vector are (potentially) invalidated. valgrind is correct. You can avoid this by using reserve to increase the capacity of the vector. If push_back does not increase the size beyond the current capacity then iterators and references remain valid.
    – john
    Commented Jul 5 at 14:55
  • Oh you are right, that is why my program was crashing, the vector can do an allocation invalidating everything in the vector. Thanks Commented Jul 5 at 15:03
  • 1
    Possible dupe
    – john
    Commented Jul 5 at 15:03
  • Address sanitizer also finds issue: godbolt.org/z/8436Kran6
    – Marek R
    Commented Jul 5 at 15:20

1 Answer 1

2

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.

  • reserved buffer has enough space to add new item and old references remain valid
  • reserved buffer is already in full use, so new buffer have to be allocated, old items has to be copied (or moved) to new location and old bufer must be freed. In such case old reference become invalid.

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;
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.