Ch6.9: C-Style Array Parameters
Overview
In C++, arrays behave differently from most other types. When you pass an array to a function, the array decays into a pointer to its first element. This means that functions cannot know the size of the array unless you pass it explicitly.
In this chapter, you will learn:
- how arrays decay to pointers
- why
int const arr[]is exactlyint const* arr - why you should avoid the misleading
[]syntax in parameters - how to correctly declare functions that take arrays
- why array size information is lost
- how multidimensional arrays work
- how pointer arithmetic relates to array indexing
- why C-style array parameters are unsafe
- why
::fast_io::arrayand::fast_io::vectorare safer alternatives
1. Arrays decay to pointers
When you pass an array to a function, it automatically converts (decays) into a pointer to its first element.
void print_first(int const* p)
{
::fast_io::println("first element = ", *p);
}
int main()
{
int arr[3]{10, 20, 30};
print_first(arr); // arr decays to int*
}
Inside print_first, the function has no idea how large the
original array was.
2. Do not let [] fool you
In a function parameter list, the syntax int arr[] or
int const arr[] does not mean “an array is
passed.” It is simply another way of writing a pointer.
These three declarations are 100% identical:
void f(int const arr[]);
void f(int const arr[10]);
void f(int const* arr);
All three mean:
“f takes a pointer to const int.”
The brackets in a function parameter list do not preserve array size and do not change the type. They exist only for readability.
In this tutorial, we will avoid the misleading
arr[] syntax in real code, but we will still show it in examples
when teaching pitfalls—because you will see it in the wild.
3. Array size is lost
Because arrays decay to pointers, the function cannot know the array size.
This example intentionally uses int const arr[] to demonstrate
the trap:
void print_size(int const arr[])
{
::fast_io::println(sizeof(arr));
// prints sizeof(int*) — NOT the size of the array!
}
This is why you should not use arr[] in real code.
You must pass the size explicitly:
void print_all(int const* arr, std::size_t n)
{
for(std::size_t i{}; i != n; ++i)
::fast_io::println(arr[i]);
}
4. Multidimensional arrays
Only the first dimension decays. The remaining dimensions must be known.
void print_matrix(int const (*arr)[4], std::size_t rows)
{
for(std::size_t r{}; r != rows; ++r)
for(std::size_t c{}; c != 4; ++c)
::fast_io::print(arr[r][c], ' ');
}
This is valid because the compiler knows the size of each row.
But this is invalid:
void bad(int const arr[][]); // ❌ error — second dimension unknown
5. Pointer arithmetic
Inside a function, array indexing is just pointer arithmetic:
void demo(int const* p)
{
::fast_io::println(p[2]); // same as *(p + 2)
}
This is why C-style array parameters are powerful but also dangerous.
6. Const correctness
If the function should not modify the array, use int const*.
void print_all(int const* arr, std::size_t n);
7. Why C-style array parameters are unsafe
C-style array parameters have several problems:
- the size is lost
- no bounds checking
- no ownership information
- easy to accidentally overflow
- easy to mismatch pointer types
For these reasons, modern C++ code avoids raw array parameters whenever possible.
8. Prefer ::fast_io::array and ::fast_io::vector
The fast_io library provides safer, less verbose containers that
can be passed into or returned from functions easily.
Fixed-size arrays
void process(::fast_io::array<int, 4> arr)
{
for(auto e : arr)
::fast_io::println(e);
}
Dynamic-size arrays
void process(::fast_io::vector<int> v)
{
for(auto e : v)
::fast_io::println(e);
}
Returning a vector
::fast_io::vector<::std::size_t> make_sequence(std::size_t n)
{
::fast_io::vector<::std::size_t> v;
for(std::size_t i{}; i != n; ++i)
v.push_back(i);
return v; // efficient: NRVO + move semantics
}
Passing a vector by const reference
void process(::fast_io::vector<int> const& v)
{
for(auto e : v)
::fast_io::println(e);
}
These containers:
- carry their size with them
- are easy to pass by value or reference
- avoid pointer decay
- avoid ODR issues caused by standard library containers
- are header-only and consistent across translation units
9. Cost of passing by value vs passing by reference
Understanding the cost of parameter passing helps you write efficient code.
| Type | Pass by value | Pass by reference |
|---|---|---|
int const arr[] / int const* arr |
cheap (pointer copy) | cheap (reference copy) |
::fast_io::vector<T> |
cheap (3-pointer struct) | cheapest |
::fast_io::array<T, N> |
copies all N elements | cheap |
::fast_io::vector is designed to be cheap to pass around. It
behaves like a small struct containing three pointers (begin, end, capacity).
::fast_io::array is fixed-size, so passing it by value copies
all elements. Passing by reference avoids this cost.
Key takeaways
- Arrays decay to pointers when passed to functions.
int const arr[]is exactlyint const* arr.- Array size information is lost; you must pass it explicitly.
- Only the first dimension of a multidimensional array decays.
- Array indexing is pointer arithmetic.
- C-style array parameters are unsafe and error-prone.
::fast_io::arrayand::fast_io::vectorare safer, less verbose alternatives.- Passing
::fast_io::vectorby value is cheap; passing by reference is cheapest.