new char[n]和new (char[n])的区别

23

new char[n]new (char[n])有什么区别吗?

在我的生成的代码中使用了第二种情况,g++(4.8.0)给出以下提示:

ISO C++ does not support variable-length array types [-Wvla]

这让我想到这两个是否相同。

  1. new char[n]的意思是“分配n个类型为char的对象”。
  2. new (char[n])的意思是“分配1个类型为n个字符数组的对象”吗?
  3. 删除第一个很清楚。
  4. 我应该用delete还是delete[]来删除第二个?
  5. 还有其他我需要注意的区别吗?
  6. 当软件的其他部分期望第二种情况时,我可以安全地删除括号并将第二种情况变成第一种吗?

代码由第三方软件生成(并被软件的其他部分使用),所以我不能只是“使用向量(vector)”。

这是最小的例子:

int main (void)
{
    int n(10);
    int *arr = new (int[n]); // removing parentheses fixes warning
    *arr = 0; // no "unused variable" warning
    return 0;
}

2
编译器和版本是什么? - user93353
gcc 4.8.0,添加到问题中 - Adam Trhon
1
@aschepler 我认为可变长度数组的类型是 int*。它们的类型无法是 int(*)[n] - Pubby
2
哦,我明白了。当分配的对象是数组时(即使用noptr-new-declarator语法或new-type-id或type-id表示数组类型),new-expression会产生指向数组初始元素(如果有)的指针。 - aschepler
显示剩余2条评论
3个回答

10

这里的基本问题在于,C++不允许在类型中使用数组边界[n],除非n是一个常量表达式。虽然g++和其他一些编译器有时也会允许,但当你开始混合使用可变长度数组和模板时,无法获得一致的行为。

看起来的例外情况 int* p = new int[n]; 能够工作是因为这里的[n]在语法上是new表达式的一部分,而不是提供给new的类型的一部分,并且new确实“知道如何”创建运行时确定长度的数组。

// can be "constexpr" in C++11:
const int C = 12;

int main() {
    int* p1 = new int[C];
    int* p2 = new (int[C]);
    typedef int arrtype[C];
    int* p3 = new arrtype;

    int n = 10;
    int* p4 = new int[n];
    // int* p5 = new (int[n]);  // Illegal!
    // typedef int arrtype2[n]; // Illegal!
    // int* p6 = new arrtype2;

    delete[] p1;
    delete[] p2;
    delete[] p3;
    delete[] p4;
}
从语义上讲,当使用任何最终的[C]将类型转换为数组类型后,新表达式只关心它是否正在处理数组。所有有关表达式类型的要求,无论是使用new[]delete[]等等,都是说“当分配的类型是数组”而不是“当使用数组新语法时”。因此,在上面的示例中,p1p2p3的初始化是等价的,在所有情况下,delete[]都是正确的释放形式。 p4的初始化是有效的,但p5p6的代码不是正确的C ++代码。当不使用-pedantic时,g++仍然会允许它们存在,并且类比推断,我希望p4p5p6 的初始化也是相等的。@MM的反汇编支持这个结论。
所以,从安全性上讲,从这种表达式中移除“额外”的括号应该是一个安全的改进。正确的删除方式是delete[]类型。

在你的回答中,我看到了类似于“如果n不是常量,则T [n]不表示类型”的内容,这也出现在@Kerrek的回答中。我无法理解为什么会出现这种情况,在C ++中没有这样的规定。new可以接受指定运行时依赖类型说明符。 - Johannes Schaub - litb
是的,new int[n] 这个新操作符表达式就是一个例子。它是一个良好形式的类型说明符(new-type-id)。 - Johannes Schaub - litb
哦,现在我想我明白你的观点了。不过,任何表达式或变量都不可能具有非常数数组维度的类型。如果我想要过于挑剔的话,我可能会建议这是一个标准缺陷,因为它没有像对于声明符或类型标识符那样明确说明 new-type-id 指定的类型是什么。 - aschepler
@dalle:我们已经有了运行时大小的容器类。C++14增加了一些。但它并没有像兼容C99的可变长度数组那样对正在讨论的规则进行任何彻底的改变。 - Ben Voigt
C++14的最终版本未包含运行时大小的数组,所以这个问题被推迟到了C++17 :) - M.M
显示剩余2条评论

6
  • new T[N] 会创建一个由 N 个类型为 T 的元素组成的数组。

  • new (T[N]) 会创建一个类型为 T[N] 的单一对象。

两者效果相同(都会返回指向数组第一个元素的 T * 指针,并需要使用 delete[] 进行删除(参见 5.3.4/5 ),但显然在后一种情况中,T[N] 必须是有效的 类型 ,因此 N 必须是常量表达式,而在前一种情况中,它是数组 new 表达式的动态参数。


[] 的优先级高于 new,所以 new T[N] 不应该被解释为与 new (T[N]) 相同吗? - stardust
“这个制造N个元素的”和“这个制造单个数组的”区别并不存在,或者至少我看不到它。 - Luc Danton
1
@Named: new T[N]形式是一种不同的语法结构,而N是数组新表达式的一部分,而不是类型的一部分。 - Kerrek SB
在语法层面上是不同的,当然。声明类型别名有两种方式,但它们最终也会做相同的事情。我对“新的带有数组类型的表达式”有多么“普通”表示怀疑,因为它不会返回 T(*)[N] - Luc Danton
并不是一个情况提供了类型而另一个情况没有。两种情况都提供了类型。但是一种情况通过类型说明符提供它,而另一种情况则通过新类型说明符提供它。像T [N]这样的文本只能以一种或另一种方式表示指定类型(有些可以表示更少的类型,有些可以表示更多的类型)。 - Johannes Schaub - litb
显示剩余9条评论

3
  new int [n]
  //Allocates memory for `n` x `sizeof(int)` and returns
  //the pointer which points to the beginning of it. 

 +-----+-----+-----+-----+-----+-----+-----+-----+------+------+
 |     |     |     |     |     |     |     |     |      |      |
 |     |     |     |     |     |     |     |     |      |      |
 |     |     |     |     |     |     |     |     |      |      |
 +-----+-----+-----+-----+-----+-----+-----+-----+------+------+


  new (int [n])
  //Allocate a (int[n]), a square which its item is an array
+----------------------------------------------------------------+
|+-----+-----+-----+-----+-----+-----+-----+-----+------+------+ |
||     |     |     |     |     |     |     |     |      |      | |
||     |     |     |     |     |     |     |     |      |      | |
||     |     |     |     |     |     |     |     |      |      | |
|+-----+-----+-----+-----+-----+-----+-----+-----+------+------+ |
+----------------------------------------------------------------+

实际上它们两者是相等的。
这是汇编器生成的代码(只是一个可以忽略的差异):
int n = 10;
int *i = new (int[n]);
int *j = new int[n];

i[1] = 123;
j[1] = 123;

----------------------------------

!    int *i = new (int[n]);
main()+22: mov    0x1c(%esp),%eax
main()+26: sub    $0x1,%eax
main()+29: add    $0x1,%eax
main()+32: shl    $0x2,%eax
main()+35: mov    %eax,(%esp)
main()+38: call   0x401620 <_Znaj> // void* operator new[](unsigned int);
main()+43: mov    %eax,0x18(%esp)
!    int *j = new int[n];
main()+47: mov    0x1c(%esp),%eax
main()+51: shl    $0x2,%eax
main()+54: mov    %eax,(%esp)
main()+57: call   0x401620 <_Znaj> // void* operator new[](unsigned int);
main()+62: mov    %eax,0x14(%esp)
!    
!    i[1] = 123;
main()+66: mov    0x18(%esp),%eax
main()+70: add    $0x4,%eax
main()+73: movl   $0x7b,(%eax)
!    j[1] = 123;
main()+79: mov    0x14(%esp),%eax
main()+83: add    $0x4,%eax
main()+86: movl   $0x7b,(%eax)

您必须使用delete [] ...删除它们的两个元素。


我想指出_Znaj是g++的俚语,表示void* operator new[](unsigned int); - aschepler
-1:仅仅因为两个东西为一个简单的代码片段生成相同(或类似)的汇编代码,并不意味着这些代码是“相等”的,甚至是等价的;汇编是非常低级的,不关心类型,而整个问题都是关于高级类型的,这些类型只存在于C++层面。当涉及到性能问题时,应该仅检查汇编转储,而不是正确性。考虑一下当类型是具有构造函数和析构函数的对象时,它们有多么不同,而不仅仅是像int或char这样的基本类型。 - fluffy
@fluffy: (1) 关于“相等性”的说法是由于其前面的注释没有遵循汇编描述而导致的结果。(2) 当然不是,我同意我们不能从一些关于C++代码的汇编代码中得到结果。但是编译器生成的汇编代码是了解一段C++代码行为的好观察材料。 - masoud
没错,但我的观点是这段代码本来就不等价。它们只是在非常特定的情况下具有相同的行为。 - fluffy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接