Я пытаюсь изменить 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
, связано ли это с этой ошибкой памяти?
О, вы правы, именно поэтому моя программа терпела сбой: вектор может выполнить распределение, делая недействительным все в векторе. Спасибо
Возможен обман
Дезинфицирующее средство для адресов также обнаруживает проблему: godbolt.org/z/8436Kran6
Сообщается об ошибке, поскольку в вашем коде есть неопределенное поведение (значит, 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 для вектора, могут произойти две вещи.
Вот демо, подтверждающее эту проблему: 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;
}
Когда вы используете
push_back
, все итераторы и ссылки на содержимое вектора (потенциально) становятся недействительными. Валгринд прав. Вы можете избежать этого, используяreserve
, чтобы увеличить емкость вектора. Еслиpush_back
не увеличивает размер сверх текущей емкости, итераторы и ссылки остаются действительными.