Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

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++.

  • Beginner
  • 18 minutes
  • Examples run with Clang 21.0.0, GCC 16.2.0 and Python 3.14.8
  • By MySmartCoPilot
Free: 18 minutes

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

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_array.cpp, and an assignment between arrays

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];
}
Both compilers, and an assignment they refuse Python · c_array_session.py
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 five int elements. With an empty list, int zeros[4]{};, every element is 0. Without any initialiser, local int elements 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, and sizeof marks the bytes: 5 × 4 = 20. A C array stores no size of its own; the compiler knows it from the type int[5], so both work only where that type is visible.
  • A range-for loop and std::println work 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:

A C array of five ints in main; a function parameter gets only a pointer to its first element, a std::span gets a pointer and the count 5.in main: int scores[5], 20 bytesshow(int scores[5])gets an int*: 8 bytesthe 5 is lostaverage(std::span<const int>)gets a pointer and a count:16 bytes, size() is 57164889257pointer + 5decays

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.

  1. 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.
  2. 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.
  3. 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.

decay.cpp C++ · decay.cpp
// 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);
}
sizeof inside the function measures a pointer Python · decay_session.py
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:

size_of_pointer.cpp C++ · size_of_pointer.cpp
// 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);
}
std::size on a pointer does not compile Python · size_of_pointer_session.py
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.cpp C++ · std_array.cpp
// 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));
}
std::array with both compilers Python · std_array_session.py
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, and scores.size() says so anywhere, inside functions too. It is 20 bytes, exactly like int[5]: the class only wraps the C array, so it costs nothing.
  • It is a value. with_bonus received its own copy, changed it and returned it, while scores stayed 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, and std::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, the int elements start with unknown values, just as in a C array, until fill(-1) or an assignment gives them one.
  • front(), back() and at(i) work as they do for std::vector, and at throws std::out_of_range for 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:

span.cpp C++ · span.cpp
// 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>));
}
One function, three kinds of array Python · span_session.py
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.

C array, std::array or std::vector?
Criterion C arraystd::arraystd::vector
Size FixedFixedCan change
size() NoYesYes
Copy and return NoYesYes
As a parameter Becomes a pointerStays an arrayStays a vector
Elements Where declaredWhere declaredOn the heap
When to choose C interfacesSmall fixed dataEverything 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:

vla.cpp C++ · vla.cpp
// 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]);
}
One variable-length array, four builds Python · vla_session.py
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 -pedantic or -Wvla makes 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:

The stack sizes, and the same counts on the stack and on the heap

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);
}
Stack sizes, then 16 MB on the stack and on the heap Python · stack_session.py
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.

Data Storage Converter Turn an array's size in bytes, elements times sizeof, into KiB and MiB to compare it with a stack limit.

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.

arrays_exercise.cpp C++ · arrays_exercise.cpp
// 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:

The starting point Python · exercise_session.py
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.

  1. Question 1 of 5 On a 64-bit computer, void f(double values[10]) prints sizeof(values). What does it print?

    Type a number.

    Show the answer to question 1

    Answer: 8

    A parameter declared as an array is adjusted to a pointer, here double*, and a pointer is 8 bytes on a 64-bit computer. The 10 is ignored. In the caller, sizeof of a double[10] array would be 80.

  2. Question 2 of 5 What happens when you call std::size(p) where p is an int*?

    Choose one answer.

    Show the answer to question 2

    Answer: The program does not compile

    std::size accepts containers and real arrays, whose size is part of their type. A pointer has no size, so no overload matches and the compiler stops. That makes it safer than sizeof(a) / sizeof(a[0]), which compiles on a pointer and gives a wrong answer.

  3. Question 3 of 5 std::array<int, 3> a{1, 2, 3}; auto b = a; b[0] = 9; What does a[0] hold afterwards?

    Choose one answer.

    Show the answer to question 3

    Answer: 1

    A std::array is a value: auto b = a copies all three elements, so changing b leaves a alone. A C array cannot be copied or assigned this way at all.

  4. Question 4 of 5 int buffer[n]; with n read at run time. Which statements are true?

    Choose every answer that is right.

    Show the answer to question 4

    Answer:

    • It is not standard C++
    • GCC accepts it without a warning unless you add -pedantic or -Wvla
    • Microsoft's compiler rejects it

    The standard requires an array bound to be a constant expression. GCC accepts variable-length arrays as an extension and says nothing by default, Clang accepts them with a warning that is on by default, and Microsoft's compiler requires a constant. Use std::vector<int> buffer(n) instead.

  5. Question 5 of 5 int counts[4'000'000]; inside main crashed in the lesson, but std::vector<int> counts(4'000'000); worked. Why?

    Choose one answer.

    Show the answer to question 5

    Answer: The array is on the stack, which holds about 8 MB; the vector's elements are on the heap

    Four million ints are 16 MB. A local array lives in the function's stack frame, and the main thread's stack on macOS is 8,176 KiB, so the program crashed with SIGSEGV. A vector keeps only its 24-byte object on the stack and its elements on the heap.

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; sizeof inside the function measures the pointer, 8 bytes. Use std::size, which refuses a pointer, instead of sizeof(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_array and std::array a{…} count the elements for you.
  • To accept any number of elements, take std::span<const T>; for exactly N, take const std::array<T, N>&.
  • int a[n] with a run-time n is not standard C++: Clang warns, GCC accepts it silently, Microsoft’s compiler rejects it. Build with -Werror=vla and use std::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

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.