An array is a fixed-size block of memory that holds a sequence of values, all of the same type, sitting one right after another. When a program needs to track the quantities of ten products, the prices in a cart, or the ratings on a review page, a single variable per value gets clumsy fast. An array lets you store all of them under one name and reach each one by its position. This lesson covers C-style arrays: declaring them, initializing them, reading and writing individual elements, and the sharp edges that catch beginners.
Consider a small online store that wants to track the stock count of five products. Without arrays, the program needs a separate variable for each one:
That works for five products. Consider a hundred. Or a thousand. Naming a thousand variables, then summing them by hand, is not a serious option. Even worse, the variables aren't connected to each other. The compiler doesn't know that stock1 through stock5 belong together, so you can't loop over them. Every operation has to spell out each name.
An array fixes both problems. It groups related values under one name and gives each value a number you can use to reach it:
Same answer, one name, and the values live next to each other in memory. The numbers in square brackets (0 through 4) are called indices, and they're how you pick which element you want. A loop can turn those five lines of addition into two.
The declaration syntax for a C-style array is:
Three parts: the element type, the array's name, and the number of elements in square brackets. The size must be a constant the compiler can figure out at compile time. It can't be a variable that gets its value at runtime.
When you declare an array without initializing it, the elements have indeterminate values if the array is a local variable. That means the memory the array occupies is whatever was left over from the last thing that used those bytes. Reading from an uninitialized local array is undefined behavior.
The output could be 0, it could be -13947, it could be anything. The compiler isn't required to clear the memory for you. Always initialize local arrays before you read from them. The next section shows how.
The diagram lines up the parts of an array declaration. Reading left to right: an int array named quantities with room for five elements, stored as five int-sized slots in a single block of memory.
Most of the time you want the array to start with known values. C++ gives you several ways to write that, and they each have a slightly different effect.
The most common form lists every value in braces:
Five values for a size-5 array. Each value lands in the matching slot: 12 at index 0, 7 at index 1, and so on.
If you provide fewer values than the array's size, the remaining slots get value-initialized. For built-in numeric types, that means they're set to 0. For bool, false. For char, '\0'.
Only two values were listed, but all five slots are well-defined: the first two hold what you wrote, and the rest hold 0. This zeroes out the tail of an array while filling the front with real data.
The shortest way to zero out an entire array is empty braces:
Every element becomes 0. This is the safe default when you want to fill the array later but don't want garbage values in the meantime.
If you provide an initializer list and leave the brackets empty, the compiler counts the values and uses that as the size:
The array's size is 5, deduced from the five values in the braces. This is handy when the size and the values are closely tied: you don't want to write 5 once and 12, 7, 25, 3, 18 next to it, then have to update both if you add a sixth value.
The "value-initialized" rule produces these defaults for the common built-in types:
| Type | Default after {} or partial init |
|---|---|
int, long, short | 0 |
double, float | 0.0 |
bool | false |
char | '\0' (the null character, value 0) |
| pointer type | nullptr |
There's also one form that does not initialize the tail:
The leftmost form is the dangerous one. Pick one of the other two unless you know you're about to overwrite every slot before reading any of them.
Once an array exists, you reach individual elements with the [] operator. The number inside the brackets is the index, and it tells the compiler which slot you want.
C-style arrays in C++ use zero-based indexing. The first element is at index 0, the second at index 1, and the last is at index size - 1. An array of size 5 has valid indices 0, 1, 2, 3, and 4. There is no element at index 5.
Reading is array[index] used as a value. Writing is the same expression on the left side of an =. Both forms work because quantities[1] refers to the actual slot in memory, not a copy of its value.
A memory-layout picture for the same array:
The five slots sit next to each other in memory. There's no gap between them. The index isn't a label stored with the element; it's an offset the compiler uses to find the slot starting from the array's first byte. Index 0 is the very first slot, index 4 is four slots past the start.
Indexing a C-style array is O(1). The compiler translates quantities[i] into "take the address of quantities, add i * sizeof(int), read that location." It's the same speed no matter how big the array is or which slot you want.
A common beginner habit is to start counting from 1 because that matches how people normally count. C++ does not. The first element is at index 0, and reaching for index 5 in a size-5 array is one of the most common bugs in the language.
sizeof TrickC-style arrays don't carry their size as a member you can query. There's no quantities.size() for a raw array. What you can do is ask the compiler how many bytes the array occupies and divide by the size of a single element:
On a typical desktop compiler, int is 4 bytes, so an array of five ints takes 20 bytes. Dividing 20 by 4 gives the element count. The full expression sizeof(quantities) / sizeof(quantities[0]) works for any element type because both sides scale together: a double array would have sizeof(quantities[0]) equal to 8, and the bytes total would scale to match.
This trick only works in the scope where the array was declared. Once you pass an array to a function, the array "decays" to a pointer, and sizeof on the pointer no longer gives you the array's size.
In modern C++ there's a cleaner alternative: std::size(prices) from <iterator> (added in C++17) gives the element count directly without the division. The sizeof trick is still worth knowing because it appears in much in older code and in code that supports older standards.
sizeof is a compile-time operation. The compiler bakes the answer into the binary, so there's no runtime work. The whole expression sizeof(arr) / sizeof(arr[0]) reduces to a plain integer literal in the compiled program.
A C-style array's size has to be known when the program is compiled. You can't ask the user for a number and then declare an array that big:
Some compilers accept this as a non-standard extension called variable-length arrays (g++ allows it with a warning), but it's not part of the C++ standard, and MSVC rejects it outright. Don't write code that depends on it. For sizes that aren't known at compile time, use std::vector (covered in the Containers section).
A C-style array also cannot grow. Once int stock[5]; is declared, it has five slots forever. You can't add a sixth element. Writing past the end (stock[5] = 99;) is one of those bugs we'll talk about in the last section, but the short version is: the array is stuck at its declared size.
If you genuinely need a collection that can grow at runtime, that's std::vector. If you know the size at compile time and want something that behaves like a proper C++ object (knows its size, can be returned from functions cleanly), that's std::array, covered in the _std::array_ lesson.
When you pass a C-style array to a function, C++ does not pass the whole array. Instead, the array's name is automatically converted to a pointer to its first element. This conversion is called array decay. For now, treat a pointer as "a value that holds the address of something." The detail that matters in this chapter is what gets lost in the conversion: the size.
Output (typical 64-bit system):
Inside main, sizeof(stock) is 20 because the compiler knows it's a size-5 array of 4-byte ints. Inside printSize, sizeof(arr) is 8 because arr is actually a pointer (8 bytes on a 64-bit system), not an array.
This is why you almost always see two parameters when a function accepts a C-style array: the array itself and a separate int size so the function knows how many elements to process.
main computes the count before passing both the array and the count into printAll. The function couldn't have figured out the size on its own. For now, remember: pass an array to a function, lose the size.
Passing an array to a function is O(1). The function receives a pointer (one machine word), not a copy of every element. That's efficient, but it's also why the size has to travel separately.
C-style arrays do no bounds checking. If you write stock[10] on a size-5 array, the compiler will not stop you, the program will not throw an exception, and on most systems the program will not even crash right away. It will read or write some memory that doesn't belong to the array, and what happens next is undefined.
A possible output (this varies, which is the point):
Or maybe stock[5] prints 21856. Or maybe the program crashes. Or maybe it appears to work today and crashes tomorrow on a different machine. Undefined behavior means the C++ standard says nothing about what should happen, and that's not just legal-speak. Real compilers exploit this for optimization, and real programs break in baffling ways because of it.
Two specific shapes of this bug are by far the most common:
Off-by-one in a loop. Looping from 0 to size (inclusive) instead of 0 to size - 1:
That loop reads stock[0] through stock[5]. The last read is past the end. The fix is i < 5, not i <= 5. The rule of thumb is that for an array of size N, the valid indices are 0 to N - 1, and a for loop should use i < N as its condition.
Hard-coded indices that drift from the declared size. You declare a size-5 array, use it for a while, and then the array grows to size 6 but a stray stock[5] = 0; shows up in an unrelated function:
The compiler can't catch this because index 5 is valid for a size-6 array. The bug is logical, not syntactic.
What's wrong with this code?
Two problems sit in the loop. The starting index is 1, which skips reviewScores[0]. The ending condition is i <= 3, which reaches reviewScores[3], one past the end. So the code misses a valid element and reads garbage from out-of-bounds memory.
Fix:
Now the loop starts at 0 and stops before reaching 3. The fix is mechanical, and it's the same fix one applies most of the time you write a loop over a raw array: start at 0, end with < (not <=) against the size.
A modern alternative that side-steps the off-by-one entirely is the range-based for loop, which iterates over the array's elements directly without exposing the indices.
Tying the pieces together, a small program that tracks one week of sales for a single product, then prints the values back along with the total. It uses inferred size, full initialization, indexing, and the sizeof trick.
A few things to notice. The array's size (7) isn't written anywhere in the declaration; the compiler counted the seven values in the braces. The sizeof trick recovers the count later when the program wants to report it. Reading dailySales[6] reaches the last (Sunday) element, since the array goes from index 0 to index 6. The update dailySales[3] = dailySales[3] + 5 reads the existing value and stores a new one in the same slot.
The manual sum at the bottom is awkward, and that's the natural lead-in to the next chapter. Adding seven elements one by one is fine. Adding a hundred would be unbearable. The next chapter introduces loops over arrays, which collapse that kind of work into a few lines no matter how big the array is.
10 quizzes