来自C++17、STD的多态分配器:向量增长和黑客攻击

2020-06-29 22:21:49

C++17中的多态分配器的概念是对标准库中的标准分配器的增强。

它比常规的分配器更容易使用,并且允许容器具有相同的类型,而拥有不同的分配器,甚至可以在运行时更改分配器。

让我们看看如何使用它和Hack来查看std::Vector容器的增长情况。

简而言之,多态分配器符合标准库中分配器的规则。不过,在其核心部分,它使用内存资源对象来执行内存管理。

多态分配器包含指向内存资源类的指针,这就是它可以使用虚方法分派的原因。您可以在运行时更改内存资源,同时保持分配器的类型不变。这与常规分配器相反,常规分配器使使用不同分配器的两个容器也是不同类型的。

多态分配器的所有类型都位于<;MEMORY_RESOURCE>;头中的单独名称空间std::pmr(pmr代表多态内存资源)中。

本文是我关于C++17库实用程序系列的一部分。以下是文章清单:

pmr::memory_resource-是所有其他实现的抽象基类。它定义了以下纯虚方法:

Pmr::Polyic_allocator-是一个标准分配器的实现,它使用MEMORY_RESOURCE对象来执行内存分配和释放。

具有多态分配器的标准容器的模板专门化,例如std::pmr::Vector、std::pmr::string、std::pmr::map等。每个专门化都定义在与相应容器相同的头文件中。

还值得一提的是,可以链接池资源(包括monottonbuffer_resource)。如果池中没有可用的内存,分配器将从“上游”资源进行分配。

它是一个自由函数,返回指向全局“默认”内存资源的指针。它使用全局NEW和DELETE来管理内存。

它是一个自由函数,返回指向全局“null”内存资源的指针,该资源在每次分配时抛出std::badalc。虽然它听起来没有什么用处,但当您想要保证对象不会在堆上分配任何内存时,它可能会很方便。也不是为了测试。

这是一个线程安全的分配器,可以管理不同大小的池。每个池都是一组分成大小一致的块的块。

这是一种非线程安全、快速、专用的资源,它从预先分配的缓冲区中获取内存,但不会通过释放来释放它。它只会生长。

#include<;iostream>;#include<;memory_resource>;//PMR核心类型#include<;Vector>;//pmr::Vector int main(){char buffer[64]={};//堆栈上的小缓冲区std::ill_n(std::Begin(Buffer),std::size(Buffer)-1,';_';);std::cout<。\n';;std::pmr::monotonic_buffer_resource pool{std::data(Buffer),std::size(Buffer)};std::pmr::Vector<;char>;vec{&;pool};for(char ch=';a';;ch<;=';z';;+ch)ve.Push_back(Ch);std:\n';;}

在上面的示例中,我们使用使用堆栈中的内存块初始化的单调缓冲区资源。通过使用一个简单的char buffer[]数组,我们可以轻松地打印“内存”的内容。向量从池中获取内存(因为它在堆栈上,所以速度非常快),如果没有更多的空间可用,它将向“上游”资源请求内存。该示例显示了需要插入更多元素时的向量重新分配。每一次向量都有更多的空间,所以它最终适合所有的字母。如您所见,单调缓冲区资源不会删除任何内存,它只会增长。

我们还可以在向量上使用Reserve(),这将限制内存分配的数量,但本例的目的是演示容器的扩展。

我提到,如果内存结束,那么分配器将从上游资源获得内存。我们怎样才能观察到它呢?

在我们的示例中,上游内存资源是默认内存资源,因为我们没有更改它。这意味着new()和delete()。但是,我们必须记住,do_allocate()和do_delocate()成员函数也接受对齐参数。

这就是为什么如果我们想要破解并查看内存是否由new()分配的原因,我们必须使用带有对齐支持的C++17的new():

void*lastAllocatedPtr=nullptr;size_t lastSize=0;void*运算符new(std::size_t size,std::ALIGN_VAL_t ALIGN){#如果定义(_Win32)||已定义(__CYGWIN__)AUTO PTR=_ALIGNED_MALLOC(SIZE,static_cast<;STD::SIZE_t>;(ALIGN));#ELSE AUTO PTR=ALIGNED_。#endif if(!ptr)抛出std::ad_alloc{};std::cout<;<;";new:";<;<;size<;&34;,align:";<;<;static_cast<;std::size_t>;(Align)<;<;";,ptr:&。';\n';;lastAllocatedPtr=ptr;lastSize=size;return ptr;}。

在上面的代码部分中,我实现了对齐的new()(您可以在我的另一篇文章:new new()-C++17的运算符的对齐参数new())中阅读关于这个全新特性的更多信息。

您还可以发现两个丑陋的全局变量:)然而,多亏了它们,当我们的记忆消失时,我们可以看到:

std::pmr auto buf_size=32;uint16_t Buffer[buf_size]={};//堆栈上的小缓冲区std::ill_n(std::start(Buffer),std::size(Buffer)-1,0);std::pmr::monotonic_buffer_resource pool{std::data(Buffer),std::size(Buffer)*sizeof(Uint16_T)};std::pmr::monotonic_buffer_resource pool{std::data(Buffer),std::size(Buffer)*sizeof(Uint16_T)};std::pmd。for(int i=1;i<;=20;++i)ve.ush_back(I);for(int i=0;i<;buf_size;++i)std::cout<;<;Buffer[i]<;<;";";;std::cout<;<;std::Endl;auto*bufTemp=(uint16_t。bufTemp[i]<;<;";";;

该程序尝试在一个向量中存储20个数字,但是由于向量会增长,因此我们需要超过预定义的缓冲区(只有32个条目)。这就是为什么分配器在某些时候会变成全局NEW和DELETE。

新建:128,对齐:16,ptr:0x21b3c20 1 1 2 1 2 3 4 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 9 11 12 13 14 15 16 0 1 2 3 4 4 5 6 7 8 9 9 10 11 12 13 14 16 17 18 19 20 00 00.。删除:128,对齐:16,PTR:0x21b3c20。

看起来预定义的缓冲区最多只能存储16个元素,但是当我们插入数字17时,向量必须增长,这就是为什么我们看到新的分配-128字节。第二行显示自定义缓冲区的内容,而第三行显示通过new()分配的内存。

前面的示例起作用了,并向我们展示了一些东西,但是使用new()和delete()并不是您在生产代码中应该做的事情。事实上,内存资源是可扩展的,如果您想要最好的解决方案,您可以滚动您的资源!

以下是您可以看到的学习如何实现它的资源。

通过本文,我想向您展示一些使用PMR的基本示例和多态分配器的概念。如您所见,为向量设置分配器比使用常规分配器简单得多。有一组预定义的分配器可供您使用,实现您的自定义版本相对容易。本文中的代码只显示了一次简单的黑客攻击,以说明内存是从哪里提取的。

您是否使用自定义内存分配器?您使用过C++中的PMR和多态分配器吗?