Dynamic Zero-Length Arrays in C++
Asked Answered
C

2

11
#include <stdlib.h>

void *operator new[](size_t size, int n){
    if( size != 0 && n != 0 )
        return calloc(n, size);
    return calloc(1, 1);
}

int main(){

    int * p1;
    const int i = 0;

//  p1 = new (20)  int[i] ; // Case 1 (OK)
    p1 = new (20) (int[i]); // Case 2 (Warning)

    if( p1 == 0 )
        return 1;
    return 0;
}

This code (https://godbolt.org/g/hjo7Xn) compiles successfully with Clang 6.0.0, however, GCC 7.3 issues a warning saying that zero-length arrays are forbidden in C++. If the parentheses are removed (Case 1), the warning goes away.

Unlike statically allocated zero-length arrays (C++03:8.3.4/1), dynamically allocated zero-length arrays are allowed (C++03:5.3.4/6). Nevertheless, in the C++ Standard the latter are explicitly allowed only when following one of the two possible syntax paths of the new-expression, that is, the one with new-type-id and without parentheses (Case 1).

Is it allowed by the C++ Standard to use the new-expression with a zero-length array following the second syntax path, that is, with type-id and parentheses (Case 2)?

The only related quote is C++03:5.3.4/5:

When the allocated object is an array (that is, the direct-new-declarator syntax is used or the new-type-id or type-id denotes an array type), the new-expression yields a pointer to the initial element (if any) of the array.

The wording (if any) would allow an array with no elements, however, it does not seem clear if it refers to both cases or only to the one with new-type-id and without parentheses (Case 1).

Thanks in advance.

Notes:

  1. ISO/IEC 14882:2003, Section 8.3.4, Paragraph 1:

    If the constant-expression (5.19) is present, it shall be an integral constant expression and its value shall be greater than zero.

  2. ISO/IEC 14882:2003, Section 5.3.4, Paragraph 6:

    The expression in a direct-new-declarator shall have integral or enumeration type (3.9.1) with a non-negative value.

  3. ISO/IEC 14882:2003, Section 5.3.4, Paragraph 7:

    When the value of the expression in a direct-new-declarator is zero, the allocation function is called to allocate an array with no elements.

  4. ISO/IEC 14882:2003, Section 5.3.4, Paragraph 1:

    new-expression:

    ::opt new new-placementopt new-type-id new-initializeropt

    ::opt new new-placementopt ( type-id ) new-initializeropt

  5. Although the above quotes are from the C++03 Standard, to the best of my knowledge this issue is still unclear in newer versions of the C++ Standard (C++11, C++14 and C++17).
  6. Interesting Herb Sutter's post about zero-length arrays.
  7. The code in the example is a slightly modified test from SolidSands' SuperTest suite.
Calistacalisthenics answered 30/4, 2018 at 18:8 Comment(6)
Just curious: Is there a reason why you are quoting the C++03 standard if you want C++11 compliance? Because various drafts are available online which might be easier to quote/link.Government
What's with the custom buggy operator new[]? What's that got to do with anything?Bosworth
No warnings from gcc 8.Gleda
@Rakete1111I'm interested in compliance from C++03 onwards. But you are right, it would have been better to just link to the Standard instead of quoting directly. If I find an online linkable per section (not PDF) C++03 Standard I will update the post.Oscular
@Cheersandhth.-Alf I think the issue would be the same, I left it because I didn't want to modify that much the original test (I already reduced it in other parts). Why is it buggy?Oscular
@JoséLuis: it does not guarantee to return at least the requested number of bytes, if it returns.Bosworth
B
2

With parenthesis you have a regular type-id, instead of the special syntax called a new-type-id that supports dynamic array size.

As of C++17 the standard makes no special provision for this use of a type-id, so the question boils down to whether you can write

auto main() -> int
{
    using Argh = int[0];
}

You can't, because a type-id's type is defined in terms of a fictitious “declaration for a variable or function of that type that omits the name of the entity” (C++17 §11.1/1), and for declaration of an array variable the rule is “If the constant-expression (8.20) is present, it shall be a converted constant expression of type std::size_t and its value shall be greater than zero” (C++17 §11.3.4/1).


Now there's a fair bit of interpretation in this. E.g. without such reasonable interpretation the last quote would not reasonably say that the array size must be non-negative and representable as a size_t. Instead, with no reasonable interpretation, it would literally say that a declaration such as

int x[42];

is invalid (of course it isn't), and would have to be expressed as

int x[std::size_t(42)];

Determining what is a reasonable interpretation or not used to be easy. One could just ask, does it make sense? So for the case above the answer would be no, and one could ditch that possibility.

However, to some degree with C++14 and increasingly more so with C++17 I find that that earlier dependable technique fails. Since the question at hand is about C++03 functionality, however, I think you can trust this answer. But if it were a question about C++14 or later stuff, then do keep in mind that any apparently clear-cut answer probably involves some subjective interpretation that probably can't be resolved by asking whether it makes sense or not.

Bosworth answered 1/5, 2018 at 11:5 Comment(0)
L
0

No, the zero-size case cannot use the parenthesized type-id. The behavior for an array of size 0 is given (in the current draft) only for the expression in a noptr-new-declarator ([expr.new]/7). A new with a parenthesized type attempts to create an object of that type, and there are no arrays of size 0 ([dcl.array]/1), not even as a type-id ([dcl.name]/1).

Zero-sized arrays are of course a common extension, so practical results may vary.

Library answered 1/5, 2018 at 4:32 Comment(7)
Also a dynamically allocated array is an object.Bosworth
@Cheersandhth.-Alf: Yes, but we have in /7 the unique idea that one can “allocate an array with no elements”.Library
I meant to say, that the statement “A new with a parenthesized type attempts to create an object of that type", is meaningless, because every (successful) new expression evaluation creates an object.Bosworth
@Cheersandhth.-Alf: What object does new int[0] create? The only reasonable type for it cannot be named (even via decltype). Perhaps it can be said to exist anyway, but it makes “new always makes an object” questionable.Library
It's an int[], that's the most that is known. Which is also the case for the object created by new int[argc].Bosworth
@Cheersandhth.-Alf: No object is created with a type that is incomplete (at that point).Library
When you deny that the type of the array object created by new int[argc], is incomplete, you have to cough up the complete type that you think it has. Or else, since every C++ object has a type, you're denying that there is an object at all. But giving a complete type is impossible, since types must be known at compile time, and argc value isn't known until run time. So what you assert is impossible. In other words, this chain of denial goes right into Fallacy Territory. Q.E.D.Bosworth

© 2022 - 2024 — McMap. All rights reserved.