Search⌘ K
AI Features

Arrays and Contiguous Data

Explore how to declare and initialize arrays in C++, understand contiguous memory layout, and learn safe ways to access and iterate over array elements. This lesson guides you through managing fixed-size collections, recognizing limitations, and using modern features like std::size and range-based for loops for efficient and safer data handling.

In previous chapters, we covered the basic building blocks of program logic. We learned how to:

  • Store data in memory using variables.

  • Use if/else and switch constructs to control execution flow.

  • Write loops to repeat actions.

  • Package a set of instructions into reusable function(s).

However, we hit a bottleneck when handling large sets of related data. Suppose, if we were building a weather application, we might need to store the temperature recorded each day for a month. Using thirty variables (e.g., temp1, temp2, temp3, and so on) to store this information is not a scalable approach. We need a way to store a collection of values under a single name. This brings us to the “arrays”.

How to declare an array in C++

Learning how to declare an array in C++ is the first step toward managing large datasets. An array is a collection of elements of the same type, stored sequentially in memory. When you declare an array, you must tell the compiler two things: the type and the number of elements.

Syntax and constraints

The syntax for declaring an array in C++ is straightforward:

type name[size];

The type can be any fundamental type (like int, double, bool) or a user-defined types (which we will cover later). The size represents the capacity of the array.

There is a critical constraint here: the size of an array must be known at compile time. The compiler needs to know exactly how much memory to set aside for this variable before the program ever runs.
We cannot use a runtime variable (like a user input) to set the size of an empty array. We typically use an integer literal or a const variable for the size.

Visualizing array memory

When we write int scores[5];, the compiler allocates one continuous block of memory. If a single int occupies 4 bytes, the array uses exactly 20 bytes in total (5 elements × 4 bytes). This memory block contains only the integer values themselves. There is no stored size information, no header, no metadata, and no padding between elements. Each value is placed directly next to the next, forming a tightly packed sequence of data.

A visualization of an array in memory
A visualization of an array in memory

This simplicity is what makes arrays extremely fast to access, since the computer can calculate the location of any element directly. At the same time, this design comes with a trade-off: arrays do not store their own size at runtime, so we must track that information ourselves and use them carefully to avoid errors.

Look at the code below.

C++ 23
#include <iostream>
int main()
{
double monthlyTemperatures[30]; // Valid: Allocates space for 30 integers
// 2. Const size (Modern Best Practice)
// Using a named constant makes the code readable and maintainable.
const int days = 7;
double dailyTemperatures[days]; // Valid: Size is fixed at compile time
// 3. Invalid Declaration
int size = 10;
// int myBadArray[userSize]; // ERROR: 'userSize' is not a compile-time constant
return 0;
}

Let’s understand this step by step:

  • Line 5: We declare monthlyTemperatures with a literal size of 30. The compiler reserves memory for 30 integers, one after the other, immediately.

  • Line 9: We define days as a const. This guarantees the value is fixed and available at compile time.

  • Line 10: We use that constant to size dailyTemperatures. This is better than using "magic numbers" like 7 directly in the code, as it adds meaning to the value.

  • Line 13: Here, size is a regular variable. Even though we set it to 10, the compiler treats it as something that could change. Therefore, it cannot be used to set a capacity for an array. Try uncommenting line 14 to see the compilation error.

Techniques to initialize arrays

Declaring an array reserves memory for its elements, but it does not automatically assign them any meaningful values. If an array is declared without initialization, its elements contain indeterminate values—whatever bit patterns happened to already exist at those memory locations. Reading or using these values results in undefined behavior.

To avoid this, C++ provides several ways to initialize arrays safely so that every element starts with a well-defined value.

Zero initialization

If we want to start with a clean slate, we can use an empty set of braces {}. This tells the compiler to set every single element to its default value (0 for integers, 0.0 for doubles, false for bools). This is the safest default habit.

// Safe: All 5 elements are set to 0
int clearBuffer[5] = {};

List initialization

We can also provide a list of values inside curly braces {}. The compiler fills the array slots in order. When initializing an array, the number of values provided must not exceed the declared size of the array. In addition, each value must be compatible with the array’s declared type.

// Fully initialized
int distinctValues[3] = { 10, 20, 30 };

If we provide fewer values than the array size, the compiler uses the provided values for the first slots and zero-initializes the rest, i.e., setting all other values to 0. This is a handy shortcut to set specific headers or starting values while keeping the rest clean.

// Values are 5, 5, 0, 0, 0
int highScores[5] = { 5, 5 };

Size deduction

If we provide an initialization list, we can omit the size inside the []. The compiler will count the elements for us and set the size automatically. This prevents errors where the size and the number of elements don't match.

// Compiler counts 3 elements. Size is implicitly 3.
int axes[] = { 1, 0, -1 };

Accessing elements in an array

We access individual elements in an array using the subscript operator [] and an index. C++ uses zero-based indexing. This means the first element is at index 0, the second is at index 1, and the last element is at index size - 1.

Consider, we have an array score of size 5, initialised as:

int score[5] = {1, 2, 3, 4, 5};
  • Valid indices: 0, 1, 2, 3, 4.

  • Invalid indices: -1, 5, 100.

Indices are used to access values
Indices are used to access values
C++ 23
#include <iostream>
int main() {
int primes[5] = {2, 3, 5, 7, 11};
// READING: Access index 0 (the first element)
std::cout << "First prime: " << primes[0] << "\n";
// WRITING: Modify index 4 (the last element)
primes[4] = 13;
std::cout << "New last prime: " << primes[4] << "\n";
return 0;
}

Let’s break this down step by step:

  • Line 4: We initialize a 5-element array.

  • Line 7: primes[0] retrieves the first value from the array that is 2.

  • Line 10: primes[4] accesses the last element from the array. We overwrite 11 with 13.

  • Line 11: We print the value at primes[4] to verify if data is updated in the memory.

The bounds problem

C++ does not check array bounds, as it prioritizes speed over safety. For instance, if you try to access index [5] of a 5-element array, the compiler will let you do it. The program will calculate the memory address where that element would be and try to read/write there. This is undefined behavior (UB).

You might crash the program (segmentation fault), or worse, you might silently overwrite a different variable that happens to sit next to the array in memory. This can lead to security vulnerabilities and bugs that are difficult to debug.

C++ 23
#include <iostream>
int main() {
int primes[5] = {2, 3, 5, 7, 11};
// DANGER ZONE: Out of bounds access
std::cout << primes[5] << "\n"; // DO NOT DO THIS
return 0;
}

Let’s break this down step by step:

  • Line 4: We initialize a 5-element array.

  • Line 7: primes[5] tries to access the 6th element but it does not exist. This causes undefined behavior in C++ as is out of bounds. Although the code compiles, this access causes undefined behavior and may produce garbage output, crash the program, or appear to work by accident.

How to get the length of an array in C++

A common question for developers is how to get the length of an array in C++. Because C++ arrays are "dumb" (they don't store their own size as a property), you cannot simply call .length() like in other languages.

In modern C++ (C++17 and later), the standard way to get the length of an array is by using std::size() from the <iterator> header.

C++ 23
#include <iostream>
#include <iterator> // Required for std::size
int main() {
int scores[] = {10, 20, 30, 40, 50};
// The modern way to get the length of an array in C++
int length = std::size(scores);
std::cout << "The array has " << length << " elements.";
return 0;
}

Pro Tip: If you are using older C++, you might see the "sizeof" trick: sizeof(scores) / sizeof(scores[0]). However, std::size() is much safer and is the preferred modern method.

Iterating over arrays

Since arrays are sequences, we rarely access elements one by one manually. Instead, we use loops to process the entire collection.

Traditional index loop

Sometimes we need the index itself. This is useful if we want to print "Element 1 is..." or if we need to access a different array at the same position. In these cases, we use a standard for loop and the technique we just covered for how to get the length of an array in C++ to ensure our loop stays within bounds.

Using std::size() (available from the <iterator> header in C++17/20) allows you to retrieve the array’s length safely. This is better than hard-coding a "magic number" that might lead to errors if the array size changes later.

C++ 23
#include <iostream>
#include <iterator> // For std::size
int main() {
double prices[] = { 10.50, 5.99, 3.25, 12.00 };
// Loop runs while i < 4 (indices 0, 1, 2, 3).
for (int i = 0; i < std::size(prices); ++i) {
std::cout << "Item " << i << ": $" << prices[i] << "\n";
}
return 0;
}

Let’s break this down step by step:

  • Line 5: It declares an array of doubles named prices with four elements.

  • Line 8: A traditional for loop is used to iterate over prices. std::size(prices) is used to get the length of the prices array.

  • Line 9: We access the prices values with the subscript operator ([]) one by one.

The range-based for loop

Modern C++ (since C++11) introduced the range-based for loop. This is the safest and cleanest way to iterate if you need to access every element. You don't need to manage indices or worry about bounds; the loop automatically walks from the start to the end.

C++ 23
#include <iostream>
int main() {
int scores[] = {90, 85, 78, 92, 88};
for (int score : scores) {
std::cout << score << ",";
}
std::cout << std::endl;
// Modification in place
for (int& score : scores) {
score = score + 10;
}
for (int score : scores) {
std::cout << score << ",";
}
std::cout << std::endl;
return 0;
}

Let’s break this down into steps:

  • Line 4: The main function begins, and we declare an array of integers named scores with five elements.

  • Line 6-7: This is a range-based for loop. The loop automatically iterates over every element in the scores array, one at a time. On each iteration, the current element’s value is copied into the variable score. We print the current value of score. There is no index involved, so there’s no risk of accessing elements out of bounds.

  • Lines 11-12: for (int& score : scores) uses a reference. This means score refers to the actual value inside the array. Changing score changes the value inside the array.

  • Line 15-16: With this range-based for loop, we can verify that the previous loop changes the values of array in-place.

Limitations of arrays

While arrays are fast and fundamental, they have significant limitations that make them difficult to use for general-purpose programming.

  • Fixed size: Once an array is created, its size is locked in. If you allocate space for 10 users and later need to store an 11th, the array cannot grow on its own. You must create a new, larger array and manually move the existing data; a process that is tedious and error-prone.

  • Loss of size information when passed to functions: When an array is passed to a function, the function no longer knows how many elements the array contains. This means we must separately provide the size ourselves. Forgetting to do so, or passing the wrong size, can easily lead to bugs.

  • No built-in protection against mistakes: Arrays do not automatically prevent you from accessing invalid positions, nor do they provide helpful features like asking how many elements they contain. If you make a mistake, the program may behave unpredictably instead of giving a clear error. Safer alternatives that solve these problems will be introduced later.