Declaring Arrays in C++
Declaring an array of values looks a lot like declaring a single value, except that we add the size of the array in square brackets after the name:
int values[42];
How do I pronounce that line? “int values square-brackets 42”?
We read this declaration using the “inside-out” rule: start at the variable name, then move out toward the edges. Go right first, then left.
In this case, we'd say, “Values is an array of forty-two
int
s”.We'll talk more about reading types a bit later in the semester.
Arrays of primitives (basic building block types, like numbers) in C++ follow the same object lifetime as single primitive variable.
-
For allocation and deallocation, the only difference is that an array of
n
values will need to managen
times as much space! -
When it comes to initialization and destruction, they're initialized in order (first element first) and destroyed in reverse order (first element last).
For example, here is a little program:
int main() {
int num = 42;
int arr[5];
// Probably we should do something with these variables, but never mind.
return 0;
}
You can see that the array takes up five slots, one for each int
in the array.
We've left the names for the individual slots off for now. We'll get to that in a moment.
Allocation for Arrays of Primitives
At the opening curly brace of the function, space will be allocated in a contiguous block for all of the values in the array.
If our function has the declaration
int myArray[3];
the compiler will write the instruction(s) needed to allocate enough space for three int
s.
So this is figured out before the program even runs?
Yes. Remember that it's the compiler's job to write the instruction(s) saying how much memory to allocate to hold the array.
So we need to know how big the array will need to be at compile time.
The compiler always needs to know when it compiles the code how big an array is going to be so it can set aside the right fixed amount of space, so writing code like the following is not allowed:
int values[]; // This code won't compile, the size is missing.
Our compiler, clang
, produces an error like
arrayBadDecl1.cpp:2:9: error: definition of variable with array type needs an explicit size or an initializer
int values[];
^
1 error generated.
I'm thinking about when you showed the size, with
int myArray[3];
… Didn't we learn in previous classes that sticking random “magic numbers” in our code was bad?Yes—a “magic number” is a raw numeric value in our code.
We do want to avoid magic numbers, because they are bad for maintainability.
If you need to change the number, you'll need to change it everywhere it appears and could make mistakes while doing so.
So… can we use a variable instead of a magic number to say how big our array is?
Let's look at that… there is actually a reason we can't use a variable there.
You might be tempted to write something like
// The compiler won't like this code either...
size_t numberOfValues = 3;
int myArray[numberOfValues];
But it won't compile. One popular C++ compiler (g++
) produces the following error for this code:
arrayBadDecl2.cpp: In function 'int main()':
arrayBadDecl2.cpp:5:9: warning: ISO C++ forbids variable length array 'myArray' [-Wvla]
5 | int myArray[numberOfValues];
| ^~~~~~~
Remember that we said that memory is set aside for all function variables before the code starts running (“buying the land” in our building analogy). As a result, the compiler expects to figure this stuff out when it is compiling the code (i.e., “at compile time”), and generate the necessary machine instructions to allocate this fixed-size space on the stack frame.
You might think that the compiler could see that we put the value 3
in numberOfValues
, but assigning values to variables is something that happens when the program runs, not when it is compiled. As human beings, we might be able to reason out that the value we just put in that variable will still be there at the very next line of code where we used it, but the compiler shouldn't be expected to figure out that kind of stuff. At compile time, the compiler is mostly paying attention to variable types, not what values they will have.
So with this example, all the compiler knows for sure is that we need space for numberOfValues
integers!
And it doesn't know at compile time what the value of numberOfValues
is, since that won't be set until the program actually runs.
But I'm sure there has to be some way to avoid magic numbers?
We can make this work. We just have to change
numberOfValues
so it isn't a variable but a constant.
The constexpr
keyword tells the compiler that
- The value of this variable should be known at compile time (so, if it isn't, generate an error) and
- The value of variable should not change during runtime (so, if it does, generate an error).
As a result, variables that are constexpr
are okay to use as array sizes.
constexpr size_t numberOfValues = 3; // Constant value, known at compile time.
int myArray[numberOfValues]; // Which makes this code okay.
Now that numberOfValues
is a compile-time constant expression, it is reasonable to expect the compiler to figure out what value it might have at some arbitrary point in the code, because it always holds the exact same fixed value.
Initialization for Arrays of Primitives
At the declaring line, all of the values in the array will be initialized, if they've been given. We can give initial values for an array by putting them in a comma-separated list inside curly braces; so
int oneTwoThree[3] = {1,2,3};
int fourFiveSix[3]{4,5,6}; // also works
will initialize the first value in oneTwoThree
to be 1
, the second to be 2
, and the third to be 3
.
That makes sense for three values… but won't it get kind of cumbersome if we have an array with a thousand values in it?
Yes! We have the option of leaving off the initial values.
If we write
int oneTwoThree[3];
then oneTwoThree
will still be initialized on the declaration line, but since we didn't give initial values, the int
s will all be default initialized.
Default initialized means set to zero, right?
No—for primitive types like
int
anddouble
, they have indeterminate value when default initialized. Don't expect them to hold any kind of sensible value.Frequently it's “whatever bits happened to be lying around when the space was allocated on the stack.”
In most situations, it's considered bad practice to default initialize variables that hold primitive types (e.g., int
, double
, etc.), but it's okay to default initialize the values in an array of primitive types as long as the very next thing you do is set the values (e.g., using a for
loop).
We'll see how to do that on the next page!
(When logged in, completion status appears here.)