在C ++中分配可变大小缓冲区的“正确”方法是什么?

时间:2011-09-14 19:42:03

标签: c++ struct size new-operator

这与this question非常相似,但答案并没有真正回答这个问题,所以我想我会再问:

有时我会与返回可变长度结构的函数进行交互;例如,Windows中的FSCTL_GET_RETRIEVAL_POINTERS返回可变大小的RETRIEVAL_POINTERS_BUFFER结构。

在C ++中不鼓励使用malloc / free,所以我想知道:
在标准C ++中分配可变长度缓冲区的“正确”方法是什么(即没有Boost等)?

vector<char>类型不安全(如果我理解的话,不保证对齐),new不适用于自定义大小的分配,我想不出一个好的替代品。有什么想法吗?

6 个答案:

答案 0 :(得分:5)

我会使用std::vector<char> buffer(n)。在C ++中确实没有可变大小的结构,所以你必须伪造它;把窗户安全扔出窗外。

答案 1 :(得分:2)

我认为您无法使用std::vector<char>

的原因
{
   std::vector<char> raii(memory_size); 
   char* memory = &raii[0];

  //Now use `memory` wherever you want
  //Maybe, you want to use placement new as:

   A *pA = new (memory) A(/*...*/); //assume memory_size >= sizeof(A);
   pA->fun();
   pA->~A(); //call the destructor, once done!

}//<--- just remember, memory is deallocated here, automatically!

好的,我理解你的对齐问题。这并不复杂。你可以这样做:

A *pA = new (&memory[i]) A();
//choose `i` such that `&memory[i]` is multiple of four, or whatever alignment requires
//read the comments..

答案 2 :(得分:2)

如果您喜欢malloc() / free(),可以使用

RETRIEVAL_POINTERS_BUFFER* ptr=new char [...appropriate size...];

... do stuff ...

delete[] ptr;

关于对齐的标准的引用(expr.new/10):

  

对于char和unsigned char的数组,它们之间存在差异   new-expression的结果和返回的地址   分配函数应是最严格的整数倍   任何对象大小的基本对齐要求(3.11)   不大于正在创建的数组的大小。 [ 注意:   因为假定分配函数返回指向存储的指针   适用于任何类型的基础对象   对齐,这种对数组分配开销的约束允许   分配字符数组的常用习惯用法   稍后将放置其他类型。 - 结束说明]

答案 3 :(得分:0)

您可以考虑使用内存池,并且在RETRIEVAL_POINTERS_BUFFER结构的特定情况下,根据其定义分配池内存量:

sizeof(DWORD) + sizeof(LARGE_INTEGER)

ExtentCount * sizeof(Extents)

(我相信你比我更熟悉这个数据结构 - 上面主要是针对你问题的未来读者)。

内存池归结为“分配一堆内存,然后使用您自己的快速分配器将这些内存分成小块”。 您可以build your own memory pool,但值得查看Boosts memory pool,这是一个纯标头(没有DLL!)库。请注意,我没有使用Boost内存池库,但你确实询问过Boost,所以我想我会提到它。

答案 4 :(得分:0)

std::vector<char>很好。通常,您可以使用零大小参数调用低级c函数,因此您知道需要多少。然后解决对齐问题:只需分配超出需要的数量,并偏移开始指针:

假设您希望缓冲区与4个字节对齐,分配needed size + 4并添加4 - ((&my_vect[0] - reinterpret_cast<char*>(0)) & 0x3)

然后使用请求的大小和偏移指针调用c函数。

答案 5 :(得分:0)

好的,让我们从头开始吧。返回可变长度缓冲区的理想方法是:

MyStruct my_func(int a) { MyStruct s; /* magic here */ return s; }

不幸的是,这不起作用,因为sizeof(MyStruct)是在编译时计算的。任何变长都不适合在编译时计算大小的缓冲区。值得注意的是,这种情况发生在c ++支持的每个变量或类型上,因为它们都支持sizeof。 C ++只有一件事可以处理缓冲区的运行时大小:

MyStruct *ptr = new MyStruct[count];

所以解决这个问题的任何事情都必然会使用new的数组版本。这包括std :: vector和之前提出的其他解决方案。请注意,像char数组的新位置这样的技巧与sizeof完全相同。可变长度缓冲区只需要堆和数组。如果你想留在c ++中,那么就无法绕过这个限制。此外,它需要不止一个对象!这个很重要。你不能用c ++制作变长对象。这是不可能的。

c ++提供的最接近可变长度对象的是“从类型跳转到类型”。每个对象不需要是相同的类型,您可以在运行时操作不同类型的对象。但是每个部分和每个完整对象仍然支持sizeof,它们的大小是在编译时确定的。程序员唯一要做的就是选择你使用的类型。

那么我们解决这个问题的方法是什么?你如何创建可变长度的对象? std :: string提供了答案。它需要在内部有多个字符,并使用数组替代方法进行堆分配。但这都是由stdlib处理的,程序员不需要关心。然后你将有一个操纵那些std :: strings的类。 std :: string可以做到,因为它实际上是2个独立的内存区域。 sizeof(std :: string)确实返回一个内存块,其大小可以在编译时计算。但实际的可变长度数据位于由new的数组版本分配的单独内存块中。

new的数组版本对它自己有一些限制。 sizeof(a[0])==sizeof(a[1])等。首先分配一个数组,然后对几个不同类型的对象进行新的放置将绕过这个限制。