Difference Between Static and Dynamic Arrays in C++

What is the difference between Static and Dynamic arrays in C++?

Static arrays are created on the stack, and have automatic storage duration: you don't need to manually manage memory, but they get destroyed when the function they're in ends. They necessarily have a fixed size at compile time:

int foo[10];

Arrays created with operator new[] have dynamic storage duration and are stored on the heap (technically the "free store"). They can have any size during runtime, but you need to allocate and free them yourself since they're not part of the stack frame:

int* foo = new int[10];
delete[] foo;

Dynamic vs static array in c

There are several flavors of arrays, depending on how and where they are declared.

Fixed-length Arrays

Fixed-length arrays must have their size determined at compile time. You cannot change the size of a fixed-length array after it has been defined.

Fixed-length arrays are declared in one of the following ways:

T a[N];
T a[N] = { /* initializer list */ };
char_type a[N] = "string literal";
T a[] = { /* initializer list */ };
char_type a[] = "string literal";

In the first three cases, N must be a constant expression whose value must be known at compile time. In the first three cases, the size of the array is taken from N; in the last two cases, it's taken from the number of elements in the initializer list or the size of the string literal.

The initial contents of a fixed-length array depend on its storage duration and whether an initializer has been supplied.

If the array has static storage duration (meaning it was declared at file scope outside of any function body, or was declared with the static keyword) and no initializer is present, then all of the array elements are initialized to 0 (for scalars) or NULL (for pointers). If T is an aggregate type such as a struct or an array type, then each member of the aggregate is initialized with a 0 or NULL. union types are similarly zeroed out.

If the array has auto storage duration (meaning it was declared within a function or block without the static keyword) and no initializer is present, then the contents of the array are indeterminate - basically, garbage.

If the array is declared with an initializer list (regardless of storage duration), then the initial values of the array elements correspond to the initializer. If there are fewer elements in the initializer than the array (for example, N is 10 but you only initialize the first 5 elements), then the remaining elements are initialized as though the array had static storage duration. IOW, given the declaration

int a[10] = {0, 1, 2};

then the initial contents of the array are {0, 1, 2, 0, 0, 0, 0, 0, 0, 0}.

Fixed-length arrays containing string values may be initialized using a string literal. C allows for "wide" character strings, so char_type may be either char or wchar_t. The rules are the same for regular initializer lists, except that N (if specified) must be at least 1 more than the length of the string to account for the string terminator.

This means that

char a[10] = "test"; 

will be initialized as {'t', 'e', 's', 't', 0, 0, 0, 0, 0, 0} and

char a[] = "test";

will be initialized as {'t', 'e', 's', 't', 0}.

Arrays with static storage duration are stored such that they are available as soon as the program is loaded, and aren't released until the program exits. This usually means that they're stored in a memory segment like .data or .bss (or the equivalent for whatever executable format your system uses).

Arrays with auto storage duration are stored such that they are allocated at block or function entry and released at block or function exit (in practice, they'll probably be allocated at function entry, regardless of whether they're limited to a smaller scope within the function) - this typically translates to the stack, although it doesn't have to.

Variable-length Arrays

Variable-length arrays were added in C99 - they behave mostly like fixed-length arrays, except that their size is established at run time; N does not have to be a compile-time constant expression:

int n;
printf( "gimme the array size: ");
scanf( "%d", &n );
T a[n]; // for any type T

Contrary to what their name implies, you cannot change the size of a variable-length array after it has been defined. "Variable-length" simply means that the size isn't fixed at compile time, and can change from definition to definition.

Since their size isn't set until runtime, variable-length arrays may not be declared at file scope or with the static keyword, nor can they be declared with an initializer list. Exactly how the space for VLAs is managed is up to the implementation; it may be (and usually is) taken from the stack, but AFAIK may be taken from somewhere else.

Dynamic Arrays

Dynamic arrays are not really "arrays" as such, at least in terms of the data types of the objects we use to manage them. Dynamic arrays are allocated at runtime using one of malloc, calloc, or realloc, and that storage is held until released with a call to free.

T *p = malloc( sizeof *p * N ); // where N may be either a compile-time or
// run-time expression
...
free( p );

A dynamic array may be resized using the realloc library function, like so:

/**
* If realloc fails, it will return NULL and leave the original array in
* place. We assign the result to a temporary variable so we don't risk
* losing our only reference to that memory.
*/
T *tmp = realloc( p, sizeof *p * new_size );
if ( tmp )
p = tmp;

While the memory for the array elements themselves is taken from the heap (or whatever dynamic memory pool), the memory for the pointer variable p will be allocated from either a .bss or .data segment or from the stack, based on p's storage duration (static or auto).

Memory allocated with malloc or realloc is not initialized; the contents of that memory will be indeterminate. Memory allocated with calloc will be initialized with zeros.

Arrays vs. Pointers

At some point, somebody is going to tell you that "an array is just a pointer". That person is not correct.

When you declare an array (either fixed- or variable-length), enough storage is set aside for the elements of that array and nothing else; no storage is set aside for any metadata such as the array length or a pointer to the first element. Given the declaration

T a[N];

then the storage will look something like this:

    +---+
a: | | a[0]
+---+
| | a[1]
+---+
| | a[2]
+---+
...
+---+
| | a[N-1]
+---+

There is no object a apart from the array elements themselves (or, more properly, the object a is the elements of the array), and the expression a may not be the target of an assignment.

But...

The expression a[i] is defined as *(a + i); that is, given a pointer value a, offset i elements (not bytes!) from that address and dereference the result. But if a is not a pointer, how can that work?

Like this - except when it is the operand of the sizeof or unary & operators, or is a string literal used as an array initializer in a declaration, an expression of type "N-element array of T" will be converted ("decay") to an expression of type "pointer to T", and the value of the expression will be the address of the first element of the array.

This has several implications:

  • The expressions a, &a, and &a[0] will all yield the same value (the address of the first element of the array), but the types of the expressions will be different (T *, T (*)[N], and T *, respectively);
  • The subscript operator [] works equally well with both array expressions and pointer expressions (indeed, it's defined to work on pointer expressions);
  • When you pass an array expression to a function, what you are actually passing is a pointer value, not the entire array;

For dynamic arrays, the situation is different. Given the line

T *p = malloc( sizeof *p * N );

then your storage will look something like this:

   +---+
p: | | ---+
+---+ |
... |
+------+
|
V
+---+
| | p[0]
+---+
| | p[1]
+---+
...
+---+
| | p[N-1]
+---+

In this case, p is a separate object from the array. Thus, &p won't give you the same value as p and &p[0], and its type will be T ** as opposed to T (*)[N]. Also, since p is just a pointer variable, you can assign a new value to it (although if you do so without freeing the memory it points to first, you'll create a memory leak).

Similarly, sizeof p won't behave like sizeof a; it will simply return the size of the pointer variable, not the size of the allocated memory that the pointer points to.

Revisited: difference between static array and dynamic array in C++?

int array1[10];
int *p = array1;
// when run out of the memory, we can resize
int array2[20];
copy(array1, array2); // copy every elements from array1 to array2;
p = array2;

In whichever function, or inner scope, array1 and array2 get declared these arrays get automatically destroyed when the function or inner scope returns. Full stop.

This is why this is called "automatic scope". The fact that there may be a pointer to one of the arrays is immaterial. The array will be gone and any attempt to dereference that pointer will result in demons flying out of your nose.

So if you had any grand designs to continue using this array, in some form or fashion, after returning from the function where they get declared, too bad. It's not going to happen.

On the other hand, after newing something, as long as you properly track the pointer to the newed object(s) they can be used anywhere else, until they get deleted. This function, another function, anywhere. Even a different execution thread.

Having said all of that, you should not be using new or delete either. You should be using C++ library's containers which will correctly handle all memory allocation, deallocation, and copying, for you. In this case, you are simply reinventing what std::vector already does for you, and it will actually do it, in some ways, far more efficient than you can do easily on your own. You just call resize(), and, presto, your vector is bigger or smaller, as the case may be. And, in all other respects the vector will be indistinguishable from your array. It will be very hard to tell the difference.

So, use C++ library's containers. They are your friends. They want you to do memory allocation correctly, on your behalf. Modern C++ code rarely uses new or delete, any more. It's important to understand how it works, but 99% of the time you don't really need it.

What is the difference between static and dynamic arrays in C?

int a[10];

is allocated on stack and is de-allocated as soon as the scope ends.

int *b = (int *) malloc(10 * sizeof(int));

is allocated on heap and is alive throughout the lifetime of the program unless it's explicitly freed.

When to use static arrays vs dynamic arrays C

Note that the word “static” has other meanings in C. You appear to be asking about the difference between declaring an array with a constant size, such as int a[40], versus declaring an array with a variable length, such as int a[n], where n is known at run time but generally not at compile time.

In this case, a general rule is to use a static size when you can:

  • when the exact size is known at compile time, or
  • when an upper bound is known and using the upper bound will not waste too much space.

In general, using a static size is more efficient because the compiler has more information and therefore has more opportunity for optimization. When the compiler is compiling address subscript operations, it has to generate instructions to calculate addresses. If it knows the array size, it may have opportunities to perform some of the calculations at compile time (e.g, for int a[40]; a[13] = 2;, the compiler can calculate that a[13] is 13•4 = 52 bytes from the start of a (assuming a four-byte int, of course) or to include the array size as an immediate operand in instructions (meaning it is built into the code and does not have to be looked up in memory or otherwise obtained at run time).

If the compiler does not know the array size, it must generate complete code to calculate addresses while the program is running. In today’s typical programming environments, this is usually not a great cost, but it may be a consideration.

Additionally, if an array has static size, it can be an external object (defined outside of any function). External objects have static storage duration, which means they exist for the lifetime of a running program. (Here, “static” is used in the C sense, different from the fixed-size sense.) When the compiler knows the size of an array, it can plan storage for it that is provided when the program starts. This enables an array with static size to have static storage duration. For an array with dynamic size, compilers generally cannot plan the necessary storage for them, so they cannot be external objects.

Difference between a static and dynamic array

Actually _static and &_static don't refer to the same location. The only reason they appear to is because you use _static in a context in which it decayed into a pointer. That is, by the way you used them, you made them refer to the same location. But they didn't before you did that -- one was an array and the other was a pointer. They couldn't be the same because they were fundamentally different things.

Difference between dynamically allocated arrays and static arrays

Now, people have already stated that int a[n] is not valid c++. But Perhaps I could help you with the answer you're looking for.

What is the advantage of dynamic arrays in this case then?

The syntax int a[n] is called a VLA (Variable Length Array). These are illegal in C++ but allowed in C. So let's focus on the technical differences, or rather disadvantages of VLAs.

Let's get the obvious thing out of the way first. C89 and before did not have VLA and hence dynamic allocation was the only way for allocating variable length memory.

One more thing, static arrays and even VLAs are allocated on the stack (although this is implementation defined, but more often than not, it will be on the stack). Whereas dynamic arrays are allocated on the heap. For more information on the stack and the heap, read this

Now, VLAs are banned in C++ for a very good reason. VLAs can cause all sorts of undefined behaviours and should always be avoided unless you know exactly what you're doing. And by "you know exactly what you're doing", I mean you know that the size argument of that VLA will not overflow the stack.

Suppose VLAs were allowed in C++, this line in your code-

cin>>n;
int a[n];

What if the user enters a massive n, way more than the stack size? That's a guaranteed stack overflow. Notice the problem? As compared to the heap, the stack is very tiny. This is also explained here and also here

And this is the primary reason VLAs should be avoided at all costs. Though VLAs actually come with much more boom than the aforementioned. Infact I always keep a list of UBs associated with VLAs handy, there just is that many problems.

So going back to my point of

[VLAs] should always be avoided unless you know exactly what you're doing

Honestly, you should never use VLAs, and you really can't because that's not even standard C++. But stack allocation is often faster than heap allocation. Though not for reasons one might consider obvious. Read this. So sometimes, if you're on C (not C++), the only times using a VLA is safe is when you know the max size of n in int a[n] will not overflow the stack and the declaration of the VLA is at the top of the scope you are currently declaring it at. The creator of alloca (which used to be the only way to use VLA prior to c99) seems to agree.

Excerpt from here-

You may use alloca() in the form of:

pointer_variable = alloca(expression);

as an expression statement in the outermost block of a function.

Oh and just to answer your edit:

Thanks for your answers. Some users responded by saying that declaring an array by typing a[n] is not allowed. However, why does my program run fine when I type the following code :

It's because your compiler allows it. But remember, the standard does not. So these kinds of things can give birth to the good ol' "It worked on my machine!"

difference between static and dynamic declarations of 2D arrays

the big difference is that in case with a[100][100] the compiler knows the full size of the array and allocates a contiguous memory region on a stack (as in your case) or in static area. When accessing an array element, the compiler is free to calculate its address based on the array dimensions and use a single reference to access it. like this

[0,0][0,1][0,2]...[0,99][1,0][1,1][1,2]...[99,0]...[99,99]
+-------0------...-----+----- 1 -------...+----- 99 -----+

In case of the dynamic allocation, which you used, the memory is allocated contiguously only for a single dimension of the array. So, you allocate 100 pointers, every one of each points to a single dimensional array of integers. Those arrays can be placed at arbitrary memory locations. As a result, the compiler has to do at least 2 references in this case, use first index to get a pointer to the second array, than use the second index to get the element in the second array.

pointers[index0] : [0][1][2]..[99]
/ | \
/ | |
V V V
[0] [0] [0]
[1] [1] [1]
... ... ...

some addition about a and *a. In 'c' when the name of the array is used in a pointer like context, it is interpreted as an address of the array. So, in printf a points to the beginning of the two-dimensional array. *a for same reason is supposed to provide you an address of the first column. Which in this case is the same as the start or the array. **a will point you to the very first array element a[0][0]. And by the way, it is better to use %p there instead of %d for pointers.

You can see that for the dynamic array p gives you the address of the array of pointers, whether *p gives you the value of its first element p[0] which by itself is a pointer to the column. The addresses are definitely different.

But in both cases you can use a[i][j] and p[i][j] to access array elements.



Related Topics



Leave a reply



Submit