Valgrind 在修改 C++ 中向量的引用返回变量后抱怨

问题描述 投票:0回答:1

我正在尝试通过使用引用来修改 std::vector,向量生命周期的设计方式是通过引用进行的修改始终有效,换句话说,引用的值在引用的生命周期中永远不会被释放.

这是“最小的、可重现的示例”:

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

这是 valgrind 的输出:

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

问题是:为什么 valgrind 会发生错误?

在原始程序中(不在最小可重现示例中),它在错误发生后运行了几毫秒,然后崩溃打印

malloc(): unsorted double linked list corrupted
,这与此内存错误有关吗?

c++ linux vector g++ valgrind
1个回答
0
投票

由于代码中存在未定义行为而报告错误(因此 Valgrind 做得很好)。

  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

当你对向量执行 Push_back 时,可能会发生两件事。

  • 保留缓冲区有足够的空间来添加新项目,并且旧引用仍然有效
  • 保留的缓冲区已被完全使用,因此必须分配新的缓冲区,必须将旧的项目复制(或移动)到新位置,并且必须释放旧的缓冲区。在这种情况下,旧参考将失效。

下面是证明这个问题的demo: 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;
}
© www.soinside.com 2019 - 2024. All rights reserved.