1

我需要找到一种方法来故意泄漏(拥有) a 的内部指针,std::vector以便它的生命周期超过原始容器的生命周期,以便以后可以手动删除它。

为什么?我正在使用需要在短时间内发送大量数据包的 C ENet 库开发网络应用程序。

我通过将数据写入std::vector<unsigned char>.

然后为了创建一个“数据包”,我使用了这个enet_packet_create函数,它接受一个指向要发送的字节数组的指针及其大小。在正常操作模式下,该函数只是在堆上动态复制给定数组,但还有一个“不分配”选项,它只接受指针和大小,使用回调函数将删除留给创建者,这正是我正在努力实现 - 数据已经存在于可以使用的向量中,因此无需再次复制它,因为它可能很昂贵。

4

4 回答 4

2

这种方法是不可能的,即使vector<T>提供了一个接口让你带着它的记忆潜逃。让我们来看看为什么。

您的问题存在是因为您要释放内存的站点没有提供任意数据。它只给出了一个指向要释放的内存的指针。如果不是这种情况,那么您只需将指向vector<T>自身的指针传递到该位置,或者以其他方式走私vector<T>对象本身。

为了潜逃vector<T>' 的记忆并成功释放它,你必须遵守vector<T>' 的规则。意思是:

  1. 您必须尊重大小/容量的区别。并非所有分配给 a 的内存vector<T>实际上都包含 live Ts。因此,您必须知道该内存中有多少 live T,以便您可以正确调用它们的析构函数(我们稍后会解决这个问题)。

    现在可以肯定,对于 的非常具体的情况unsigned char,调用析构函数是无关紧要的,因为它们是微不足道的。但是vector<T>的接口需要统一;如果你可以带着 avector<unsigned char>的记忆潜逃,那么你必须能够以vector<T>同样的方式带着 any 潜逃。因此,任何潜逃接口都必须不仅提供指向数据的指针,还必须提供大小和容量,以便您可以正确销毁容器的成员。

  2. 你必须尊重Allocator. 请记住:模板是vector<T, Allocator>,在哪里Allocator进行内存分配/释放,以及Tvector. 并且由于您可以提供特定Allocator实例的特定对象,因此任何潜逃接口都必须存储该特定Allocator对象(或对其进行复制/移动),以便可以释放分配。

    同样,vector<unsigned char>不关心的具体情况,因为默认分配器std::allocator仅用于::operator new/delete分配/释放内存,并直接调用placement-new/destructor 来创建/销毁Ts。但同样,一般潜逃接口必须与 anyT和 any一起使用Allocator。所以它必须考虑所有这些。

这意味着,归根结底,当您带着 avector的内存潜逃时,该接口必须提供一个对象,该对象存储指向分配的指针、该分配中活动元素的数量、该分配的大小(因为Allocator接口要求),以及用于销毁/释放对象的实例Allocator(或复制/移动)。

简而言之,带着 avector<T, Allocator>的记忆潜逃意味着创造一个vector<T, Allocator>.

如上所述,您不能这样做。你已经到了一个本质上矛盾的境地。

有两种解决方案:

  1. 更改您的代码,以便您可以将 a 偷运vector<T>到 . 这可以通过从指针到数据的一些全局/类范围/等映射来完成vector<unsigned char>*。或者其他一些机制。您必须弄清楚它,因为它取决于您尚未提出的系统的特定方面(这是XY 问题的定义)。

  2. 停止使用vector<unsigned char>。相反,只需堆分配一个数组unsigned char,您可以很好地销毁它。

于 2019-10-16T14:47:03.787 回答
2

你不需要泄漏任何东西。只需使用结构体的userData字段ENetPacket来存储要删除的std::vector,然后在回调中删除即可:

void myCallback(ENetPacket *pkt) {
    std::vector<uint8_t> *data=(std::vector<uint8_t> *)pkt->userData;
    delete data;
}

void sendData() {
    //Create the vector in heap, so it is not destroyed after returning from this function, effectively extending its life until the callback is called.
    std::vector<uint8_t> *data=new std::vector<uint8_t>;
    //Fill data here
    ENetPacket *pkt=enet_packet_create(data.data(), data.size(), ENET_PACKET_FLAG_NO_ALLOCATE);
    pkt->userData=(void*)data;
    pkt->freeCallback=myCallback;

}

userDatavoid 指针是保存不透明用户数据并在回调中使用它的常用策略,因此库的用户可以检索调用回调的上下文。

它可以是任何东西(void*),来自状态持有者结构以便在回调之后执行复杂的逻辑,或者只是需要像您的情况一样释放的数据指针。


从您的评论中,您说您不想动态分配vector.

请记住,向量内的任何数据都已被动态分配(除非使用了自定义分配器)并且ENetPacket结构也已被动态分配(传递的标志只是表示不分配data,而不是结构)


最后,如果您知道(或可以预先计算)数据的大小,另一种方法是创建传递 NULL 数据指针的数据包。

该函数enet_packet_create将创建数据缓冲区,您可以直接将数据填充到数据包缓冲区中,而不需要不同的缓冲区然后将其复制到数据包中。

于 2019-10-16T14:39:57.620 回答
1

我需要找到一种方法来故意泄漏 a 的内部指针std::vector

泄漏内部缓冲区的唯一方法std::vector是泄漏向量本身。例子:

std::vector<T>* ptr = new std::vector<T>;
ptr = nullptr; // memory leaked succesfully

但一般来说,泄漏内存并不是一个好主意。

我并不是真的要制造内存泄漏,内存需要被释放。

在这种情况下,唯一的解决方案是确保缓冲区的生命周期std::vector长于缓冲区的使用时间。向量总是在销毁时释放它拥有的缓冲区,并且没有办法从中提取所有权,除非进入另一个向量。

实现这一目标的一种方法是:

// stored somewhere with guaranteed longer lifetime than any packet
std::unordered_map<unsigned char*, std::vector<unsigned char>> storage;

void foo()
{
    std::vector<unsigned char> vec;
    // fill vec here
    unsigned char* ptr = vec.data();
    storage[ptr] = std::move(vec);
    auto destroy_callback = [](unsigned char* ptr) {
        storage.erase(ptr);
    }
    // pass ptr and destroy_callback into some async API
}

您可以使用池分配器来避免每个数据包的冗余分配。

示例改编自这个答案(现在这个问题已经从泄漏转移到转移所有权,这接近重复)。在对同一问题的另一个答案中还有一个替代建议,它使用“窃取”所有权的自定义分配器

于 2019-10-16T14:08:04.967 回答
0

以下不是答案!这是说服您重新考虑您的方法的另一种尝试,但是评论太长了。(话虽如此,我必须说我喜欢这种类型的黑客只是为了好玩,但当他们进入生产代码时我更讨厌它们。)

从 OP 来看,使用“no alloc”选项的动机是避免内存分配和复制内部字节enet_packet_create。这给我带来了一个问题,为什么要使用vector?

如果您创建 avector但不从一开始就固定其容量(使用reserveor resize),而是在添加元素时让它增加,那么每次增加容量时vector都会分配内存并复制字节,这正是您想要的避免。

也许你从一开始就知道最终的尺寸是多少vector。在这种情况下,您可以通过从一开始就保留该大小来避免所有副本和内存分配(但只有一个)。在这种情况下,为什么不像昆汀建议的那样简单地使用new[]and呢?delete[]您不必窃取内存,因为它将是您的。更好的是,您可以创建一个unique_ptr<unsigned char[]>(consider make_unique<unsigned char[]>),在调用“窃取”内存release之前使用它的方法,然后再调用以释放内存。enet_packet_createdelete[]

于 2019-10-16T16:59:25.787 回答