我正在尝试通过使用引用来修改 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
,这与此内存错误有关吗?
由于代码中存在未定义行为而报告错误(因此 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;
}