删除程序的析构函数问题[]

时间:2018-06-26 18:04:50

标签: c++ memory valgrind new-operator delete-operator

对于我正在编写的程序,我编写了一个简单的数组包装器类(其思想是应该是固定大小的。我知道我可以只使用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字节。 但是我不明白为什么。我似乎无法访问。
我缺少什么琐碎的东西吗?
我意识到对于可能只使用标准向量的问题,这可能是一个过于复杂的解决方案,我可能会对此进行更改。但是我现在大多数时候都很好奇。

2 个答案:

答案 0 :(得分:1)

您正在混合hwi_oauth: firewall_names: [secured_area] resource_owners: auth0: type: oauth2 class: 'App\Auth0ResourceOwner' client_id: "%env(AUTH0_CLIENT_ID)%" client_secret: "%env(AUTH0_CLIENT_SECRET)%" base_url: "https://%env(AUTH0_DOMAIN)%" scope: "openid email profile" paths: identifier: sub firstname: given_name middlename: middle_name lastname: family_name email_verified: email_verified dob: birthdate identities: https://www.example.org/identities new,这是未定义的行为。 delete[]使用std::allocator::allocate,而不使用operator new,因此您必须在其上调用operator new[]

由于混合了分配存储的方式,因此需要跟踪分配方式,以便正确地对其进行分配。

尽管只需将delete作为类的数据成员,就可以避免所有这些情况。然后您的构造函数变为

std::vector

您将获得不必再存储size成员的好处,因为vector会为您做到这一点。

答案 1 :(得分:1)

如果未使用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;
}