【问题标题】:differences between new char[n] and new (char[n])new char[n] 和 new (char[n]) 的区别
【发布时间】:2013-05-14 03:17:24
【问题描述】:

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 个 array of n chars 类型的对象”吗?
  3. 删除第一个很清楚。
  4. 我应该用deletedelete[] 删除第二个吗?
  5. 还有其他我应该注意的区别吗?
  6. 当软件的其他部分期望使用第二种情况时,我可以安全地删除括号并将第二种情况转换为第一种情况吗?

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

这是最小的例子:

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

【问题讨论】:

  • 编译器和版本是什么?
  • gcc 4.8.0,添加到问题中
  • 我很惊讶它允许从int(*)[n] 转换为int*
  • @aschepler 我认为 VLA 的类型是 int*。他们int(*)[n] 不能作为一种类型存在。
  • 哦,我明白了。 “当分配的对象是一个数组(即使用 noptr-new-declarator 语法或 new-type-id 或 type-id 表示数组类型)时,new-expression 产生一个指向初始元素的指针 (如果有的话)的数组。”

标签: c++ memory-management


【解决方案1】:

这里的基本问题是 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 接受表示运行时相关的 type 说明符,在这里。
  • @JohannesSchaub-litb 我很困惑。 [ constant-expression opt ]declaratortype-id 语法的一部分. (dcl.decl/4 noptr-declarator;dcl.name/1 noptr-abstract-declaratornoptr-abstract-pack-declarator)你有没有一个例子,n 不是一个常量表达式,T[n] 是一个格式良好的类型?
  • 是的,新的运算符表达式new int[n]就是这样一个例子。它是一个格式良好的类型说明符(new-type-id)。
  • 哦,现在我想我明白你的意思了。尽管如此,任何表达式或变量都不可能具有非常量数组维度的类型。如果我想过于挑剔,我可能会建议这是一个标准缺陷,它没有像 那样明确说明 new-type-id 指定了什么类型声明符type-id.
  • 等到 C++14 的运行时大小的数组。
【解决方案2】:
  • new T[N] 创建一个 N 类型为 T 的元素的数组。

  • new (T[N]) 生成T[N] 类型的单个对象。

效果是一样的(两个表达式都产生一个T *,它指向数组的第一个元素,需要用delete[]删除(参见5.3.4/5),但显然T[N]在后一种情况下必须是有效的类型,所以N 必须是一个常量表达式,而在前一种情况下它是array-new 表达式的动态参数。

【讨论】:

  • [] 的优先级高于 new,因此不应将 new T[N] 解释为与 new (T[N]) 相同的解释吗?
  • 不存在“这个做N个元素”、“这个做一个数组”的区别,或者至少我看不到。
  • @Named:new T[N] 形式是不同的语法结构,N 是 array-new 表达式的一部分,而不是类型。
  • @LucDanton:区别在于new T[rand()] 有效,但new (T[rand()]) 无效。当N 是一个常量表达式时,T[N] 是一个类型
  • 是的,这在你的段落中,我不反对。是要点。如果两种语法都有效,它们的意思和做同样的事情。完全没有区别。
【解决方案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 [] ...delete 两个人@

【讨论】:

  • 我要指出_Znajvoid* operator new[](unsigned int); 的g++-manglish
  • -1:仅仅因为两个东西为一个平凡的代码框架生成相同(或相似)的汇编代码并不意味着代码是“相等的”甚至等同的;汇编是非常低级的并且不关心类型,而整个问题是关于仅在 C++ 级别的高级类型。只有在涉及性能问题而不是正确性问题时,才应检查装配转储。考虑一下当类型是具有构造函数和析构函数的对象时它们有多么不同,而不仅仅是像 int 或 char 这样的基本类型。
  • @fluffy: (1) 关于 equality 的声明是其先前注释未遵循汇编描述的结果。 (2) 当然不是,我同意我们不能从一些关于 C++ 代码的汇编代码中得到结果。 但是编译器生成的汇编代码是很好的观察来熟悉一段 C++ 代码的行为。
  • 是的,但我的意思是代码首先是不等价的。他们只是碰巧在一组非常特定的情况下具有相同的行为。
猜你喜欢
  • 2011-04-23
  • 2014-07-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-06
  • 1970-01-01
相关资源
最近更新 更多