Question
How to Use C Arrays in C++: Syntax, Initialization, and Safety
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.
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:
values[] = {10, 20, 30}creates an array with three elements. C++ deduces its size from the initializer.totalstarts at0.- At
index == 0,values[0]is10;totalbecomes10. - At
index == 1,values[1]is20;totalbecomes30. - At
index == 2,values[2]is30;totalbecomes60. indexbecomes3, soindex < 3is false and the loop stops.- 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*orunsigned 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::arrayis 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);
}
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
| Feature | C-style array int a[3] | std::array<int, 3> | std::vector<int> |
|---|---|---|---|
| Size | Fixed | Fixed | Can change at runtime |
| Knows its size | Only in its original scope | Yes, with .size() | Yes, with .size() |
| Assignable | No | Yes | Yes |
| Passed to functions | Usually decays to pointer | Retains type and size | Retains object and size |
| Bounds-checked access | No | .at() is checked | .at() is checked |
| Best use | C API or legacy interoperability | New fixed-size data | New 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.
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 withnew[]; preferstd::vectorinstead. - Prefer
std::arrayandstd::vectorfor 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.
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.
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.