【问题标题】:What is the "proper" way to allocate variable-sized buffers in C++?在 C++ 中分配可变大小缓冲区的“正确”方法是什么?
【发布时间】:2011-09-14 19:42:02
【问题描述】:

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

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

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

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

【问题讨论】:

  • 我不明白你的结构中有一个std::vector<WHATEVER> 的数据成员有什么问题。
  • @Oli:在 my 结构中?它已经由 Windows 定义,而不是由我定义...
  • "vector<char> 是类型不安全的" ...怎么会这样?
  • @Mehrdad:哦,我明白了。我以为你只是用 Windows 的例子来说明如何在 C 中完成。
  • @AJG85:它没有告诉我它拥有RETRIEVAL_POINTERS_BUFFER 结构这一事实,因此它显然是类型不安全的。

标签: c++ struct size new-operator


【解决方案1】:

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

【讨论】:

  • 但是甚至不能保证是例如正确对齐,是吗?它似乎比malloc hackier 多...
  • @Mehrdad,我不知道vector 是否保证对齐其内部缓冲区,但实际上我相信它在常见的编译器上是这样。如果需要保证,您始终可以使用自定义分配器。
  • @Mehrdad 无论如何,您都将负责对齐。不过,C++11 有更多工具来帮助执行它。
  • @Mehrdad:它对齐正确。动态分配保证适合任何类型。
  • @Mehrdad,为什么你认为new 提供的保证比malloc 少?
【解决方案2】:

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

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

... do stuff ...

delete[] ptr;

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

对于 char 和 unsigned char 的数组, 新表达式的结果和返回的地址 分配函数应该是最严格的整数倍 任何大小的对象类型的基本对齐要求(3.11) 不大于正在创建的数组的大小。 [ 笔记: 因为假设分配函数返回指向存储的指针 与任何类型的对象适当对齐 对齐,这种对数组分配开销的约束允许 将字符数组分配到哪个对象中的常见习语 稍后将放置其他类型。 ——尾注]

【讨论】:

  • 噢……哇,这真的很酷。因此,new 的结果总是针对原始类型正确对齐,无论您将其分配为什么类型?
  • 仅适用于new char[n](或unsigned char)。
  • @Mehrdad:不只是原始类型,所有类型。
  • @Mehrdad:这使得 all 类型的对齐始终正确对齐。一个类的对齐方式与其成员的对齐方式相同,对齐方式最严格,最终这是一个原始类型。
  • @GMan,Mooing Duck:它不能是所有类型,因为编译器扩展可以进行 64 之类的对齐,这是它无法处理的。但是,是的,如果你不理会它,我猜它会起作用。
【解决方案3】:

我看不出你不能使用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..

【讨论】:

  • 对齐保证是什么?地址是否保证对齐正确?
  • @Mehrdad:这是安置new 的工作,以及你想如何使用它。
  • @Mehdrad placement new 不会在向量为您分配的先前分配的内存中分配它places 东西。
  • @Nazaz:struct A {int data[3];}; 必须与sizeof(int) 的倍数对齐,而不是sizeof(A)。如果您分配 sizeof(A) 字节,但它不是从(对于 32 位机器)字节四的倍数开始的,那么一切都会消失。 (我认为是UB)
  • @Mehrdad:但是如果你给它一个已经对齐的地址,那就没有问题了,这里就是这种情况。
【解决方案4】:

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

sizeof(DWORD) + sizeof(LARGE_INTEGER)

ExtentCount * sizeof(Extents)

(我相信你比我更熟悉这个数据结构——以上主要是为你问题的未来读者准备的)。

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

【讨论】:

    【解决方案5】:

    std::vector&lt;char&gt; 很好。通常,您可以使用大小为零的参数调用低级 c 函数,这样您就知道需要多少。然后你解决你的对齐问题:只需分配比你需要的更多,并偏移起始指针:

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

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

    【讨论】:

      【解决方案6】:

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

      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]) 等。首先分配一个数组,然后为几个不同类型的对象做新的放置将绕过这个限制。

      【讨论】:

        猜你喜欢
        • 2020-02-02
        • 2020-09-05
        • 2011-04-24
        • 2021-06-06
        • 2012-08-10
        • 1970-01-01
        • 1970-01-01
        • 2011-07-19
        相关资源
        最近更新 更多