C++ Module 5 – Arrays, vectors and aggregate types
std::array, C arrays and array decay
See why a C array loses its size when passed to a function, use std::array and std::span instead, and why variable-length arrays are not standard C++.
What you will learn
- Use std::array for fixed-size data
- Explain why a C array loses its size when passed to a function
- Recognise variable-length arrays as non-standard
Before you start
What you’ll learn · Before you start
What you will learn
- Use std::array for fixed-size data
- Explain why a C array loses its size when passed to a function
- Recognise variable-length arrays as non-standard
Before you start
On this page
A C array such as int marks[5] is a fixed number of elements side by side in memory, the kind of array C++
inherited from C. It knows its size only where it is declared. Pass it to a function and the function receives just
a pointer to the first element: the array decays, and its size is lost. It also cannot be assigned or returned.
std::array<int, 5>, from the header <array>, holds exactly the same 20 bytes but behaves like a normal value: it
keeps its size, can be copied, compared and returned, and never turns into a pointer by itself.
So the choice is short. When the number of elements is fixed while you write the program, use std::array. When it
is decided while the program runs, use std::vector. C++ has no third kind: a variable-length array such as
int a[n] is not standard C++, even though one popular compiler accepts it without a word. This lesson shows each
of these with both compilers, Apple clang and GCC 16.
C arrays: elements and nothing else
C++ (c_array.cpp) · c_array.cpp
// C arrays: a fixed number of elements side by side, and nothing else: no size is stored anywhere.
#include <iterator>
#include <print>
int main() {
int marks[5]{71, 64, 88, 92, 57}; // five ints
int zeros[4]{}; // {} sets every element to 0
int days[]{31, 28, 31}; // no number: the size, 3, comes from the list
std::println("sizes {}, {} and {}; marks takes {} bytes", std::size(marks), std::size(zeros), std::size(days), sizeof marks);
std::println("marks {}, zeros {}", marks, zeros);
int total = 0;
for (int m : marks) total += m; // range-for works while the array's size is part of its type
std::println("total {}", total);
} C++ (array_assign.cpp) · array_assign.cpp
// A C array cannot be assigned as a whole.
int main() {
int a[3]{1, 2, 3};
int b[3]{};
b = a;
return b[0];
} from session import run
# c_array.cpp with both compilers, then an assignment of one C array to another, which neither accepts.
run("""
clang++ -std=c++23 -Wall -Wextra c_array.cpp -o c_array
./c_array
g++-16 -std=c++23 -Wall -Wextra c_array.cpp -o c_array-gcc
./c_array-gcc
clang++ -std=c++23 -Wall -Wextra array_assign.cpp -o array_assign
g++-16 -std=c++23 -Wall -Wextra array_assign.cpp -o array_assign-gcc
""") Output
$ clang++ -std=c++23 -Wall -Wextra c_array.cpp -o c_array
$ ./c_array
sizes 5, 4 and 3; marks takes 20 bytes
marks [71, 64, 88, 92, 57], zeros [0, 0, 0, 0]
total 372
$ g++-16 -std=c++23 -Wall -Wextra c_array.cpp -o c_array-gcc
$ ./c_array-gcc
sizes 5, 4 and 3; marks takes 20 bytes
marks [71, 64, 88, 92, 57], zeros [0, 0, 0, 0]
total 372
$ clang++ -std=c++23 -Wall -Wextra array_assign.cpp -o array_assign
array_assign.cpp:5:7: error: array type 'int[3]' is not assignable
5 | b = a;
| ~ ^
1 error generated.
[exit status 1]
$ g++-16 -std=c++23 -Wall -Wextra array_assign.cpp -o array_assign-gcc
array_assign.cpp: In function 'int main()':
array_assign.cpp:5:7: error: invalid array assignment
5 | b = a;
| ~~^~~
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 c_array_session.py
int marks[5]{71, 64, 88, 92, 57};creates fiveintelements. With an empty list,int zeros[4]{};, every element is 0. Without any initialiser, localintelements hold whatever was in that memory, so always write the braces.int days[]{31, 28, 31};leaves out the number, and the compiler counts the list: 3.std::size(marks)gives the number of elements, andsizeof marksthe bytes: 5 × 4 = 20. A C array stores no size of its own; the compiler knows it from the typeint[5], so both work only where that type is visible.- A range-for loop and
std::printlnwork for the same reason. b = a;is an error in both compilers: a C array cannot be assigned, copied as a whole or returned from a function.
Array decay: the size does not reach the function
Almost any use of a C array’s name converts it to a pointer to its first element. A function parameter written as an
array is a pointer too: the standard adjusts a parameter of type “array of T” to “pointer to T”, and a number
inside the brackets is ignored:
What a function receives when you pass it a C array
Text description of the diagram
The diagram shows one C array and two ways to hand it to a function.
- In main, int scores[5] holds 71, 64, 88, 92 and 57 side by side: 20 bytes, and its type, int[5], includes the size.
- A function declared as show(int scores[5]) does not receive the array. The parameter is adjusted to int*, a pointer to the first element, 8 bytes on a 64-bit computer. The 5 in the declaration is ignored, so inside the function nothing records how many elements there are, and sizeof(scores) measures the pointer.
- A function that takes std::span<const int> receives a pointer to the same first element together with the count, 16 bytes in all, so values.size() is still 5.
A std::array, the third option, is passed or returned as a whole value, so its size is never lost.
// A C array passed to a function arrives as a pointer: inside the function, sizeof measures the pointer.
#include <cstddef>
#include <print>
void show(int scores[5]) { // the 5 is ignored: the parameter is really int* scores
std::println("in show: sizeof(scores) = {}", sizeof(scores));
}
int main() {
int scores[5]{71, 64, 88, 92, 57};
std::println("in main: sizeof(scores) = {}, {} elements", sizeof(scores), sizeof(scores) / sizeof(scores[0]));
std::println("in main: std::size(scores) = {}", std::size(scores));
show(scores);
} from session import run
# The sizeof bug: both compilers warn, build the program anyway, and it prints the size of a pointer.
run("""
clang++ -std=c++23 -Wall -Wextra decay.cpp -o decay
./decay
g++-16 -std=c++23 -Wall -Wextra decay.cpp -o decay-gcc
./decay-gcc
""") Output
$ clang++ -std=c++23 -Wall -Wextra decay.cpp -o decay
decay.cpp:6:56: warning: sizeof on array function parameter will return size of 'int *' instead of 'int[5]' [-Wsizeof-array-argument]
6 | std::println("in show: sizeof(scores) = {}", sizeof(scores));
| ^
decay.cpp:5:15: note: declared here
5 | void show(int scores[5]) { // the 5 is ignored: the parameter is really int* scores
| ^
1 warning generated.
$ ./decay
in main: sizeof(scores) = 20, 5 elements
in main: std::size(scores) = 5
in show: sizeof(scores) = 8
$ g++-16 -std=c++23 -Wall -Wextra decay.cpp -o decay-gcc
decay.cpp: In function 'void show(int*)':
decay.cpp:6:57: warning: 'sizeof' on array function parameter 'scores' will return size of 'int*' [-Wsizeof-array-argument]
6 | std::println("in show: sizeof(scores) = {}", sizeof(scores));
| ~^~~~~~~
decay.cpp:5:15: note: declared here
5 | void show(int scores[5]) { // the 5 is ignored: the parameter is really int* scores
| ~~~~^~~~~~~~~
$ ./decay-gcc
in main: sizeof(scores) = 20, 5 elements
in main: std::size(scores) = 5
in show: sizeof(scores) = 8
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 decay_session.py
In main, sizeof(scores) is 20. In show, it is 8, the size of a pointer on a 64-bit computer, although the
parameter says [5]; GCC’s message even names the function as show(int*). The classic way to count elements,
sizeof(a) / sizeof(a[0]), gives 8 / 4 = 2 inside the function: a wrong answer that compiles. Both compilers warn
by default with -Wsizeof-array-argument, so read the warnings. std::size is safer, because it refuses a pointer
outright:
// std::size refuses a pointer, so the decay bug becomes a compile error instead of a wrong answer.
#include <iterator>
int count(int scores[]) {
return static_cast<int>(std::size(scores)); // scores is an int*: it has no size
}
int main() {
int scores[5]{71, 64, 88, 92, 57};
return count(scores);
} from session import run
# std::size on the pointer: an error on both compilers (grep keeps the error lines of the long messages).
run("""
clang++ -std=c++23 -Wall -Wextra size_of_pointer.cpp -o size_of_pointer 2>&1 | grep "error:"
g++-16 -std=c++23 -Wall -Wextra size_of_pointer.cpp -o size_of_pointer-gcc 2>&1 | grep "error:"
""") Output
$ clang++ -std=c++23 -Wall -Wextra size_of_pointer.cpp -o size_of_pointer 2>&1 | grep "error:"
size_of_pointer.cpp:5:29: error: no matching function for call to 'size'
$ g++-16 -std=c++23 -Wall -Wextra size_of_pointer.cpp -o size_of_pointer-gcc 2>&1 | grep "error:"
size_of_pointer.cpp:5:38: error: no matching function for call to 'size(int*&)'
• error: request for member 'size' in '__cont', which is of non-class type 'int* const'
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 size_of_pointer_session.py
std::array: a fixed-size array that behaves like a value
// std::array<T, N>: a fixed-size array that knows its size and behaves like a value.
#include <array>
#include <print>
// Returns the scores with every one raised by bonus: the array is copied in and copied out.
std::array<int, 5> with_bonus(std::array<int, 5> scores, int bonus) {
for (int& s : scores) s += bonus;
return scores;
}
int main() {
std::array<int, 5> scores{71, 64, 88, 92, 57};
std::println("{} elements, {} bytes, {}", scores.size(), sizeof scores, scores);
std::array<int, 5> raised = with_bonus(scores, 5); // copy in, copy out
std::println("raised: {}", raised);
std::println("original: {}", scores); // unchanged: the function had its own copy
std::println("equal? {} less? {}", raised == scores, scores < raised);
auto days = std::to_array({31, 28, 31, 30}); // C++20: the size, 4, is worked out
std::array primes{2, 3, 5, 7, 11}; // C++17: the type and the size are deduced
std::println("days {} ({}), primes {} ({})", days, days.size(), primes, primes.size());
std::array<int, 3> zeros{}; // {} sets every element to 0
std::array<int, 3> garbage; // no initialiser: the ints are not initialised
garbage.fill(-1); // fill() sets every element
std::println("zeros {}, filled {}", zeros, garbage);
std::println("front {}, back {}, at(4) {}", scores.front(), scores.back(), scores.at(4));
} from session import run
run("""
clang++ -std=c++23 -Wall -Wextra std_array.cpp -o std_array
./std_array
g++-16 -std=c++23 -Wall -Wextra std_array.cpp -o std_array-gcc
./std_array-gcc
""") Output
$ clang++ -std=c++23 -Wall -Wextra std_array.cpp -o std_array $ ./std_array 5 elements, 20 bytes, [71, 64, 88, 92, 57] raised: [76, 69, 93, 97, 62] original: [71, 64, 88, 92, 57] equal? false less? true days [31, 28, 31, 30] (4), primes [2, 3, 5, 7, 11] (5) zeros [0, 0, 0], filled [-1, -1, -1] front 71, back 57, at(4) 57 $ g++-16 -std=c++23 -Wall -Wextra std_array.cpp -o std_array-gcc $ ./std_array-gcc 5 elements, 20 bytes, [71, 64, 88, 92, 57] raised: [76, 69, 93, 97, 62] original: [71, 64, 88, 92, 57] equal? false less? true days [31, 28, 31, 30] (4), primes [2, 3, 5, 7, 11] (5) zeros [0, 0, 0], filled [-1, -1, -1] front 71, back 57, at(4) 57
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 std_array_session.py
- The size is part of the type.
std::array<int, 5>always has five elements, andscores.size()says so anywhere, inside functions too. It is 20 bytes, exactly likeint[5]: the class only wraps the C array, so it costs nothing. - It is a value.
with_bonusreceived its own copy, changed it and returned it, whilescoresstayed as it was.==compares every element, and<compares element by element like words in a dictionary. - Two ways to let the compiler count.
std::to_array({31, 28, 31, 30})(C++20) works out the size, 4, andstd::array primes{2, 3, 5, 7, 11}(C++17) deduces both the element type and the size. - Initialise it.
std::array<int, 3> zeros{};sets every element to 0. Without the braces, theintelements start with unknown values, just as in a C array, untilfill(-1)or an assignment gives them one. front(),back()andat(i)work as they do forstd::vector, andatthrowsstd::out_of_rangefor a bad index.
Copying a large std::array into a function costs time, as copying any large value does. Pass it as
const std::array<int, 5>& when the function only reads it, and by value, as with_bonus does, when the function
needs its own copy anyway.
Passing arrays to functions without losing the size
A function that should accept “some ints”, from a C array, a std::array or a std::vector, takes a
std::span (C++20, header <span>). A span is a view: a pointer to the first element and the number of
elements, so the size travels with it. It does not copy or own the elements, and a later lesson covers it in depth:
// std::span (C++20) passes "some ints" without losing their number: it holds a pointer and a count.
#include <array>
#include <cstddef>
#include <print>
#include <span>
#include <vector>
double average(std::span<const int> values) {
if (values.empty()) return 0.0;
int sum = 0;
for (int v : values) sum += v;
return static_cast<double>(sum) / static_cast<double>(values.size());
}
int main() {
int c_array[]{71, 64, 88, 92, 57};
std::array<int, 3> fixed{60, 70, 80};
std::vector<int> growing{1, 2, 3, 4};
std::println("{} {} {}", average(c_array), average(fixed), average(growing)); // no sizes passed
std::println("first three marks: {}", average(std::span(c_array).first(3))); // a part of an array
std::println("a span is {} bytes: a pointer and a count", sizeof(std::span<const int>));
} from session import run
run("""
clang++ -std=c++23 -Wall -Wextra span.cpp -o span
./span
g++-16 -std=c++23 -Wall -Wextra span.cpp -o span-gcc
./span-gcc
""") Output
$ clang++ -std=c++23 -Wall -Wextra span.cpp -o span $ ./span 74.4 70 2.5 first three marks: 74.33333333333333 a span is 16 bytes: a pointer and a count $ g++-16 -std=c++23 -Wall -Wextra span.cpp -o span-gcc $ ./span-gcc 74.4 70 2.5 first three marks: 74.33333333333333 a span is 16 bytes: a pointer and a count
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 span_session.py
average was called with three different kinds of array and no sizes, and it was right every time. first(3)
made a span of the first three elements. A span is 16 bytes, a pointer and a count, so passing it by value is cheap.
The C++ Core Guidelines give the same advice in their rule R.14: avoid array parameters, prefer span.
| Criterion | C array | std::array | std::vector |
|---|---|---|---|
| Size | Fixed | Fixed | Can change |
| size() | No | Yes | Yes |
| Copy and return | No | Yes | Yes |
| As a parameter | Becomes a pointer | Stays an array | Stays a vector |
| Elements | Where declared | Where declared | On the heap |
| When to choose | C interfaces | Small fixed data | Everything else |
Variable-length arrays are not C++
C allows an array whose size is a variable, int squares[n]. Standard C++ does not: the bound of an array must be a
constant expression, a value known while compiling. The compilers disagree about how loudly to say so:
// A variable-length array: the size is known only when the program runs.
#include <iostream>
#include <print>
int main() {
int n = 0;
std::cin >> n;
int squares[n]; // not standard C++: n is not a constant
for (int i = 0; i < n; ++i) squares[i] = i * i;
std::println("the last of {} squares is {}", n, squares[n - 1]);
} from session import run
# One variable-length array, four builds: Clang warns by default, GCC accepts it silently unless asked to be
# pedantic, and -Werror=vla turns it into an error on both.
run("""
clang++ -std=c++23 vla.cpp -o vla
g++-16 -std=c++23 -Wall -Wextra vla.cpp -o vla-gcc
echo 5 | ./vla-gcc
g++-16 -std=c++23 -pedantic vla.cpp -o vla-gcc
clang++ -std=c++23 -Werror=vla vla.cpp -o vla
g++-16 -std=c++23 -Werror=vla vla.cpp -o vla-gcc
""") Output
$ clang++ -std=c++23 vla.cpp -o vla
vla.cpp:8:17: warning: variable length arrays in C++ are a Clang extension [-Wvla-cxx-extension]
8 | int squares[n]; // not standard C++: n is not a constant
| ^
vla.cpp:8:17: note: read of non-const variable 'n' is not allowed in a constant expression
vla.cpp:6:9: note: declared here
6 | int n = 0;
| ^
1 warning generated.
$ g++-16 -std=c++23 -Wall -Wextra vla.cpp -o vla-gcc
$ echo 5 | ./vla-gcc
the last of 5 squares is 16
$ g++-16 -std=c++23 -pedantic vla.cpp -o vla-gcc
vla.cpp: In function 'int main()':
vla.cpp:8:9: warning: ISO C++ forbids variable length array 'squares' [-Wvla]
8 | int squares[n]; // not standard C++: n is not a constant
| ^~~~~~~
$ clang++ -std=c++23 -Werror=vla vla.cpp -o vla
vla.cpp:8:17: error: variable length arrays in C++ are a Clang extension [-Werror,-Wvla-cxx-extension]
8 | int squares[n]; // not standard C++: n is not a constant
| ^
vla.cpp:8:17: note: read of non-const variable 'n' is not allowed in a constant expression
vla.cpp:6:9: note: declared here
6 | int n = 0;
| ^
1 error generated.
[exit status 1]
$ g++-16 -std=c++23 -Werror=vla vla.cpp -o vla-gcc
vla.cpp: In function 'int main()':
vla.cpp:8:9: error: variable length array 'squares' is used [-Werror=vla]
8 | int squares[n]; // not standard C++: n is not a constant
| ^~~~~~~
cc1plus: some warnings being treated as errors
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 vla_session.py
- Apple clang accepts it as a Clang extension and warns by default (
-Wvla-cxx-extension). - GCC accepts it silently, even with
-Wall -Wextra, and the program runs. Only-pedanticor-Wvlamakes it warn. The GCC manual lists variable-length arrays in C++ as a GCC extension. - Microsoft’s compiler rejects it. Its documentation says the number of elements must be a literal or a constant expression, and a bound that is not constant gives error C2131, “expression did not evaluate to a constant”.
So code with a variable-length array builds on a Mac and on Linux and then fails on Windows, and its stack use grows
with input that you do not control. -Werror=vla turns the extension into an error on both GCC and Clang, which keeps
it out of a project. The standard replacement is std::vector<int> squares(n);.
Big arrays and the stack
A local array lives in the function’s stack frame, and the stack is small:
C++ (stack_sizes.cpp) · stack_sizes.cpp
// How much stack the main thread and a new thread get on macOS (pthread_get_stacksize_np is a macOS function;
// on Linux, `ulimit -s` shows the main thread's limit).
#include <pthread.h>
#include <print>
#include <thread>
int main() {
std::println("main thread: {} KiB of stack", pthread_get_stacksize_np(pthread_self()) / 1024);
std::thread worker([] { std::println("a std::thread: {} KiB of stack", pthread_get_stacksize_np(pthread_self()) / 1024); });
worker.join();
} C++ (big_stack.cpp) · big_stack.cpp
// Four million ints, 16 MB, as a local array: more than the stack of the main thread holds.
#include <cstddef>
#include <print>
int main() {
int counts[4'000'000];
for (std::size_t i = 0; i < std::size(counts); ++i) counts[i] = static_cast<int>(i % 7);
long long sum = 0;
for (int c : counts) sum += c;
std::println("sum {}", sum);
} C++ (big_heap.cpp) · big_heap.cpp
// The same four million ints in a std::vector: the elements live on the heap.
#include <cstddef>
#include <print>
#include <vector>
int main() {
std::vector<int> counts(4'000'000);
for (std::size_t i = 0; i < counts.size(); ++i) counts[i] = static_cast<int>(i % 7);
long long sum = 0;
for (int c : counts) sum += c;
std::println("sum {}", sum);
} from session import run
# The stack sizes of this Mac, then a 16 MB local array, which does not fit, and a vector of the same size, which does.
run("""
clang++ -std=c++23 -Wall -Wextra stack_sizes.cpp -o stack_sizes
./stack_sizes
clang++ -std=c++23 -Wall -Wextra big_stack.cpp -o big_stack
./big_stack
clang++ -std=c++23 -Wall -Wextra big_heap.cpp -o big_heap
./big_heap
""") Output
$ clang++ -std=c++23 -Wall -Wextra stack_sizes.cpp -o stack_sizes $ ./stack_sizes main thread: 8176 KiB of stack a std::thread: 524 KiB of stack $ clang++ -std=c++23 -Wall -Wextra big_stack.cpp -o big_stack $ ./big_stack [stopped by SIGSEGV: a shell reports exit status 139] $ clang++ -std=c++23 -Wall -Wextra big_heap.cpp -o big_heap $ ./big_heap sum 11999994
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 stack_session.py
On this Mac, the main thread’s stack can grow to 8,176 KiB, just under 8 MiB, and a thread started with
std::thread gets only 524 KiB. Four million ints need 16,000,000 bytes, about 15.3 MiB, so the program crashed
with SIGSEGV before printing anything. The vector keeps only its 24-byte object on the stack and the 16 MB of
elements on the heap, and it worked. Keep large or input-sized arrays in a std::vector, remember that code running
in other threads has far less room, and use std::array for small, fixed data.
Your turn: replace C-array parameters
The file below has three functions with C-array parameters, one of them with the sizeof bug and one with a
variable-length array. The checks in main already call the functions the way they should look: total(marks)
with no size, best_of_three(judges) with a std::array<int, 3>, and running_totals(runs) with a vector.
// Exercise: rewrite the three functions so that this file builds with the command below and every check says "ok":
// clang++ -std=c++23 -Wall -Wextra -Werror=vla arrays_exercise.cpp -o arrays_exercise
// ./arrays_exercise
// Take std::span<const int> where any number of ints may come, and std::array where the number is fixed. The checks
// in main already call the functions the new way, so the file does not build until the functions match them.
#include <array>
#include <cstddef>
#include <print>
#include <span>
#include <vector>
// 1. The total of some ints.
int total(const int values[], std::size_t n) {
int sum = 0;
for (std::size_t i = 0; i < n; ++i) sum += values[i];
return sum;
}
// 2. The best of three judges' scores.
int best_of_three(const int scores[3]) {
int best = scores[0];
for (std::size_t i = 1; i < sizeof(scores) / sizeof(scores[0]); ++i)
if (scores[i] > best) best = scores[i];
return best;
}
// 3. Running totals: {4, 0, 6} gives {4, 4, 10}.
std::vector<int> running_totals(const int values[], std::size_t n) {
int sums[n];
int sum = 0;
for (std::size_t i = 0; i < n; ++i) {
sum += values[i];
sums[i] = sum;
}
return std::vector<int>(sums, sums + n);
}
int failures = 0;
void check(bool passed, const char* what) {
std::println("{} {}", passed ? "ok " : "FAILED", what);
if (!passed) ++failures;
}
int main() {
int marks[]{71, 64, 88, 92, 57};
std::vector<int> runs{4, 0, 6};
std::array<int, 3> judges{7, 6, 9};
check(total(marks) == 372, "total of a C array of five marks is 372");
check(total(runs) == 10, "total of the vector {4, 0, 6} is 10");
check(total(std::span<const int>{}) == 0, "total of no values is 0");
check(best_of_three(judges) == 9, "best of {7, 6, 9} is 9");
check(running_totals(runs) == std::vector<int>{4, 4, 10}, "running totals of {4, 0, 6} are {4, 4, 10}");
check(running_totals(marks).back() == 372, "the last running total of the marks is 372");
std::println("{} of 6 checks failed", failures);
return failures == 0 ? 0 : 1;
} As it starts, the file does not build:
from session import run
# The exercise as it starts: grep keeps the first line of each message. The VLA is an error under -Werror=vla, and
# the checks call the functions in a way their C-array parameters do not allow.
run("""
clang++ -std=c++23 -Wall -Wextra -Werror=vla arrays_exercise.cpp -o arrays_exercise 2>&1 | grep -E "error|warning"
""") Output
$ clang++ -std=c++23 -Wall -Wextra -Werror=vla arrays_exercise.cpp -o arrays_exercise 2>&1 | grep -E "error|warning" arrays_exercise.cpp:22:39: warning: sizeof on array function parameter will return size of 'const int *' instead of 'const int[3]' [-Wsizeof-array-argument] arrays_exercise.cpp:22:48: warning: 'sizeof (scores)' will return the size of the pointer, not the array itself [-Wsizeof-pointer-div] arrays_exercise.cpp:29:14: error: variable length arrays in C++ are a Clang extension [-Werror,-Wvla-cxx-extension] arrays_exercise.cpp:50:11: error: no matching function for call to 'total' arrays_exercise.cpp:51:11: error: no matching function for call to 'total' arrays_exercise.cpp:52:11: error: no matching function for call to 'total' arrays_exercise.cpp:53:11: error: no matching function for call to 'best_of_three' arrays_exercise.cpp:54:11: error: no matching function for call to 'running_totals' arrays_exercise.cpp:55:11: error: no matching function for call to 'running_totals' 2 warnings and 7 errors generated.
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 exercise_session.py
Change total and running_totals to take std::span<const int>, best_of_three to take
const std::array<int, 3>&, and replace the variable-length array with a std::vector<int>. You are done when the
build command at the top of the file succeeds and the program prints 0 of 6 checks failed. Then check your own
work: best_of_three must not need the number 3 anywhere in its body.
Check yourself
5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Interview questions
Fresher · Why does sizeof give a different answer inside a function that takes an array parameter?
Because the function never receives an array. A parameter declared as int a[] or even int a[5] is adjusted to
int* a, and at the call the array converts to a pointer to its first element. Inside the function, sizeof(a) is
therefore the size of a pointer, 8 bytes on a 64-bit computer, and sizeof(a) / sizeof(a[0]) gives 2 for an
int array of any length. Both GCC and Clang warn about it by default. The fixes are to pass the size separately,
to take a std::span, which carries a pointer and a count, or to use std::array, whose size is part of its type.
std::size(a) makes the mistake a compile error, because it refuses a pointer.
Key takeaways
- A C array is only its elements: its size is known where it is declared and nowhere else, and it cannot be assigned, copied or returned.
- Passed to a function, a C array decays to a pointer;
sizeofinside the function measures the pointer, 8 bytes. Usestd::size, which refuses a pointer, instead ofsizeof(a) / sizeof(a[0]). std::array<T, N>has the same layout at no extra cost, keeps its size and can be copied, compared and returned;std::to_arrayandstd::array a{…}count the elements for you.- To accept any number of elements, take
std::span<const T>; for exactly N, takeconst std::array<T, N>&. int a[n]with a run-timenis not standard C++: Clang warns, GCC accepts it silently, Microsoft’s compiler rejects it. Build with-Werror=vlaand usestd::vector.- Locals live on the stack: 8,176 KiB for the main thread on this Mac and 524 KiB for a
std::thread. A 16 MB local array crashed the program, while a vector of the same size worked.
References
- Working Draft, Programming Languages: C++ (N4950): arrays, functions, array-to-pointer conversion, class template array (ISO/IEC JTC1/SC22/WG21)
- C++ Core Guidelines, SL.con.1: Prefer using STL array or vector instead of a C array (Standard C++ Foundation)
- C++ Core Guidelines, R.14: Avoid [] parameters, prefer span (Standard C++ Foundation)
- Diagnostic flags in Clang: -Wvla-cxx-extension (LLVM Project)
- Diagnostic flags in Clang: -Wsizeof-array-argument (LLVM Project)
- Using the GNU Compiler Collection: Options to Request or Suppress Warnings (-Wvla, -Wsizeof-array-argument) (GNU Project)
- Using the GNU Compiler Collection: Arrays of Variable Length (GNU Project)
- Arrays (C++) (Microsoft)
- Compiler Error C2131 (Microsoft)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress