【问题标题】:How do I over-allocate memory using new to allocate variables within a struct?如何使用 new 在结构中分配变量来过度分配内存?
【发布时间】:2010-12-27 11:09:31
【问题描述】:

所以我有几个结构......

struct myBaseStruct
{
};

struct myDerivedStruct : public myBaseStruct
{
    int a, b, c, d;
    unsigned char* ident;
};

myDerivedStruct* pNewStruct;

...我想动态分配足够的空间,以便我可以在某些数据中“memcpy”,包括以零结尾的字符串。基本结构的大小显然是“1”(我假设它不能为零),派生的大小是 20,这似乎是有道理的 (5 x 4)。

所以,我有一个大小为 29 的数据缓冲区,前 16 个字节是整数,其余 13 个字节是字符串。

如何为 pNewStruct 分配足够的内存,以便有足够的空间用于字符串?理想情况下,我只想去:

  • 在 pNewStruct 分配 29 个字节;
  • memcpy 从缓冲区到 pNewStruct;

谢谢,

【问题讨论】:

  • 好的,感谢您的反馈。我对 malloc 的想法很感兴趣,但我不熟悉范式,这表明这是一个坏主意?无论如何都叫我 n00b - 或者,对详细说明这一点的文档的一些参考会更有成效。
  • 如果你想要一个连续的变长结构,你必须在最后放一个数组,而不是指针。
  • 谢谢。是的,根据 Remus 的回答,这就是我所做的,结合使用 malloc,我已经让它工作了。为了澄清,派生结构的原因是我可以保留一个非特定类型的指针,然后将 memcpy 保存在一个循环中。该过程实际上执行以下操作: - 读取块类型字节。 - 读取块的大小。 - 将正确类型的成员变量分配给扩展大小。 - 使用本地基类指针指向成员变量。 - Memcpy 的指针大小。基于不知道接下来是什么块,这在循环上运行。
  • 非常重要,除非您可以保证编译器将成员放置在目标结构和目标结构之间完全相同的位置,否则不要使用 memcpy。允许编译器在不通知用户的情况下在成员之间添加填充。最安全和最便携的方法是使用成员方式复制,通过复制构造函数或函数。
  • 如果两个 POD 类以相同的顺序具有相同的数据成员,那么它们是“布局兼容的”(9.2/14)。虽然我找不到这样说的规范,但我很确定这意味着编译器必须将成员放在完全相同的位置,因此您可以从一个到另一个进行 memcpy。如果在这种情况下不是基类,那么具有问题中给出的成员的两个不同的类/结构将符合条件。问题是基类占用一些字节是合法的。

标签: c++ dynamic structure new-operator allocation


【解决方案1】:

您可以通过以下方式动态分配空间:

myDerivedStruct* pNewStruct = reinterpret_cast<myDerivedStruct*>(new char[size]);

然而

您确定要这样做吗?

另外,请注意,如果您打算使用 ident 作为指向字符串开头的指针,那将是不正确的。您实际上需要 &ident,因为 ident 变量本身就是未使用空间的开头,因此将 at 的空间解释为指针很可能是没有意义的。因此,如果 ident 是 unsigned charchar 而不是 unsigned char* 会更有意义。

[再次编辑] 我想强调的是,你所做的确实是一个非常糟糕的主意。

【讨论】:

  • 天哪,请停止痛苦!
  • 因为: - 您必须始终自己为这些结构分配所有内存,否则最终会损坏内存。 - 当你 memcpy 进入结构时,结构字段的值将取决于平台(最明显的是它们将取决于字节顺序和 int 的大小)。 - sizeof(myDerivedStruct) 会产生误导 - 你的代码阅读起来会非常混乱,而且将来维护它的任何人都可能不明白你在做什么,即使你明白了。
【解决方案2】:

在这种情况下,混合使用 memcpynew 似乎是个糟糕的主意。考虑改用malloc

【讨论】:

  • 我认为这可能是最好的主意。 “哦。”
【解决方案3】:
char* buffer = [some data here];
myDerivedStruct* pNewStruct = new myDerivedStruct();
memcpy(buffer,pNewStruct,4*sizeof(int));
pNewStruct->ident = new char[ strlen(buffer+(4*sizeof int)) ];
strcpy(pNewStruct->ident,buffer+(4*sizeof int));

类似的东西。

【讨论】:

  • 我要声明,以这种方式做事是个坏主意(tm) 如果你使用的是 c++,你应该使用 std::string
  • STL 不被认为是我正在工作的平台的最佳选择,不幸的是:/
【解决方案4】:

在当前的 C++ 标准中,myDerivedStruct 是非 POD,因为它有一个基类。 memcpying 任何东西的结果是未定义的。

我听说 C++0x 会放宽规则,所以 POD 的类比 C++98 中的多,但我没有研究过。另外,我怀疑很多编译器会以与 POD 不兼容的方式布置您的类。我希望你只会遇到一些没有做空基类优化的问题。但它就在那里。

如果是 POD,或者如果您愿意冒险实施,那么您可以使用 malloc(sizeof(myStruct)+13)new char[sizeof(myStruct)+13] 来分配足够的空间,与 C 中的基本相同。大概动机是为了避免将std::string 成员放入您的类中的内存和时间开销,但代价是必须为手动内存管理编写代码。

【讨论】:

  • 在我的脑海中,我不认为这是真的。它没有虚函数或自定义默认构造函数,所以我认为它是 POD。
  • 在我的脑海中,9/4:“一个 POD 结构是一个聚合类”,以及 8.5.1/1:“一个聚合是一个数组或类......没有基类”。好吧,我撒谎说它不在我的脑海中;-)
  • @Steve:那为什么抱怨非 POD 类的某些操作的编译器 (GCC) 不抱怨简单的派生结构?
  • @Steve:特别是我可以声明它们的数组来覆盖 mmap() 的数据而不触发默认构造函数。
  • 我对那个特定的 GCC 警告一无所知,所以我无法解释它,抱歉。既然您说警告是关于默认构造函数的,我推测它可能与 myDerivedStruct 的构造函数在 GCC 上仍然没有任何作用的事实有关。如果您将 offsetof 与 myDerivedStruct 一起使用,那么您确实会收到来自 GCC 的警告,但无论如何它都会给出“正确”的答案。
【解决方案5】:

您回到 C 或放弃这些想法,并按预期实际使用 C++。

  • 使用构造函数分配内存,使用析构函数删除。
  • 不要让其他代码写入您的内存空间,创建一个确保分配内存的函数。
  • 使用 std:string 或 std::vector 来保存数据,而不是滚动您自己的容器类。

理想情况下你应该说:

myDerivedClass* foo = new myDerivedClass(a, b, c, d, ident);

【讨论】:

  • 我可以这样做,但它会大大降低效率。此外,传入的缓冲区很好地打包了数据 - 将其全部拆分以重新组装它有什么意义?我要做的就是从缓冲区复制数据并使用我的结构给它一些上下文。
  • @acron:问问自己效率是否如此重要,以至于您负担不起编写可靠、可读的 C++ 代码的费用。如果您确定确实如此,请按照建议考虑使用 C。
【解决方案6】:

您可以使用malloc 分配您想要的任何大小:

myDerivedStruct* pNewStruct = (myDerivedStruct*) malloc(
      sizeof(myDerivedStruct) + sizeof_extra data);

不过,您有一个不同的问题,因为 myDerivedStruct::ident 是一个非常模棱两可的构造。它是一个指向 char(数组)的指针,那么结构以 char 数组开始的 address 结束? ident 可以指向任何地方,并且非常模糊谁拥有 ident 指向的数组。在我看来,您希望结构以实际的 char 数组本身结尾,并且结构拥有额外的数组。这样的结构通常有一个 size 成员来跟踪它们自己的大小,以便 API 函数可以正确地管理和复制它们,并且按照惯例,额外的数据在结构结束后开始。或者它们以 0 长度数组 char ident[0] 结尾,尽管这会给某些编译器带来问题。由于许多原因,此类结构中没有继承的空间:

struct myStruct 
{
size_t size;    
int a, b, c, d;    
char ident[0];
};

【讨论】:

  • 大小为 0 的数组声明在 C++(以及 C,顺便说一句)中格式错误。 所有编译器都有问题,而不仅仅是一些。
  • 是的,所以基于每个字符串至少有 1 个字符的假设,我使用了 'char ident;'。这个方法奏效了。
  • @AndreyT:GNU C 允许将长度为 0 的数组作为扩展。不确定这是否意味着它“有问题”。它在迂腐模式下禁止它。 C99 允许在结构的末尾使用灵活长度的数组,char ident[],这是为此目的而设计的。
  • 旧的编译器允许 'struct hack' 使用零长度数组。旧系统头文件中充满了以 0 长度数组结尾的结构。较新的编译器明确禁止它,并且一些接受无大小数组的新语法。我猜“旧”和“新”的含义是相对的……这也在讨论stackoverflow.com/questions/627364/…
  • 因为他在他的结构中使用 0 终止字符串,所以他应该将数组大小设置为 1。然后他不需要在 strlen() 调用之后的 +1。无论如何,这根本不是 C++ 解决方案。
【解决方案7】:

缓冲区大小在编译时是否已知?在这种情况下,静态分配的数组将是一个更简单的解决方案。否则,请参阅上面的 Remus Rusanu 的回答。这就是 win32 api 管理可变大小结构的方式。

struct myDerivedStruct : public myBaseStruct
{
    int a, b, c, d;
    unsigned char ident[BUFFER_SIZE];
};

【讨论】:

    【解决方案8】:

    首先,我不明白拥有myBaseStruct 基数有什么意义。你没有提供任何解释。

    其次,您在原始帖子中声明的内容不适用于您描述的数据布局。对于您在 OP 中描述的内容,您需要结构的最后一个成员是数组,而不是指针

    struct myDerivedStruct : public myBaseStruct {
        int a, b, c, d;
        unsigned char ident[1];
    };
    

    数组大小无关紧要,但它应该大于 0。大小为 0 的数组在 C++ 中是明确非法的。

    第三,如果你出于某种原因想特别使用new,你必须分配一个char对象的缓冲区,然后将结果指针转换为你的指针类型

    char *raw_buffer = new char[29];
    myDerivedStruct* pNewStruct = reinterpret_cast<myDerivedStruct*>(raw_buffer);
    

    然后你可以做你的memcpy,假设大小是正确的。

    【讨论】:

      【解决方案9】:

      您可以为任何类实例过度分配,但这意味着一定数量的管理开销。唯一有效的方法是使用自定义内存分配调用。无需更改类定义,您就可以这样做。

      void* pMem = ::operator new(sizeof(myDerivedStruct) + n);
      myDerivedStruct* pObject = new (pMem) myDerivedStruct;
      

      假设您没有在层次结构中重载operator delete,那么delete pObject 将是销毁pObject 并释放分配的内存的正确方法。当然,如果您在多余的内存区域中分配任何对象,那么您必须在释放内存之前正确释放它们。

      然后,您可以在以下地址访问n 字节的原始内存:void* p = pObject + 1。您可以随意memcpy 数据进出该区域。您可以分配给对象本身,而不需要memcpy 其数据。

      您还可以在类本身中提供一个自定义内存分配器,它需要一个额外的 size_t 来描述要分配的多余内存量,使您能够在单个 new 表达式中进行分配,但这需要更多的开销类设计。

      myDerivedStruct* pObject = new (n) myDerivedStruct;
      

      struct myDerivedStruct
      {
          // ...
          void* operator new(std::size_t objsize, std::size_t excess storage);
      
          // other operator new and delete overrides to make sure that you have no memory leaks
      };
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-10-05
        • 2018-12-29
        • 2010-11-20
        • 2015-06-27
        • 2013-05-09
        • 2021-05-20
        • 1970-01-01
        相关资源
        最近更新 更多