Question

How to Use C Arrays in C++: Syntax, Initialization, and Safety

cpparrayspointersmultidimensional-arrayc++-faq

Question

How do C-style arrays work in C++?

C++ inherits built-in arrays from C, and they still appear frequently in legacy code and C library interfaces. However, modern C++ also provides safer, easier-to-use alternatives such as std::vector<T> (since C++98) and std::array<T, N> (since C++11).

This guide explains C-style arrays, including:

  • array types and element access
  • creation and initialization
  • assignment and function parameter passing
  • multidimensional arrays and arrays of pointers
  • common pitfalls

In this context, array means a built-in C-style array, not std::array.

Short Answer

You will learn how to declare, initialize, access, and pass C-style arrays in C++. You will also see why array bounds and size information require special care, when dynamic arrays are needed, and when std::array or std::vector is the better choice.

Concept

A C-style array is a fixed-size sequence of values of the same type stored next to each other in memory.

int scores[3] = {90, 75, 88};

This creates three int elements. Their indexes are 0, 1, and 2:

scores[0] = 90;
scores[1] = 75;
scores[2] = 88;

C-style arrays matter because many older C++ codebases and C APIs use them. They are efficient and have simple memory layouts, but they are also easy to misuse:

  • Their size is often lost when passed to a function.
  • They do not check index bounds.
  • They cannot be assigned or returned like ordinary values.
  • A local array must have a size known at compile time.

For new application code, prefer std::array for fixed-size collections and std::vector for collections whose size can change.

Mental Model

Think of an array as a row of numbered lockers.

scores: [ 90 ][ 75 ][ 88 ]
index:     0     1     2

Every locker holds the same kind of item, and the number on a locker is its index. Asking for scores[1] opens the second locker.

The important rule is that the row has a fixed number of lockers. If it has three lockers, valid indexes are only 0, 1, and 2. Trying scores[3] is like reaching past the end of the row: C++ does not stop you, but the result is undefined behavior.

Take Quiz

Syntax and Examples

Declare an array by writing the element type, its name, and the number of elements in square brackets:

int temperatures[7];

Initialize it immediately when possible:

int temperatures[7] = {18, 20, 21, 19, 17, 16, 18};

Use an index to read or change an element:

#include <iostream>

int main() {
    int temperatures[3] = {18, 20, 21};

    temperatures[1] = 22;

    std::cout << temperatures[0] << '\n'; // 18
    std::cout << temperatures[1] << '\n'; // 22
}

Indexes begin at zero. Therefore, an array declared as int values[3] has valid indexes from 0 through 2.

Initialization forms

int a[3] = {1, 2, 3};  // All elements supplied
int b[3] = {1};        // {1, 0, 0}; remaining elements are zero-initialized
int c[] = {4, 5, 6};   // Compiler deduces a size of 3
int d[3] = {};         // {0, 0, 0}

For character arrays, a string literal includes a terminating null character ('\0'):

char name[] = "Ada";  // {'A', 'd', 'a', '\0'}: size is 4

Step by Step Execution

Consider this program:

#include <iostream>

int main() {
    int values[] = {10, 20, 30};
    int total = 0;

    for (int index = 0; index < 3; ++index) {
        total += values[index];
    }

    std::cout << total << '\n';
}

Execution proceeds as follows:

  1. values[] = {10, 20, 30} creates an array with three elements. C++ deduces its size from the initializer.
  2. total starts at 0.
  3. At index == 0, values[0] is 10; total becomes 10.
  4. At index == 1, values[1] is 20; total becomes 30.
  5. At index == 2, values[2] is 30; total becomes 60.
  6. index becomes 3, so index < 3 is false and the loop stops.
  7. The program prints 60.

A range-based loop is often clearer when you only need each element:

for (int value : values) {
    std::cout << value << '\n';
}

Real World Use Cases

C-style arrays are still useful or unavoidable in several situations:

  • C library APIs: A library may expect a pointer to bytes, such as const char* or unsigned char*.
  • Fixed hardware buffers: Embedded software may use a known-size buffer for sensor samples or packets.
  • Interoperability: File, graphics, networking, and operating-system APIs often use pointer-and-length interfaces.
  • Legacy code maintenance: Existing projects may use built-in arrays extensively.
  • Compile-time fixed data: A small lookup table with a permanently fixed size can be represented by an array, though std::array is usually preferable in new code.

For example, a function that receives bytes from an external API may accept a pointer and a length:

void sendBytes(const unsigned char* data, std::size_t length);

int main() {
    unsigned char packet[] = {0x01, 0xA0, 0xFF};
    sendBytes(packet, 3);
}
Take Quiz

Real Codebase Usage

In real C++ projects, developers usually isolate C-style arrays at boundaries and use safer containers inside the application.

Passing an array with its size

When an array is passed to a function, it usually becomes a pointer. Pass the number of elements separately:

#include <cstddef>
#include <iostream>

void printValues(const int values[], std::size_t count) {
    for (std::size_t index = 0; index < count; ++index) {
        std::cout << values[index] << '\n';
    }
}

int main() {
    int values[] = {4, 8, 15, 16};
    printValues(values, 4);
}

Preserving the size with an array reference

A template can accept only a real array and retain its compile-time size:

#include <cstddef>

template <typename T, std::size_t N>
void clear(T (&values)[N]) {
    for (std::size_t index = 0; index < N; ++index) {
        values[index] = T{};
    }
}

Prefer RAII for dynamically sized storage

Avoid manually pairing new[] with delete[]. std::vector manages memory automatically:

#include <vector>

std::vector<int> readMeasurements(std::size_t count) {
    return std::vector<int>(count, 0);
}

This avoids leaks if an exception occurs and keeps the size available through .size().

Common Mistakes

Accessing outside the array

This code is broken because a three-element array ends at index 2:

int values[3] = {1, 2, 3};
values[3] = 4; // Undefined behavior

Avoid this by using a correct loop condition, such as index < 3, never index <= 3.

Assuming an array knows its size inside a function

#include <cstddef>

void process(int values[]) {
    std::size_t count = sizeof(values) / sizeof(values[0]); // Wrong
}

In a function parameter, values is treated as a pointer, so sizeof(values) is the size of a pointer, not the array. Pass the count, use an array-reference template, or use std::array/std::vector.

Assigning one built-in array to another

int source[3] = {1, 2, 3};
int destination[3];
destination = source; // Compilation error

Copy elements with a loop, or use std::copy:

#include <algorithm>

std::copy(source, source + 3, destination);

Using delete instead of delete[]

int* values = new int[10];
delete values; // Wrong: undefined behavior
delete[] values; // Required if new[] was used

Prefer std::vector<int> values(10); instead, which requires no manual deletion.

Confusing a character array with a C++ string

char word[3] = "cat"; // Error: no room for the terminating '\0'

Use char word[4] = "cat";, char word[] = "cat";, or preferably std::string word = "cat";.

Comparisons

FeatureC-style array int a[3]std::array<int, 3>std::vector<int>
SizeFixedFixedCan change at runtime
Knows its sizeOnly in its original scopeYes, with .size()Yes, with .size()
AssignableNoYesYes
Passed to functionsUsually decays to pointerRetains type and sizeRetains object and size
Bounds-checked accessNo.at() is checked.at() is checked
Best useC API or legacy interoperabilityNew fixed-size dataNew variable-size data

Array versus pointer

An array is a collection of elements. A pointer is a variable that stores an address. In many expressions, an array converts (or decays) to a pointer to its first element:

int values[3] = {1, 2, 3};
int* first = values; // Equivalent to &values[0]

That conversion is why a function usually cannot determine the original array length from its parameter.

Take Quiz

Cheat Sheet

// Declaration
int values[3];

// Initialization
int values[3] = {1, 2, 3};
int values[] = {1, 2, 3}; // Size deduced
int values[3] = {};       // All zero

// Access: valid indexes are 0 through size - 1
values[0] = 42;
int first = values[0];

// Loop through a known-size array
for (int index = 0; index < 3; ++index) {
    // values[index]
}

// Range-based loop
for (int value : values) {
    // use value
}

// Array size, only where values is still an actual array
std::size_t count = sizeof(values) / sizeof(values[0]);

// Function parameter: pass size separately
void process(const int values[], std::size_t count);

Rules to remember:

  • Array indexes start at 0.
  • Built-in arrays have no automatic bounds checking.
  • Arrays cannot be assigned with = after declaration.
  • Array parameters usually decay to pointers.
  • Use delete[] only for memory created with new[]; prefer std::vector instead.
  • Prefer std::array and std::vector for new code unless a C-style array is required.

FAQ

What is a C-style array in C++?

It is a built-in fixed-size sequence, declared with square brackets, such as int values[5];.

Are C-style arrays still used in modern C++?

Yes, especially in legacy code, embedded code, and C library interfaces. For most new C++ code, std::array and std::vector are safer choices.

How do I find the size of a C-style array?

In the scope where it is declared, use sizeof(values) / sizeof(values[0]). Do not use this after passing the array to a normal function parameter, because it has decayed to a pointer.

Why does an array become a pointer in a function call?

C++ function parameters written as int values[] are adjusted to int* values. The function receives an address, not the complete array type and size.

Can I return a C-style array from a function?

Not directly by value. Return std::array, std::vector, or another object that owns the data instead.

Can I assign one C-style array to another?

No. Copy individual elements with a loop or std::copy, or use std::array or std::vector, which support assignment.

Should I use new[] to create an array with a runtime size?

Usually no. Use std::vector<T> because it manages allocation and cleanup automatically.

What happens when I access an invalid array index?

The behavior is undefined. The program may appear to work, produce incorrect results, crash, or corrupt memory.

Take Quiz

Mini Project

Description

Create a small weekly temperature report using a fixed-size C-style array. The project practices initialization, indexed access, loops, passing an array with its size, and safe handling of a fixed collection.

Goal

Calculate and display the minimum, maximum, and average values stored in a C-style array.

Requirements

Create a C-style array containing seven daily temperatures. Write a function that receives the array and its element count. Calculate the minimum, maximum, and average temperature. Print a readable report. Do not access elements outside the supplied count.

Take Quiz

Keep learning

Related questions

View more