C++ inherited arrays from C where they are used virtually everywhere. C++ provides abstractions that are easier to use and less error-prone (std::vector<T>
since C++98 and std::array<T, n>
since C++11), so the need for arrays does not arise quite as often as it does in C. However, when you read legacy code or interact with a library written in C, you should have a firm grasp on how arrays work.
This FAQ is split into five parts:
If you feel something important is missing in this FAQ, write an answer and link it here as an additional part.
In the following text, "array" means "C array", not the class template std::array
. Basic knowledge of the C declarator syntax is assumed. Note that the manual usage of new
and delete
as demonstrated below is extremely dangerous in the face of exceptions, but that is the topic of another FAQ.
This question is related to
c++
arrays
pointers
multidimensional-array
c++-faq
As with any other kind of C++ object, arrays can be stored either directly in named variables (then the size must be a compile-time constant; C++ does not support VLAs), or they can be stored anonymously on the heap and accessed indirectly via pointers (only then can the size be computed at runtime).
Automatic arrays (arrays living "on the stack") are created each time the flow of control passes through the definition of a non-static local array variable:
void foo()
{
int automatic_array[8];
}
Initialization is performed in ascending order. Note that the initial values depend on the element type T
:
T
is a POD (like int
in the above example), no initialization takes place.T
initializes all the elements.T
provides no accessible default-constructor, the program does not compile.Alternatively, the initial values can be explicitly specified in the array initializer, a comma-separated list surrounded by curly brackets:
int primes[8] = {2, 3, 5, 7, 11, 13, 17, 19};
Since in this case the number of elements in the array initializer is equal to the size of the array, specifying the size manually is redundant. It can automatically be deduced by the compiler:
int primes[] = {2, 3, 5, 7, 11, 13, 17, 19}; // size 8 is deduced
It is also possible to specify the size and provide a shorter array initializer:
int fibonacci[50] = {0, 1, 1}; // 47 trailing zeros are deduced
In that case, the remaining elements are zero-initialized. Note that C++ allows an empty array initializer (all elements are zero-initialized), whereas C89 does not (at least one value is required). Also note that array initializers can only be used to initialize arrays; they cannot later be used in assignments.
Static arrays (arrays living "in the data segment") are local array variables defined with the static
keyword and array variables at namespace scope ("global variables"):
int global_static_array[8];
void foo()
{
static int local_static_array[8];
}
(Note that variables at namespace scope are implicitly static. Adding the static
keyword to their definition has a completely different, deprecated meaning.)
Here is how static arrays behave differently from automatic arrays:
(None of the above is specific to arrays. These rules apply equally well to other kinds of static objects.)
Array data members are created when their owning object is created. Unfortunately, C++03 provides no means to initialize arrays in the member initializer list, so initialization must be faked with assignments:
class Foo
{
int primes[8];
public:
Foo()
{
primes[0] = 2;
primes[1] = 3;
primes[2] = 5;
// ...
}
};
Alternatively, you can define an automatic array in the constructor body and copy the elements over:
class Foo
{
int primes[8];
public:
Foo()
{
int local_array[] = {2, 3, 5, 7, 11, 13, 17, 19};
std::copy(local_array + 0, local_array + 8, primes + 0);
}
};
In C++0x, arrays can be initialized in the member initializer list thanks to uniform initialization:
class Foo
{
int primes[8];
public:
Foo() : primes { 2, 3, 5, 7, 11, 13, 17, 19 }
{
}
};
This is the only solution that works with element types that have no default constructor.
Dynamic arrays have no names, hence the only means of accessing them is via pointers. Because they have no names, I will refer to them as "anonymous arrays" from now on.
In C, anonymous arrays are created via malloc
and friends. In C++, anonymous arrays are created using the new T[size]
syntax which returns a pointer to the first element of an anonymous array:
std::size_t size = compute_size_at_runtime();
int* p = new int[size];
The following ASCII art depicts the memory layout if the size is computed as 8 at runtime:
+---+---+---+---+---+---+---+---+
(anonymous) | | | | | | | | |
+---+---+---+---+---+---+---+---+
^
|
|
+-|-+
p: | | | int*
+---+
Obviously, anonymous arrays require more memory than named arrays due to the extra pointer that must be stored separately. (There is also some additional overhead on the free store.)
Note that there is no array-to-pointer decay going on here. Although evaluating new int[size]
does in fact create an array of integers, the result of the expression new int[size]
is already a pointer to a single integer (the first element), not an array of integers or a pointer to an array of integers of unknown size. That would be impossible, because the static type system requires array sizes to be compile-time constants. (Hence, I did not annotate the anonymous array with static type information in the picture.)
Concerning default values for elements, anonymous arrays behave similar to automatic arrays. Normally, anonymous POD arrays are not initialized, but there is a special syntax that triggers value-initialization:
int* p = new int[some_computed_size]();
(Note the trailing pair of parenthesis right before the semicolon.) Again, C++0x simplifies the rules and allows specifying initial values for anonymous arrays thanks to uniform initialization:
int* p = new int[8] { 2, 3, 5, 7, 11, 13, 17, 19 };
If you are done using an anonymous array, you have to release it back to the system:
delete[] p;
You must release each anonymous array exactly once and then never touch it again afterwards. Not releasing it at all results in a memory leak (or more generally, depending on the element type, a resource leak), and trying to release it multiple times results in undefined behavior. Using the non-array form delete
(or free
) instead of delete[]
to release the array is also undefined behavior.
OK, you’ve been told, or have found out yourself, that globals (namespace scope variables that can be accessed outside the translation unit) are Evil™. But did you know how truly Evil™ they are? Consider the program below, consisting of two files [main.cpp] and [numbers.cpp]:
// [main.cpp]
#include <iostream>
extern int* numbers;
int main()
{
using namespace std;
for( int i = 0; i < 42; ++i )
{
cout << (i > 0? ", " : "") << numbers[i];
}
cout << endl;
}
// [numbers.cpp]
int numbers[42] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
In Windows 7 this compiles and links fine with both MinGW g++ 4.4.1 and Visual C++ 10.0.
Since the types don't match, the program crashes when you run it.
In-the-formal explanation: the program has Undefined Behavior (UB), and instead of crashing it can therefore just hang, or perhaps do nothing, or it can send threating e-mails to the presidents of the USA, Russia, India, China and Switzerland, and make Nasal Daemons fly out of your nose.
In-practice explanation: in main.cpp
the array is treated as a pointer, placed
at the same address as the array. For 32-bit executable this means that the first
int
value in the array, is treated as a pointer. I.e., in main.cpp
the
numbers
variable contains, or appears to contain, (int*)1
. This causes the
program to access memory down at very bottom of the address space, which is
conventionally reserved and trap-causing. Result: you get a crash.
The compilers are fully within their rights to not diagnose this error, because C++11 §3.5/10 says, about the requirement of compatible types for the declarations,
[N3290 §3.5/10]
A violation of this rule on type identity does not require a diagnostic.
The same paragraph details the variation that is allowed:
… declarations for an array object can specify array types that differ by the presence or absence of a major array bound (8.3.4).
This allowed variation does not include declaring a name as an array in one translation unit, and as a pointer in another translation unit.
memset
& friends).Not written yet
With deep C experience it’s natural to write …
#define N_ITEMS( array ) (sizeof( array )/sizeof( array[0] ))
Since an array
decays to pointer to first element where needed, the
expression sizeof(a)/sizeof(a[0])
can also be written as
sizeof(a)/sizeof(*a)
. It means the same, and no matter how it’s
written it is the C idiom for finding the number elements of array.
Main pitfall: the C idiom is not typesafe. For example, the code …
#include <stdio.h>
#define N_ITEMS( array ) (sizeof( array )/sizeof( *array ))
void display( int const a[7] )
{
int const n = N_ITEMS( a ); // Oops.
printf( "%d elements.\n", n );
}
int main()
{
int const moohaha[] = {1, 2, 3, 4, 5, 6, 7};
printf( "%d elements, calling display...\n", N_ITEMS( moohaha ) );
display( moohaha );
}
passes a pointer to N_ITEMS
, and therefore most likely produces a wrong
result. Compiled as a 32-bit executable in Windows 7 it produces …
7 elements, calling display...
1 elements.
int const a[7]
to just int const a[]
.int const a[]
to int const* a
.N_ITEMS
is therefore invoked with a pointer.sizeof(array)
(size of a pointer) is then 4.sizeof(*array)
is equivalent to sizeof(int)
, which for a 32-bit executable is also 4.In order to detect this error at run time you can do …
#include <assert.h>
#include <typeinfo>
#define N_ITEMS( array ) ( \
assert(( \
"N_ITEMS requires an actual array as argument", \
typeid( array ) != typeid( &*array ) \
)), \
sizeof( array )/sizeof( *array ) \
)
7 elements, calling display...
Assertion failed: ( "N_ITEMS requires an actual array as argument", typeid( a ) != typeid( &*a ) ), file runtime_detect ion.cpp, line 16This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.
The runtime error detection is better than no detection, but it wastes a little processor time, and perhaps much more programmer time. Better with detection at compile time! And if you're happy to not support arrays of local types with C++98, then you can do that:
#include <stddef.h>
typedef ptrdiff_t Size;
template< class Type, Size n >
Size n_items( Type (&)[n] ) { return n; }
#define N_ITEMS( array ) n_items( array )
Compiling this definition substituted into the first complete program, with g++, I got …
M:\count> g++ compile_time_detection.cpp
compile_time_detection.cpp: In function 'void display(const int*)':
compile_time_detection.cpp:14: error: no matching function for call to 'n_items(const int*&)'M:\count> _
How it works: the array is passed by reference to n_items
, and so it does
not decay to pointer to first element, and the function can just return the
number of elements specified by the type.
With C++11 you can use this also for arrays of local type, and it's the type safe C++ idiom for finding the number of elements of an array.
constexpr
array size function.With C++11 and later it's natural, but as you'll see dangerous!, to replace the C++03 function
typedef ptrdiff_t Size;
template< class Type, Size n >
Size n_items( Type (&)[n] ) { return n; }
with
using Size = ptrdiff_t;
template< class Type, Size n >
constexpr auto n_items( Type (&)[n] ) -> Size { return n; }
where the significant change is the use of constexpr
, which allows
this function to produce a compile time constant.
For example, in contrast to the C++03 function, such a compile time constant can be used to declare an array of the same size as another:
// Example 1
void foo()
{
int const x[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 4};
constexpr Size n = n_items( x );
int y[n] = {};
// Using y here.
}
But consider this code using the constexpr
version:
// Example 2
template< class Collection >
void foo( Collection const& c )
{
constexpr int n = n_items( c ); // Not in C++14!
// Use c here
}
auto main() -> int
{
int x[42];
foo( x );
}
The pitfall: as of July 2015 the above compiles with MinGW-64 5.1.0 with
-pedantic-errors
, and,
testing with the online compilers at gcc.godbolt.org/, also with clang 3.0
and clang 3.2, but not with clang 3.3, 3.4.1, 3.5.0, 3.5.1, 3.6 (rc1) or
3.7 (experimental). And important for the Windows platform, it does not compile
with Visual C++ 2015. The reason is a C++11/C++14 statement about use of
references in constexpr
expressions:
A conditional-expression
e
is a core constant expression unless the evaluation ofe
, following the rules of the abstract machine (1.9), would evaluate one of the following expressions:
?
- an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
- it is initialized with a constant expression or
- it is a non-static data member of an object whose lifetime began within the evaluation of e;
One can always write the more verbose
// Example 3 -- limited
using Size = ptrdiff_t;
template< class Collection >
void foo( Collection const& c )
{
constexpr Size n = std::extent< decltype( c ) >::value;
// Use c here
}
… but this fails when Collection
is not a raw array.
To deal with collections that can be non-arrays one needs the overloadability of an
n_items
function, but also, for compile time use one needs a compile time
representation of the array size. And the classic C++03 solution, which works fine
also in C++11 and C++14, is to let the function report its result not as a value
but via its function result type. For example like this:
// Example 4 - OK (not ideal, but portable and safe)
#include <array>
#include <stddef.h>
using Size = ptrdiff_t;
template< Size n >
struct Size_carrier
{
char sizer[n];
};
template< class Type, Size n >
auto static_n_items( Type (&)[n] )
-> Size_carrier<n>;
// No implementation, is used only at compile time.
template< class Type, size_t n > // size_t for g++
auto static_n_items( std::array<Type, n> const& )
-> Size_carrier<n>;
// No implementation, is used only at compile time.
#define STATIC_N_ITEMS( c ) \
static_cast<Size>( sizeof( static_n_items( c ).sizer ) )
template< class Collection >
void foo( Collection const& c )
{
constexpr Size n = STATIC_N_ITEMS( c );
// Use c here
(void) c;
}
auto main() -> int
{
int x[42];
std::array<int, 43> y;
foo( x );
foo( y );
}
About the choice of return type for static_n_items
: this code doesn't use std::integral_constant
because with std::integral_constant
the result is represented
directly as a constexpr
value, reintroducing the original problem. Instead
of a Size_carrier
class one can let the function directly return a
reference to an array. However, not everybody is familiar with that syntax.
About the naming: part of this solution to the constexpr
-invalid-due-to-reference
problem is to make the choice of compile time constant explicit.
Hopefully the oops-there-was-a-reference-involved-in-your-constexpr
issue will be fixed with
C++17, but until then a macro like the STATIC_N_ITEMS
above yields portability,
e.g. to the clang and Visual C++ compilers, retaining type safety.
Related: macros do not respect scopes, so to avoid name collisions it can be a
good idea to use a name prefix, e.g. MYLIB_STATIC_N_ITEMS
.
Programmers often confuse multidimensional arrays with arrays of pointers.
Most programmers are familiar with named multidimensional arrays, but many are unaware of the fact that multidimensional array can also be created anonymously. Multidimensional arrays are often referred to as "arrays of arrays" or "true multidimensional arrays".
When using named multidimensional arrays, all dimensions must be known at compile time:
int H = read_int();
int W = read_int();
int connect_four[6][7]; // okay
int connect_four[H][7]; // ISO C++ forbids variable length array
int connect_four[6][W]; // ISO C++ forbids variable length array
int connect_four[H][W]; // ISO C++ forbids variable length array
This is how a named multidimensional array looks like in memory:
+---+---+---+---+---+---+---+
connect_four: | | | | | | | |
+---+---+---+---+---+---+---+
| | | | | | | |
+---+---+---+---+---+---+---+
| | | | | | | |
+---+---+---+---+---+---+---+
| | | | | | | |
+---+---+---+---+---+---+---+
| | | | | | | |
+---+---+---+---+---+---+---+
| | | | | | | |
+---+---+---+---+---+---+---+
Note that 2D grids such as the above are merely helpful visualizations. From the point of view of C++, memory is a "flat" sequence of bytes. The elements of a multidimensional array are stored in row-major order. That is, connect_four[0][6]
and connect_four[1][0]
are neighbors in memory. In fact, connect_four[0][7]
and connect_four[1][0]
denote the same element! This means that you can take multi-dimensional arrays and treat them as large, one-dimensional arrays:
int* p = &connect_four[0][0];
int* q = p + 42;
some_int_sequence_algorithm(p, q);
With anonymous multidimensional arrays, all dimensions except the first must be known at compile time:
int (*p)[7] = new int[6][7]; // okay
int (*p)[7] = new int[H][7]; // okay
int (*p)[W] = new int[6][W]; // ISO C++ forbids variable length array
int (*p)[W] = new int[H][W]; // ISO C++ forbids variable length array
This is how an anonymous multidimensional array looks like in memory:
+---+---+---+---+---+---+---+
+---> | | | | | | | |
| +---+---+---+---+---+---+---+
| | | | | | | | |
| +---+---+---+---+---+---+---+
| | | | | | | | |
| +---+---+---+---+---+---+---+
| | | | | | | | |
| +---+---+---+---+---+---+---+
| | | | | | | | |
| +---+---+---+---+---+---+---+
| | | | | | | | |
| +---+---+---+---+---+---+---+
|
+-|-+
p: | | |
+---+
Note that the array itself is still allocated as a single block in memory.
You can overcome the restriction of fixed width by introducing another level of indirection.
Here is a named array of five pointers which are initialized with anonymous arrays of different lengths:
int* triangle[5];
for (int i = 0; i < 5; ++i)
{
triangle[i] = new int[5 - i];
}
// ...
for (int i = 0; i < 5; ++i)
{
delete[] triangle[i];
}
And here is how it looks like in memory:
+---+---+---+---+---+
| | | | | |
+---+---+---+---+---+
^
| +---+---+---+---+
| | | | | |
| +---+---+---+---+
| ^
| | +---+---+---+
| | | | | |
| | +---+---+---+
| | ^
| | | +---+---+
| | | | | |
| | | +---+---+
| | | ^
| | | | +---+
| | | | | |
| | | | +---+
| | | | ^
| | | | |
| | | | |
+-|-+-|-+-|-+-|-+-|-+
triangle: | | | | | | | | | | |
+---+---+---+---+---+
Since each line is allocated individually now, viewing 2D arrays as 1D arrays does not work anymore.
Here is an anonymous array of 5 (or any other number of) pointers which are initialized with anonymous arrays of different lengths:
int n = calculate_five(); // or any other number
int** p = new int*[n];
for (int i = 0; i < n; ++i)
{
p[i] = new int[n - i];
}
// ...
for (int i = 0; i < n; ++i)
{
delete[] p[i];
}
delete[] p; // note the extra delete[] !
And here is how it looks like in memory:
+---+---+---+---+---+
| | | | | |
+---+---+---+---+---+
^
| +---+---+---+---+
| | | | | |
| +---+---+---+---+
| ^
| | +---+---+---+
| | | | | |
| | +---+---+---+
| | ^
| | | +---+---+
| | | | | |
| | | +---+---+
| | | ^
| | | | +---+
| | | | | |
| | | | +---+
| | | | ^
| | | | |
| | | | |
+-|-+-|-+-|-+-|-+-|-+
| | | | | | | | | | |
+---+---+---+---+---+
^
|
|
+-|-+
p: | | |
+---+
Array-to-pointer decay naturally extends to arrays of arrays and arrays of pointers:
int array_of_arrays[6][7];
int (*pointer_to_array)[7] = array_of_arrays;
int* array_of_pointers[6];
int** pointer_to_pointer = array_of_pointers;
However, there is no implicit conversion from T[h][w]
to T**
. If such an implicit conversion did exist, the result would be a pointer to the first element of an array of h
pointers to T
(each pointing to the first element of a line in the original 2D array), but that pointer array does not exist anywhere in memory yet. If you want such a conversion, you must create and fill the required pointer array manually:
int connect_four[6][7];
int** p = new int*[6];
for (int i = 0; i < 6; ++i)
{
p[i] = connect_four[i];
}
// ...
delete[] p;
Note that this generates a view of the original multidimensional array. If you need a copy instead, you must create extra arrays and copy the data yourself:
int connect_four[6][7];
int** p = new int*[6];
for (int i = 0; i < 6; ++i)
{
p[i] = new int[7];
std::copy(connect_four[i], connect_four[i + 1], p[i]);
}
// ...
for (int i = 0; i < 6; ++i)
{
delete[] p[i];
}
delete[] p;
For no particular reason, arrays cannot be assigned to one another. Use std::copy
instead:
#include <algorithm>
// ...
int a[8] = {2, 3, 5, 7, 11, 13, 17, 19};
int b[8];
std::copy(a + 0, a + 8, b);
This is more flexible than what true array assignment could provide because it is possible to copy slices of larger arrays into smaller arrays.
std::copy
is usually specialized for primitive types to give maximum performance. It is unlikely that std::memcpy
performs better. If in doubt, measure.
Although you cannot assign arrays directly, you can assign structs and classes which contain array members. That is because array members are copied memberwise by the assignment operator which is provided as a default by the compiler. If you define the assignment operator manually for your own struct or class types, you must fall back to manual copying for the array members.
Arrays cannot be passed by value. You can either pass them by pointer or by reference.
Since arrays themselves cannot be passed by value, usually a pointer to their first element is passed by value instead. This is often called "pass by pointer". Since the size of the array is not retrievable via that pointer, you have to pass a second parameter indicating the size of the array (the classic C solution) or a second pointer pointing after the last element of the array (the C++ iterator solution):
#include <numeric>
#include <cstddef>
int sum(const int* p, std::size_t n)
{
return std::accumulate(p, p + n, 0);
}
int sum(const int* p, const int* q)
{
return std::accumulate(p, q, 0);
}
As a syntactic alternative, you can also declare parameters as T p[]
, and it means the exact same thing as T* p
in the context of parameter lists only:
int sum(const int p[], std::size_t n)
{
return std::accumulate(p, p + n, 0);
}
You can think of the compiler as rewriting T p[]
to T *p
in the context of parameter lists only. This special rule is partly responsible for the whole confusion about arrays and pointers. In every other context, declaring something as an array or as a pointer makes a huge difference.
Unfortunately, you can also provide a size in an array parameter which is silently ignored by the compiler. That is, the following three signatures are exactly equivalent, as indicated by the compiler errors:
int sum(const int* p, std::size_t n)
// error: redefinition of 'int sum(const int*, size_t)'
int sum(const int p[], std::size_t n)
// error: redefinition of 'int sum(const int*, size_t)'
int sum(const int p[8], std::size_t n) // the 8 has no meaning here
Arrays can also be passed by reference:
int sum(const int (&a)[8])
{
return std::accumulate(a + 0, a + 8, 0);
}
In this case, the array size is significant. Since writing a function that only accepts arrays of exactly 8 elements is of little use, programmers usually write such functions as templates:
template <std::size_t n>
int sum(const int (&a)[n])
{
return std::accumulate(a + 0, a + n, 0);
}
Note that you can only call such a function template with an actual array of integers, not with a pointer to an integer. The size of the array is automatically inferred, and for every size n
, a different function is instantiated from the template. You can also write quite useful function templates that abstract from both the element type and from the size.
Source: Stackoverflow.com