代码之家  ›  专栏  ›  技术社区  ›  James Sutherland

新阵列的放置能否以便携方式使用?

  •  36
  • James Sutherland  · 技术社区  · 16 年前

    从new[]返回的指针似乎并不总是与传入的地址相同(5.3.4,标准中的注释12似乎证实了这一点),但如果是这样,我不知道如何为数组分配缓冲区。

    下面的示例显示了该问题。使用Visual Studio编译,此示例会导致内存损坏:

    #include <new>
    #include <stdio.h>
    
    class A
    {
        public:
    
        A() : data(0) {}
        virtual ~A() {}
        int data;
    };
    
    int main()
    {
        const int NUMELEMENTS=20;
    
        char *pBuffer = new char[NUMELEMENTS*sizeof(A)];
        A *pA = new(pBuffer) A[NUMELEMENTS];
    
        // With VC++, pA will be four bytes higher than pBuffer
        printf("Buffer address: %x, Array address: %x\n", pBuffer, pA);
    
        // Debug runtime will assert here due to heap corruption
        delete[] pBuffer;
    
        return 0;
    }
    

    查看内存,编译器似乎正在使用缓冲区的前四个字节来存储缓冲区中项目数的计数。这意味着,因为缓冲区是 sizeof(A)*NUMELEMENTS 大,数组中的最后一个元素写入未分配的堆。

    所以问题是,为了安全地使用placement new[],您能否找出实现需要多少额外开销?理想情况下,我需要一种可以在不同编译器之间移植的技术。请注意,至少在VC的情况下,不同类的开销似乎有所不同。例如,如果在示例中删除虚拟析构函数,则从new[]返回的地址与传入的地址相同。

    7 回复  |  直到 12 年前
        1
  •  32
  •   Stack Exchange Supports Israel    9 年前

    就我个人而言,我会选择不在数组中使用placement new,而是在数组中的每个项目上单独使用placement new。例如:

    int main(int argc, char* argv[])
    {
      const int NUMELEMENTS=20;
    
      char *pBuffer = new char[NUMELEMENTS*sizeof(A)];
      A *pA = (A*)pBuffer;
    
      for(int i = 0; i < NUMELEMENTS; ++i)
      {
        pA[i] = new (pA + i) A();
      }
    
      printf("Buffer address: %x, Array address: %x\n", pBuffer, pA);
    
      // dont forget to destroy!
      for(int i = 0; i < NUMELEMENTS; ++i)
      {
        pA[i].~A();
      }    
    
      delete[] pBuffer;
    
      return 0;
    }
    

    笔记 我还没有编译过,但我认为它应该工作(我在一个没有安装C++编译器的机器上)。它仍然表明了这一点:)希望它在某种程度上有所帮助!


    编辑:

        2
  •  5
  •   James Sutherland    16 年前

    5.3.4,第12节讨论了数组分配开销,除非我误读了它,否则它似乎向我建议编译器将其添加到新位置也是有效的:

    也就是说,我认为VC是唯一一个给我带来麻烦的编译器,除了GCC、Codewarrior和ProDG。不过,我得再检查一下。

        3
  •  4
  •   Derek Park    16 年前

    @詹姆斯

    我甚至不太清楚它为什么需要额外的数据,因为您无论如何都不会在数组上调用delete[],所以我不完全明白它为什么需要知道其中有多少项。

    考虑过这一点后,我同意你的看法。没有理由认为placement new需要存储元素的数量,因为没有placement delete。由于没有放置删除,因此没有理由使用新放置来存储元素的数量。

    我还在Mac上用gcc测试了这一点,使用了一个带析构函数的类。在我的系统中,位置是新的 更改指针。这让我想知道这是否是一个VC++问题,以及这是否会违反标准(据我所知,标准并没有专门解决这个问题)。

        4
  •  3
  •   James Sutherland    16 年前

    我甚至不太清楚它为什么需要额外的数据,因为您无论如何都不会在数组上调用delete[],所以我不完全明白它为什么需要知道其中有多少项。

        5
  •  3
  •   Tim Cooper    12 年前

    Placement new本身是可移植的,但是您对它使用指定内存块所做的假设是不可移植的。就像前面所说的,如果你是一个编译器,并且有一块内存,如果你只有一个指针,你怎么知道如何分配一个数组并正确地销毁每个元素?(见操作员删除[]界面)

    编辑:

    实际上有一个placement delete,只有当构造函数在分配placement new[]数组时抛出异常时才会调用它。

    new[]是否真的需要以某种方式跟踪元素的数量取决于标准,这取决于编译器。不幸的是,在这种情况下。

        6
  •  2
  •   Andrew Grant    16 年前

    与使用单个元素计算一个新放置的大小类似,使用这些元素的数组计算数组所需的大小。

    如果您需要其他计算的大小,而元素数量可能未知,则可以使用sizeof(A[1])并乘以所需的元素计数。

    例如

    char *pBuffer = new char[ sizeof(A[NUMELEMENTS]) ];
    A *pA = (A*)pBuffer;
    
    for(int i = 0; i < NUMELEMENTS; ++i)
    {
        pA[i] = new (pA + i) A();
    }
    
        7
  •  1
  •   Yossi Kreinin    16 年前

    我认为gcc和MSVC做了同样的事情,但当然这并不能使它“可移植”。

    typedef A Arr[NUMELEMENTS];

    A*p=新(缓冲区)Arr;

    这应该使用新的标量放置。