22

I'm reading Stroustrup's A Tour of C++. On page 9, he states:

"The size of an array must be a constant expression."

Yet later, on pg. 16, he uses the following code sample:

void vector vector_init(Vector& v, int s)
{
  v.elem = new double[s]; // Allocate an array of s doubles
  v.sz = s;
}

Here s is not a constant expression, so how is initializing v.elem to new double[s] legal?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
George
  • 6,927
  • 4
  • 34
  • 67
  • 2
    constant expressions are required for arrays on the stack, not on the heap. http://www.learncpp.com/cpp-tutorial/79-the-stack-and-the-heap/ – Hatted Rooster Mar 23 '16 at 12:49
  • 3
    It's kind of implied by the rest of the paragraph (but should be spelled out, in my opinion) that the quote from page 9 refers to the *declaration* of an array. – molbdnilo Mar 23 '16 at 12:49
  • 3
    @GillBates It has nothing to do with where the array is stored. If the array is a member of a class and an instance of that class is dynamically allocated, the array declaration still requires a constant-expression size, despite the actual array being "on the heap". – molbdnilo Mar 23 '16 at 12:52
  • The author also qualifies the book to be 'thin'. In my opinion, it is not a book to learn such fundamental concepts from. – Elan Mar 23 '16 at 12:53
  • 1
    @molbdnilo That's obviously not what I meant. I was talking about how the array itself is initialized. Through `new` with a pointer to it or just locally. – Hatted Rooster Mar 23 '16 at 12:54
  • The size of array is till constant. Once it is allocated its size can not be changed without losing all the original data making it constant. – Sarthak Singh Mar 23 '16 at 12:54
  • It means the size of a C-style array (i.e. an array whose name was declared with `[]`). Other sorts of array are resizeable. – M.M Mar 23 '16 at 13:20

3 Answers3

41

There is a differentiation between allocated arrays (i.e. those created with a new[] expression, like new double[s]), whose lifetimes must be managed by the code (via delete[]) and declared arrays, whose lifetimes are managed by their scope alone:

int* p = new int[s];  // allocated array, p has type int*
int q[10];            // declared array, q has type int[10]
std::vector<int> u;   // has member allocated array
std::array<int, 5> v; // has member declared array

The differentiation is not based on stack/heap. A declared array can be heap allocated (e.g. new array<int,5>) or not on the stack (e.g. static double x[100];)

With an allocated array, the size does not have to be a constant expression. The size will simply be encoded into the block of memory yielded by the allocator somehow (for instance the four leading bytes before the actual data starts) so that the corresponding delete[] knows how many elements to delete.

With a declared array (or non-allocated array, no new/malloc/etc.), the size must be coded into the type, so that the destructor knows what to do. The only allowed, standard array declaration is:

T D[constant-expression_opt];

(where D is a declarator that could be a name or another array declaration, etc.) Declared arrays are not limited to the stack. Note that, for added confusion, the constant-expression is optional.

Arrays offer many sources of confusion in C++. Allocated and declared arrays have different size rules, different management practices, but you can assign a T* to either and they're equivalently indexed. An allocated array is a pointer (that's all you get), but a declared array decays to a pointer (but is an array!).


Note that there is a concept of a Variable Length Array (VLA). gcc, for instance, supports them as an extension, but they are non-standard C++. It gets periodically proposed though, and you can see this question for more information about them.

Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    A much better answer than the one currently most upvoted. – SergeyA Mar 23 '16 at 13:41
  • Maybe you could also add infos about the behaviour of the `sizeof` operator (e.g. `sizeof(p) == sizeof(int *)` vs `sizeof(q) == 10*sizeof(int)`) – king_nak Mar 23 '16 at 15:57
  • @king_nak I don't see that as adding additional information, given that the types are already listed. It's the types that are important. – Barry Mar 23 '16 at 16:35
  • It is another distinguishing property of allocated and declared arrays. Beginners quite often try to get the number of elements in an allocated array using `sizeof(arr)`, which will not give the correct result. I just thought it is worth mentioning – king_nak Mar 23 '16 at 16:50
  • @king_nak I would say that type is *the* distinguishing property. Size is a function of the type. – Barry Mar 23 '16 at 17:43
11

When creating an array whose memory is managed by compiler, its size must be (compile time) constant. For ex:

int a[5];
const int sz = 7;
int b[sz] = {0};

(Some languages for ex: C (C99 onwards) support dynamic array size)

If you want a dynamically sized array (Example snippet given by you), you need to allocate memory for it by yourself you'll also need to free it with delete when you're done. Size of such arrays can be non-const also. Moreover you need to manage the memory by yourself, allocation may fail and operators (for example sizeof) would operate on the pointer rather than array.

In modern C++ (C++11 onwards), even stl container std::array must have a constant size.

Mohit Jain
  • 30,259
  • 8
  • 73
  • 100
4

The quote

The size of an array must be a constant expression.

is talking about an array declaration, such as

double a[EXPR];

where EXPR must indeed be a constant or constexpr (C has variable-length arrays, but they're not part of standard C++).

The expression you mention as a counter-example,

new double[s]

is not an array, despite the []. It is a new-expression, and yields a pointer, not an array. You didn't show the definition of v.elem, but I can tell it's a pointer-to-double.

Note from the linked discussion on new expressions that

If type is an array type, all dimensions other than the first must be specified as positive {something like an integral constant - detail elided}.

So the type referred to above is double[s], which is explicitly allowed.

Admittedly the difference between an array, and the array type passed to a new expression is a little subtle, but you can't conflate them just because of the [], any more than you can claim that

map["key"]

violates something by declaring an array with length "key".

Useless
  • 64,155
  • 6
  • 88
  • 132