C-Style Arrays

A detailed guide to working with classic C-style arrays within C++, and their many problems
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

a64.jpg
Ryan McCombe
Ryan McCombe
Edited

At this point, we’ve covered how to create both dynamic arrays using std::vector, and statically-sized arrays using std::array.

However, there is another, older way to create arrays in C++. These are commonly called C-style arrays.

Where possible, we should avoid using them. They have many problems, and this lesson will cover some of them. However, they are a fundamental, built-in part of the language, so it’s important we understand them. They crop up all the time when integrating with other APIs and libraries, and we’ll see them constantly being used when we’re researching new topics.

Creating C-Style Arrays

C-style arrays are built right into the language - no #include directives are needed.

To create a C-style array that can contain 5 integers, we would do this:

int MyArray[5];

We can provide initial values at the same time:

int MyArray[5]{1, 2, 3, 4, 5};

We can also let the compiler deduce the length of the array when we’re providing initial values:

// Will have a length of 5
int MyArray[]{1, 2, 3, 4, 5};

Accessing Members of C-Style Arrays

Similar to std::vector and std::array, access to array elements is available through the [] syntax:

int main(){
  int MyArray[]{1, 2, 3, 4, 5};

  int FirstElement{MyArray[0]};
  MyArray[1] = 100;
  MyArray[4] = 200;
}

Ensuring the index is within range is the responsibility of the developer. There is no equivalent to the at() method.

Iterating over C-Style Arrays

The standard way we’d iterate over all elements of a C-style array in any of the usual ways. Normally, we’ll use a range-based for loop:

#include <iostream>

int main(){
  int MyArray[]{1, 2, 3, 4, 5};

  for (auto i : MyArray) { std::cout << i; }
}
12345

We cover range-based for loops in more detail a little later in this course.

Getting the Size or Length of a C-style Array

The sizeof() function will return how many bytes an expression or type is using in memory.

#include <iostream>

int main(){
  int SomeArray[]{1, 2, 3, 4, 5};

  std::cout << "Size: " << sizeof(SomeArray);
}
Size: 20

In this case, the array is consuming 20 bytes. This is because it has 5 integers and, on the environment this code was run on, an int is 4 bytes.

If we lose track of how many items are in our array, we can use some sizeof() arithmetic to find out:

#include <iostream>

int main(){
  int SomeArray[]{1, 2, 3, 4, 5};

  std::cout
    << "Length: "
    << sizeof(SomeArray) / sizeof(int);
}
Length: 5

More commonly, if we want to refer to the length of the array, we would simply extract it out as a constexpr variable, which we can use to initialize the array, and then reuse elsewhere as needed:

#include <iostream>

int main(){
  constexpr std::size_t Length{5};
  int SomeArray[Length]{1, 2, 3, 4, 5};

  std::cout
    << "Length: " << Length;
}
Length: 5

C-Style Arrays Decaying to Pointers

A frustrating quirk with C-style arrays is their tendency to lose track of their size. This happens when passing an array to a function, for example:

#include <iostream>

void SomeFunc(int Array[]){
  std::cout << "\nThe size is now "
    << sizeof(Array) << "?!";
}

int main(){
  int Array[]{1, 2, 3, 4, 5};
  std::cout << "The size is " << sizeof(Array);
  SomeFunc(Array);
}
The size is 20
The size is now 8?!

This behavior is referred to as decaying to a pointer. The 8 in this output is the size of a pointer (8 bytes) in the environment the code was run.

Specifically, the array has decayed to a pointer to the first element of the array. We can safely access the first element by dereferencing the pointer, as a C-style array can’t have a length of 0. But without knowing how many more elements are in the array, we can’t do much else. For example, attempting to iterate over the array using a range-based for loop will throw a compilation error.

To counter this, we need to keep track of the array’s length separately, passing it around to anywhere it is needed. That could mean, for example, adding additional parameters to functions that receive C-style arrays.

void SomeFunc(int Array[], std::size_t Length);

We still can’t use a range-based for loop, but at least we now have the information we need to create a standard for loop:

#include <iostream>

void SomeFunc(int Array[], std::size_t Length){
  for (std::size_t i{0}; i < Length; ++i) {
    std::cout << Array[i];
  }
}

int main(){
  int Array[]{1, 2, 3, 4, 5};
  SomeFunc(Array, 5);
}
12345

Resizing C-Style Arrays

C-style arrays are statically sized. The size must be known at compile time

If we need to “resize” a C-style array, we do that manually by allocating a new array and then copying the existing elements over to the new memory location.

The std::memcpy() function can help us with this. It copies bytes from one memory location to another. It accepts three arguments:

  • A pointer to the destination where we want things to be copied to
  • A pointer to the source we want things to be copied from
  • The number of bytes to copy

If our array hasn’t decayed to a pointer, we can get the number of bytes using the sizeof() function:

#include <iostream>

int main(){
  int SmallArray[]{1, 2, 3, 4, 5};
  int BigArray[6];

  std::memcpy(BigArray, SmallArray,
              sizeof(SmallArray));

  BigArray[5] = 6;

  for (auto i : BigArray) { std::cout << i; }
}
123456

If it has decayed to a pointer, we can calculate the number of bytes in memory by multiplying the array length by the sizeof() the data type it contains:

#include <iostream>

void SomeFunction(int SmallArray[],
                  std::size_t Length){
  int BigArray[6];

  std::memcpy(BigArray, SmallArray,
              Length * sizeof(int));

  BigArray[5] = 6;

  for (auto i : BigArray) { std::cout << i; }
}

int main(){
  int SomeArray[]{1, 2, 3, 4, 5};
  SomeFunction(SomeArray, 5);
}
123456

More Reasons to Avoid C-Style Arrays

Above, we’ve already highlighted some problems with C-style arrays, compared to standard library containers such as std::array and std::vector:

  • They can not perform bounds checking on the indexes we pass to the [] operator
  • Resizing an array is something we have to do manually, whilst a std::vector comes with that capability built-in
  • They forget their own size, requiring us to introduce additional code to keep track

There are yet more advantages of std::array and std::vector. This includes their native support for concepts such as iterators and ranges, and their compatibility with standardized algorithms which are all things we will cover later in this course***.***

Because of these factors, we should almost always avoid C-style arrays. Often, it’s unavoidable - we’ll be working with third-party libraries or platforms that provide C-style arrays, or expect us to provide C-style arrays as function arguments.

But, in general, we should avoid creating C-style arrays as much as possible.

Creating std::vector and std::array containers from C-style arrays

When we have a C-style array, we may want to convert it to one of the friendlier standard library containers, such as a vector or an array.

Both of those containers have a constructor that accepts two iterators and will copy everything from the first pointer to the second pointer into their container.

Iterators can be created from pointers, so the following example demonstrates how we can create a std::vector using the contents of a C-style array, by passing two pointers:

#include <iostream>
#include <vector>

int main(){
  int Values[]{1, 2, 3, 4, 5};
  std::vector<int> Vec{Values, Values + 5};
  std::cout << "Vector Size: " << Vec.size();
}
Vector Size: 5

However, copying every object in an array can be an expensive operation, so we should only do it sparingly.

The next lesson introduces spans, which allow us to bypass many of the problems of a C-style array, without the performance cost of converting it to a totally different type of container.

Was this lesson useful?

Edit History

  • — Added section on converting C-style arrays to standard library containers

  • — First Published

Ryan McCombe
Ryan McCombe
Edited
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Arrays and Linked Lists
7a.jpg
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access!

This course includes:

  • 106 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Array Views using std::span

A detailed guide to creating a "view" of an array using std::span, and why we would want to
3D Concept Art
Contact|Privacy Policy|Terms of Use
Copyright © 2023 - All Rights Reserved