对于我正在编写的程序,我编写了一个简单的数组包装器类(想法是它应该是固定大小的。我知道我可以使用std :: vectors)并且在删除数组时遇到问题。 那些是我的构造者:
template <class T>
Array<T>::Array(size_t size): m_data(new T[size]), m_size(size)
{}
template <class T>
Array<T>::Array(const std::vector<T> &vec): m_size(vec.size())
{
std::allocator<T> a;
m_data = a.allocate(m_size);
for(int i = 0; i<m_size; i++)
{
new (m_data+i) T(vec[i]);
}
}
这是我的析构函数:
template <class T>
Array<T>::~Array()
{
delete[] m_data;
}
我用valgrind试图找出发生了什么,但它没有帮助。
==20840== Invalid read of size 8
==20840== at 0x10ABB8: sapph_dijkstra::Array<sapph_dijkstra::Node>::~Array() (in /home/sapphie/dijkstra/test)
==20840== by 0x1091CF: sapph_dijkstra::MinHeap::~MinHeap() (minheap.h:40)
==20840== by 0x109021: main (test.c:20)
==20840== Address 0x5b21318 is 8 bytes before a block of size 400 alloc'd
==20840== at 0x4C3017F: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==20840== by 0x109F99: __gnu_cxx::new_allocator<sapph_dijkstra::Node>::allocate(unsigned long, void const*) (new_allocator.h:111)
==20840== by 0x10AA36: sapph_dijkstra::Array<sapph_dijkstra::Node>::Array(std::vector<sapph_dijkstra::Node, std::allocator<sapph_dijkstra::Node> > const&) (in /home/sapphie/dijkstra/test)
==20840== by 0x10A232: sapph_dijkstra::MinHeap::MinHeap(std::vector<sapph_dijkstra::Node, std::allocator<sapph_dijkstra::Node> > const&) (in /home/sapphie/dijkstra/test)
==20840== by 0x108FD1: main (test.c:20)
==22059== Invalid free() / delete / delete[] / realloc()
==22059== at 0x4C3173B: operator delete[](void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==22059== by 0x10ABA4: sapph_dijkstra::Array<sapph_dijkstra::Node>::~Array() (in /home/sapphie/dijkstra/test)
==22059== by 0x1091CF: sapph_dijkstra::MinHeap::~MinHeap() (minheap.h:40)
==22059== by 0x109021: main (test.c:20)
(我已经删除了一些相同的错误消息)
在我完成的打印中,我已经发现我分配的地址是在0x5b21320
,所以valgrind显示的地址确实是8字节之前。但我不明白为什么。我似乎没有访问它。
有什么小事我不见了吗?
我意识到这对于我可以使用标准向量的问题来说可能是一个过于复杂的解决方案,我可能会改变它。但我现在很好奇。
如果未使用delete[]
初始化内存,则无法使用new[]
删除。这是一种情况(非常非常非常少的情况)直接调用对象的析构函数是正确的:
template <class T>
Array<T>::~Array()
{
for(size_t i = 0; i < m_size; i++) {
m_data[i].~T();
}
allocator.deallocate(m_data);
}
您可能应该进行一些其他更改:
Array
对象的成员,而不是其构造函数中的本地对象。虽然大多数分配器都不是有状态的,但有些是,并且必须仅在构造函数内创建分配器的本地副本意味着分配器将失去其状态。delete[]
销毁对象的顺序来决定是否要销毁对象。在您的情况下,您需要通过更改析构函数中for循环的顺序来指定自己。这就是这样:
template <class T>
Array<T>::~Array()
{
for(size_t i = 0; i < m_size; i++) {
m_data[m_size - i - 1].~T();
}
allocator.deallocate(m_data);
}
int
来处理涉及此数组大小的任何内容。有一种观点认为int64_t
优于size_t
(尽管这会打破STL的行为方式,并且需要谨慎做出决定),但你绝对不想限制使用签名32比特数或更小(int
在大多数环境中)。delete
:delete
期望传递给它的指针是它自己的离散分配对象。使用placement-new
创建的对象不是以这种方式分配的。考虑以下:
struct point {
int32_t x, y;
};
int main() {
char memory[1024];
size_t size = 128;
point * arr = reinterpret_cast<point*>(memory);
for(size_t i = 0; i < size; i++) {
new(memory + i) point();
}
for(size_t i = 0; i < size; i++) {
arr[i].x = 5;
arr[i].y = 10;
}
for(size_t i = 0; i < size; i++) {
//undefined behavior, we're deleting objects that weren't allocated on the heap!
delete (arr + (size - i - 1));
}
}
在这个例子中,根本没有动态的内存分配;使用的所有内存都在堆栈上分配。如果我们在这种情况下使用delete
,我们将删除指向堆栈内存的指针,并且可能发生任何数量的(未定义的)事情,很可能包括崩溃我们的程序。调用析构函数允许对象清理其组件对象,而无需执行(在这种情况下,不必要的)内存释放。
for(size_t i = 0; i < size; i++) {
//Correct behavior, doesn't delete stack memory
arr[size - i - 1].~point();
}
即使在堆上分配了这个内存的情况下,我们仍然会删除未被代码明确分配的指针。这是被语言禁止的,并且很容易理解为什么:原始内存的单个释放会擦除整个分配的内存块,不需要释放单个块。
struct point {
int32_t x, y;
};
int main() {
char * memory = new char[1024];
size_t size = 128;
point * arr = reinterpret_cast<point*>(memory);
for(size_t i = 0; i < size; i++) {
new(memory + i) point();
}
for(size_t i = 0; i < size; i++) {
arr[i].x = 5;
arr[i].y = 10;
}
for(size_t i = 0; i < size; i++) {
//Still UB: only the first object is a discrete allocation, the rest are part of
//that original allocation. This attempts to deallocate the same chunk of memory 128 times
//delete (arr + (size - i - 1));
//This is correct
arr[size - i - 1].~point();
}
//We do need to deallocate the original memory allocated, but we only do this once.
delete[] memory;
}
你正在混合new
和delete[]
这是未定义的行为。 std::allocator::allocate
使用operator new
,而不是operator new[]
,所以你必须在上面打电话给delete
。
由于您要混合分配存储的方式,因此需要跟踪分配存储的方式,以便正确地释放它。
你可以通过只使用std::vector
作为类的数据成员来避免所有这些。那么你的构造者就会变成
template <class T>
Array<T>::Array(size_t size): m_data(size) {}
template <class T>
Array<T>::Array(const std::vector<T> &vec): m_data(vec) {}
并且您可以获得无需存储大小成员的好处,因为向量可以为您执行此操作。