Stroustroup:free():实现 vector::reserve() 时在 tcache 2 中检测到双重释放
Stroustroup: free(): double free detected in tcache 2 when implementing vector::reserve()
Bjarne Stroustrup - 编程原理和实践 - 第 19.5.6 章“向量的 RAII”中给出的代码在调用 push_back() 然后 reserve() 时不起作用。他们分开工作正常。
我收到此错误“free():在 tcache 2 中检测到双重释放”
通过调试,我看到在调用 swap() inside reserve() 之后,变量 b.elem[0] 有
<错误:无法访问地址为 0x55555556d 的内存>
书上的uninitialized_copy写错了,是不是还漏了什么?还是我做错了什么?
更新 17.08: 我为 vector_base 和向量(受 C++ 编程语言启发)添加了构造函数、析构函数、复制和移动操作,现在可以使用了。谢谢回复!
我有一个问题:《编程原理与实践》使向量class成为vector_baseclass的导数,而《C++程序设计》 Language”使 vector_base 对象成为 class 向量的数据成员。我想知道哪个选项更好?不过它们看起来是一样的。
#include <iostream>
#include <stdexcept>
#include <string>
#include <algorithm>
template <typename T>
class allocator
{
public:
T *allocate(int n) { return (T *)malloc(n * sizeof(T)); } // allocate space for n objects of type T
void construct(T *p, const T &v) { new (p) T{v}; } // construct a T with the value v in p
void destroy(T *p) { p->~T(); } // destroy the T in p
void deallocate(T *p, int n) { free(p); } // deallocate n objects of type T starting at p
};
template <typename T, typename A = allocator<T>>
struct vector_base
{
A alloc; // allocator,
T *elem; // start of allocation
int sz; // number of elements
int space; // amount of allocated space
vector_base()
: sz{0}, elem{nullptr}, space{0} {}
vector_base(const A &a, int n)
: alloc{a}, elem{alloc.allocate(n)}, sz{0}, space{n}
{
}
~vector_base() { alloc.deallocate(elem, space); }
vector_base(const vector_base &) = delete; // no copy operations
vector_base &operator=(const vector_base &) = delete;
vector_base(vector_base &&); // move operations
vector_base &operator=(vector_base &&);
};
template <class T, class A>
vector_base<T, A>::vector_base(vector_base &&a) // move constructor
: alloc{a.alloc}, elem{a.elem}, sz{a.sz}, space{a.space} // no free space if space{a.sz}
{
a.elem = nullptr;
a.sz = a.space = 0;
}
template <class T, class A>
vector_base<T, A> &vector_base<T, A>::operator=(vector_base &&a) // move assignment
{
elem = a.elem;
sz = a.sz;
space = a.space; // also move space
a.elem = nullptr;
a.sz = a.space = 0;
return *this;
// swap(*this, a); // also works
// return *this;
}
template <typename T, typename A = allocator<T>>
class vector : private vector_base<T, A>
{
public:
vector()
: vector_base<T, A>() {}
explicit vector(int s);
vector(std::initializer_list<T> lst); // 18.2 initializer-list constructor
vector(const vector &); // copy constructor
vector &operator=(const vector &); // copy assignment
vector(vector &&); // move constructor
vector &operator=(vector &&); // move assignment
~vector(); // destructor
T &operator[](int n) { return this->elem[n]; }
const T &operator[](int n) const { return this->elem[n]; }
int size() const { return this->sz; }
int capacity() const { return this->space; }
T *get_elem() const { return this->elem; }
A get_alloc() const { return this->alloc; }
void reserve(int newalloc);
void push_back(const T &d);
void resize(int newsize, T val = T());
};
template <typename T, typename A>
vector<T, A>::vector(int s)
: vector_base<T, A>(this->alloc, s)
{
for (int i = 0; i < s; ++i)
this->alloc.construct(&this->elem[i], T());
}
template <typename T, typename A>
vector<T, A>::vector(std::initializer_list<T> lst)
: vector_base<T, A>(this->alloc, lst.size())
{
std::copy(lst.begin(), lst.end(), this->elem);
this->sz = lst.size();
}
template <typename T, typename A>
vector<T, A>::vector(const vector &a) // copy constructor
: vector_base<T, A>(a.alloc, a.size())
{
uninitialized_copy(a.elem, a.elem + a.sz, this->elem);
this->sz = a.sz;
}
template <typename T, typename A>
vector<T, A> &vector<T, A>::operator=(const vector &a) // copy assignment
{
if (this == &a) // self-assignment, no work needed
return *this;
if (a.sz <= this->space) // enough space, no need for new allocation
{
uninitialized_copy(a.get_elem(), &a.get_elem()[a.size()], this->elem);
this->sz = a.sz;
}
// took it from C++ Programming Language
vector temp{a}; // copy allocator
std::swap(*this, temp);
return *this;
}
template <typename T, typename A> // move constructor
vector<T, A>::vector(vector &&a)
: vector_base<T, A>(a.alloc, a.size()) // get a's data members (alloc, elem, space)
{
this->sz = a.sz; // also get a's size
a.elem = nullptr;
a.sz = a.space = 0;
}
template <typename T, typename A>
vector<T, A> &vector<T, A>::operator=(vector<T, A> &&a) // move assignment
//: vector_base<T, A>(a.alloc, a.size())
{
for (int i = 0; i < this->sz; ++i)
this->alloc.destroy(&this->elem[i]);
this->alloc.deallocate(this->elem, this->space);
this->elem = a.elem;
this->sz = a.sz;
this->space = a.space;
a.elem = nullptr;
a.sz = a.space = 0;
return *this;
}
template <typename T, typename A>
vector<T, A>::~vector() // destructor
{
for (int i = 0; i < this->sz; ++i)
this->alloc.destroy(&this->elem[i]);
}
template <typename T, typename A>
void print(const vector<T, A> &v, const std::string &s)
{
std::cout << '\n';
std::cout << s << ' ' << "size: " << v.size() << ' ' << "capacity: " << v.capacity() << ' ';
std::cout << "elements: ";
for (int i = 0; i < v.size(); ++i)
std::cout << v[i] << ' ';
std::cout << '\n';
}
template <typename T, typename A>
void vector<T, A>::reserve(int newalloc)
{
if (newalloc <= this->space)
return; // never decrease allocation
vector_base<T, A> b(this->alloc, newalloc); // allocate new space
uninitialized_copy(this->elem, &this->elem[size()], b.elem); // copy from caller of reserve() to b
b.sz = this->sz; // vector_base b has size = 0 because of the constructor so you have to change it
for (int i = 0; i < this->sz; ++i)
this->alloc.destroy(&this->elem[i]); // destroy old
std::swap<vector_base<T, A>>(*this, b); // swap representations
} // at exit ~vector_base will deallocate b
template <typename T, typename A> // 19.3.7
void vector<T, A>::push_back(const T &val)
{
if (this->space == 0)
reserve(8);
else if (this->sz == this->space)
reserve(2 * this->space);
this->alloc.construct(&this->elem[this->sz], val); // add val to position sz
++this->sz;
}
template <typename T, typename A>
void vector<T, A>::resize(int newsize, T val) // deleted =T() from here
{
reserve(newsize);
for (int i = this->sz; i < newsize; ++i) // if newsize is bigger than currect size construct default values
this->alloc.construct(&this->elem[i], val); // construct
for (int i = newsize; i < this->sz; ++i) // if newsize is smaller than currenct size destroy the elements
this->alloc.destroy(&this->elem[i]); // destroy
this->sz = this->space= newsize; // reset space - like in C++PL
}
int main()
try
{
vector<std::string> vs;
vs.push_back("test");
print(vs, "vs.push_back():");
vs.reserve(10);
print(vs, "vs.reserve(10):");
}
catch (std::exception &e)
{
std::cerr << "exception: " << e.what() << '\n';
return 1;
}
catch (...)
{
std::cerr << "exception\n";
return 2;
}
问题是示例中的 vector_base
没有遵循 5 的规则(之前是 3)。它实现了一个释放资源的析构函数,但没有实现复制/移动构造函数或赋值运算符来处理它。当 reserve
使用 vector_base
参数调用 swap
时,它将移动(先前复制)实例,然后执行两次移动(先前复制)分配,之后中间对象被销毁...和该析构函数释放了仍由其中一个参数拥有的同一指针,当在该对象的析构函数中再次释放该指针时,会导致未定义的行为。
一个正确的 C++03 实现是删除 ~vector_base()
并在 vector::reserve
和 vector::~vector
的末尾取消分配(需要添加)。或者您可以在 vector_base
中使用 std::auto_ptr
。您还需要定义向量的复制构造函数和赋值运算符。
在 C++11 及更高版本中,您将为 vector_base
定义移动构造函数和赋值运算符。
Bjarne Stroustrup - 编程原理和实践 - 第 19.5.6 章“向量的 RAII”中给出的代码在调用 push_back() 然后 reserve() 时不起作用。他们分开工作正常。
我收到此错误“free():在 tcache 2 中检测到双重释放”
通过调试,我看到在调用 swap() inside reserve() 之后,变量 b.elem[0] 有
<错误:无法访问地址为 0x55555556d 的内存>
书上的uninitialized_copy写错了,是不是还漏了什么?还是我做错了什么?
更新 17.08: 我为 vector_base 和向量(受 C++ 编程语言启发)添加了构造函数、析构函数、复制和移动操作,现在可以使用了。谢谢回复!
我有一个问题:《编程原理与实践》使向量class成为vector_baseclass的导数,而《C++程序设计》 Language”使 vector_base 对象成为 class 向量的数据成员。我想知道哪个选项更好?不过它们看起来是一样的。
#include <iostream>
#include <stdexcept>
#include <string>
#include <algorithm>
template <typename T>
class allocator
{
public:
T *allocate(int n) { return (T *)malloc(n * sizeof(T)); } // allocate space for n objects of type T
void construct(T *p, const T &v) { new (p) T{v}; } // construct a T with the value v in p
void destroy(T *p) { p->~T(); } // destroy the T in p
void deallocate(T *p, int n) { free(p); } // deallocate n objects of type T starting at p
};
template <typename T, typename A = allocator<T>>
struct vector_base
{
A alloc; // allocator,
T *elem; // start of allocation
int sz; // number of elements
int space; // amount of allocated space
vector_base()
: sz{0}, elem{nullptr}, space{0} {}
vector_base(const A &a, int n)
: alloc{a}, elem{alloc.allocate(n)}, sz{0}, space{n}
{
}
~vector_base() { alloc.deallocate(elem, space); }
vector_base(const vector_base &) = delete; // no copy operations
vector_base &operator=(const vector_base &) = delete;
vector_base(vector_base &&); // move operations
vector_base &operator=(vector_base &&);
};
template <class T, class A>
vector_base<T, A>::vector_base(vector_base &&a) // move constructor
: alloc{a.alloc}, elem{a.elem}, sz{a.sz}, space{a.space} // no free space if space{a.sz}
{
a.elem = nullptr;
a.sz = a.space = 0;
}
template <class T, class A>
vector_base<T, A> &vector_base<T, A>::operator=(vector_base &&a) // move assignment
{
elem = a.elem;
sz = a.sz;
space = a.space; // also move space
a.elem = nullptr;
a.sz = a.space = 0;
return *this;
// swap(*this, a); // also works
// return *this;
}
template <typename T, typename A = allocator<T>>
class vector : private vector_base<T, A>
{
public:
vector()
: vector_base<T, A>() {}
explicit vector(int s);
vector(std::initializer_list<T> lst); // 18.2 initializer-list constructor
vector(const vector &); // copy constructor
vector &operator=(const vector &); // copy assignment
vector(vector &&); // move constructor
vector &operator=(vector &&); // move assignment
~vector(); // destructor
T &operator[](int n) { return this->elem[n]; }
const T &operator[](int n) const { return this->elem[n]; }
int size() const { return this->sz; }
int capacity() const { return this->space; }
T *get_elem() const { return this->elem; }
A get_alloc() const { return this->alloc; }
void reserve(int newalloc);
void push_back(const T &d);
void resize(int newsize, T val = T());
};
template <typename T, typename A>
vector<T, A>::vector(int s)
: vector_base<T, A>(this->alloc, s)
{
for (int i = 0; i < s; ++i)
this->alloc.construct(&this->elem[i], T());
}
template <typename T, typename A>
vector<T, A>::vector(std::initializer_list<T> lst)
: vector_base<T, A>(this->alloc, lst.size())
{
std::copy(lst.begin(), lst.end(), this->elem);
this->sz = lst.size();
}
template <typename T, typename A>
vector<T, A>::vector(const vector &a) // copy constructor
: vector_base<T, A>(a.alloc, a.size())
{
uninitialized_copy(a.elem, a.elem + a.sz, this->elem);
this->sz = a.sz;
}
template <typename T, typename A>
vector<T, A> &vector<T, A>::operator=(const vector &a) // copy assignment
{
if (this == &a) // self-assignment, no work needed
return *this;
if (a.sz <= this->space) // enough space, no need for new allocation
{
uninitialized_copy(a.get_elem(), &a.get_elem()[a.size()], this->elem);
this->sz = a.sz;
}
// took it from C++ Programming Language
vector temp{a}; // copy allocator
std::swap(*this, temp);
return *this;
}
template <typename T, typename A> // move constructor
vector<T, A>::vector(vector &&a)
: vector_base<T, A>(a.alloc, a.size()) // get a's data members (alloc, elem, space)
{
this->sz = a.sz; // also get a's size
a.elem = nullptr;
a.sz = a.space = 0;
}
template <typename T, typename A>
vector<T, A> &vector<T, A>::operator=(vector<T, A> &&a) // move assignment
//: vector_base<T, A>(a.alloc, a.size())
{
for (int i = 0; i < this->sz; ++i)
this->alloc.destroy(&this->elem[i]);
this->alloc.deallocate(this->elem, this->space);
this->elem = a.elem;
this->sz = a.sz;
this->space = a.space;
a.elem = nullptr;
a.sz = a.space = 0;
return *this;
}
template <typename T, typename A>
vector<T, A>::~vector() // destructor
{
for (int i = 0; i < this->sz; ++i)
this->alloc.destroy(&this->elem[i]);
}
template <typename T, typename A>
void print(const vector<T, A> &v, const std::string &s)
{
std::cout << '\n';
std::cout << s << ' ' << "size: " << v.size() << ' ' << "capacity: " << v.capacity() << ' ';
std::cout << "elements: ";
for (int i = 0; i < v.size(); ++i)
std::cout << v[i] << ' ';
std::cout << '\n';
}
template <typename T, typename A>
void vector<T, A>::reserve(int newalloc)
{
if (newalloc <= this->space)
return; // never decrease allocation
vector_base<T, A> b(this->alloc, newalloc); // allocate new space
uninitialized_copy(this->elem, &this->elem[size()], b.elem); // copy from caller of reserve() to b
b.sz = this->sz; // vector_base b has size = 0 because of the constructor so you have to change it
for (int i = 0; i < this->sz; ++i)
this->alloc.destroy(&this->elem[i]); // destroy old
std::swap<vector_base<T, A>>(*this, b); // swap representations
} // at exit ~vector_base will deallocate b
template <typename T, typename A> // 19.3.7
void vector<T, A>::push_back(const T &val)
{
if (this->space == 0)
reserve(8);
else if (this->sz == this->space)
reserve(2 * this->space);
this->alloc.construct(&this->elem[this->sz], val); // add val to position sz
++this->sz;
}
template <typename T, typename A>
void vector<T, A>::resize(int newsize, T val) // deleted =T() from here
{
reserve(newsize);
for (int i = this->sz; i < newsize; ++i) // if newsize is bigger than currect size construct default values
this->alloc.construct(&this->elem[i], val); // construct
for (int i = newsize; i < this->sz; ++i) // if newsize is smaller than currenct size destroy the elements
this->alloc.destroy(&this->elem[i]); // destroy
this->sz = this->space= newsize; // reset space - like in C++PL
}
int main()
try
{
vector<std::string> vs;
vs.push_back("test");
print(vs, "vs.push_back():");
vs.reserve(10);
print(vs, "vs.reserve(10):");
}
catch (std::exception &e)
{
std::cerr << "exception: " << e.what() << '\n';
return 1;
}
catch (...)
{
std::cerr << "exception\n";
return 2;
}
问题是示例中的 vector_base
没有遵循 5 的规则(之前是 3)。它实现了一个释放资源的析构函数,但没有实现复制/移动构造函数或赋值运算符来处理它。当 reserve
使用 vector_base
参数调用 swap
时,它将移动(先前复制)实例,然后执行两次移动(先前复制)分配,之后中间对象被销毁...和该析构函数释放了仍由其中一个参数拥有的同一指针,当在该对象的析构函数中再次释放该指针时,会导致未定义的行为。
一个正确的 C++03 实现是删除 ~vector_base()
并在 vector::reserve
和 vector::~vector
的末尾取消分配(需要添加)。或者您可以在 vector_base
中使用 std::auto_ptr
。您还需要定义向量的复制构造函数和赋值运算符。
在 C++11 及更高版本中,您将为 vector_base
定义移动构造函数和赋值运算符。