23

Is there any difference between new char[n] and new (char[n])?

I have the second case in a generated code, g++ (4.8.0) gives me

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

This makes me think if these two are the same or not.

  1. new char[n] means "allocate n objects of type char.
  2. does new (char[n]) mean "allocate 1 object of type array of n chars"?
  3. deleting the first is clear.
  4. should I delete the second with delete or delete[]?
  5. are there any other differences I should be aware of?
  6. may I safely remove the parentheses and turn the second case into the first, when other parts of the software expect the second?

The code is generated by a third party software (and used by other parts of the software), so I cannot just "use vector instead".

This is minimal example:

int main (void)
{
    int n(10);
    int *arr = new (int[n]); // removing parentheses fixes warning
    *arr = 0; // no "unused variable" warning
    return 0;
}
Adam Trhon
  • 2,915
  • 1
  • 20
  • 51

3 Answers3

10

The basic issue here is that C++ does not allow an array bound [n] to be used in a type unless n is a constant expression. g++ and some other compilers will sometimes allow it anyway, but it's impossible to get consistent behavior when you start mixing variable-length-arrays and templates.

The apparent exception int* p = new int[n]; works because here the [n] is syntactically part of the new expression, not part of the type provided to the new, and new does "know how" to create arrays with length determined at runtime.

// 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;
}

Semantically, though, after any final [C] is used to convert a type into an array type, the new expression only cares about whether it's dealing with an array or not. All the requirements about type of the expression, whether to use new[] and delete[], and so on say things like "when the allocated type is an array", not "when the array new syntax is used". So in the example above, the initializations of p1, p2, and p3 are all equivalent, and in all cases delete[] is the correct deallocation form.

The initialization of p4 is valid, but the code for p5 and p6 is not correct C++. g++ would allow them anyway when not using -pedantic, and by analogy I'd expect the initializations for p4, p5, and p6 to also all be equivalent. @MM's disassembly supports that conclusion.

So yes, it should be a safe improvement to remove the "extra" parentheses from this sort of expression. And the correct deletion is the delete[] type.

aschepler
  • 70,891
  • 9
  • 107
  • 161
  • In your answer i see sort of the same "T [n] does not denote a type if n is not constant" which @Kerrek in his answer does too. I cannot see why that should be the case, there is no such rule in C++. new accepts denoted runtime dependent *type* specifiers, here. – Johannes Schaub - litb May 19 '13 at 14:52
  • @JohannesSchaub-litb I'm confused. `[` _constant-expression_ _opt_ `]` is part of the syntax for a _declarator_ or _type-id_. (dcl.decl/4 _noptr-declarator_; dcl.name/1 _noptr-abstract-declarator_ and _noptr-abstract-pack-declarator_) Do you have an example where `n` is not a constant expression and `T[n]` is a well-formed type? – aschepler May 19 '13 at 17:29
  • yes, the new operator expression `new int[n]` is such an example. It is a well formed type specifier (new-type-id). – Johannes Schaub - litb May 19 '13 at 17:35
  • Oh, now I think I see your point. Still, it's impossible for any expression or variable to have a type with non-constant array dimension. And if I wanted to be overly picky, I might suggest it's a Standard defect that it doesn't explicitly say what type is specified by a _new-type-id_ the way it does for a _declarator_ or _type-id_. – aschepler May 19 '13 at 17:42
  • Just wait until the runtime-sized arrays of C++14. – dalle May 19 '13 at 19:18
  • @dalle: We already have runtime-sized container classes. C++14 adds a few more. But it doesn't make any sweeping changes to the rules being discussed here, the way C99-compatible VLAs would. – Ben Voigt May 19 '13 at 19:24
  • The final release of C++14 did not include runtime-sized arrays, so this problem is deferred to C++17 :) – M.M Sep 25 '14 at 01:44
6
  • new T[N] makes an array of N elements of type T.

  • new (T[N]) makes a single object of type T[N].

The effect is the same (and both expressions yield a T * that points to the first element of the array and needs to be deleted with delete[] (cf. 5.3.4/5), but clearly T[N] must be a valid type in the latter case, so N must be a constant expression, while in the former case it is a dynamic argument of the array-new expression.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • `[]` has a higher precedence than `new` so shouldn't `new T[N]` be interpreted the same as `new (T[N])`? – stardust May 19 '13 at 13:08
  • The 'this one makes N elements', 'this one makes a single array' distinction doesn't exist, or at least I can't see it. – Luc Danton May 19 '13 at 13:09
  • 1
    @Named: The `new T[N]` form is a different grammatical construct, and the `N` is part of the array-new expression, not of the type. – Kerrek SB May 19 '13 at 13:09
  • @LucDanton: The distinction is that `new T[rand()]` is valid, but `new (T[rand()])` isn't. And `T[N]` is a *type* when `N` is a constant expression. – Kerrek SB May 19 '13 at 13:10
  • Right, that's in your paragraph and I don't object to that. It's the bullet points. If both syntaxes are valid, they mean and do the same thing. No distinction at all. – Luc Danton May 19 '13 at 13:12
  • @LucDanton: But they're different grammatical things. The first is an array-new expression, and the latter is an ordinary new expression with an array type. I'm not claiming that they mean different things. – Kerrek SB May 19 '13 at 13:13
  • (The standard doesn't actually call it "array-new expression", but it has some grammar details that distinguish the two forms.) – Kerrek SB May 19 '13 at 13:15
  • Different on the grammar level, sure. There are two ways of declaring type aliases as well, but they end up doing the same thing, too. I question how 'ordinary' is a 'new expression with an array type' given that it doesn't return `T(*)[N]`. – Luc Danton May 19 '13 at 13:15
  • @LucDanton: You never get a pointer to the array, but only to its first element, because of that sentence 5 I mentioned, which explicitly says what happens when the type is an array type (including a provision for the *noptr-new-declarator* form). – Kerrek SB May 19 '13 at 13:17
  • I know what happens. And it's the same for both. – Luc Danton May 19 '13 at 13:18
  • Both create an object of type `T [N]`. Your answer seems to state that is not the case. – Johannes Schaub - litb May 19 '13 at 14:43
  • It is not that one case supplies a type and another case does not. Both cases supply types. But one case supplies it by a *type-specifier* and the other case by a *new-type-specifier*. All which a text like `T [N]` can do is to *denote* or *specify* a type in one or another way (some can denote less and some more types). – Johannes Schaub - litb May 19 '13 at 14:47
  • @JohannesSchaub-litb: Hm, yes, they certainly both supply types, but the way I think about it is that the first form supplies `T` and says "make an array of `N`", and the second form supplies `T[N]` and says "make one". The result is indeed in both cases an array of `N` elements of type `T`, as I hope I've stated. – Kerrek SB May 19 '13 at 15:18
  • Both create `N` distinct object of type `T`, using the array allocator. Even if you use `typedef T T5[5]; new T5` the compiler still uses `operator new[]`. – Ben Voigt May 19 '13 at 19:21
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
+----------------------------------------------------------------+
|+-----+-----+-----+-----+-----+-----+-----+-----+------+------+ |
||     |     |     |     |     |     |     |     |      |      | |
||     |     |     |     |     |     |     |     |      |      | |
||     |     |     |     |     |     |     |     |      |      | |
|+-----+-----+-----+-----+-----+-----+-----+-----+------+------+ |
+----------------------------------------------------------------+

In fact both of them are equal.

 

Here is the code generated by assembler (just an ignorable difference):

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)

You must delete both of them by delete [] ...

masoud
  • 55,379
  • 16
  • 141
  • 208
  • I'd point out that `_Znaj` is g++-manglish for `void* operator new[](unsigned int);` – aschepler May 19 '13 at 13:26
  • -1: Just because two things generate the same (or similar) assembly code for a trivial code frament doesn't mean that the code is "equal" or even equivalent; assembly is very low-level and doesn't care about types, while this entire problem is about high-level types which are only at the C++ level. Assembly dumps should only be inspected when it comes to issues of performance, NOT correctness. Consider how different they are when the type is an object with a constructor and destructor, and not just a fundamental like int or char. – fluffy May 19 '13 at 17:42
  • @fluffy: (1) The statement about _equality_ is the result of its previous notes not following assembly descriptions. (2) Of course not, I agree we can't get the result from some assembly code about a C++ code. **BUT** assembly codes which generated by compiler are good observation to get familiar to the behavior of a piece of C++ code. – masoud May 19 '13 at 18:42
  • Right, but my point is that the code isn't equivalent in the first place. They just happen to have the same behavior under a very specific set of circumstances. – fluffy May 20 '13 at 19:04