5.4. Pointers and arrays¶
Pointers are not arrays and arrays are not pointers. However, much confusion arises between them because arrays in expressions often behave like pointers. The term you'll often see is that arrays decay into pointers.
Things don't really "decay" in C or C++, but the name of an array resolves to an address. Given the following array:
int data[5] = {3, 5, 8, 13, 21};
The variable data[0] stores the value 3.
The variable data stores the address of element 0.
Pointers store addresses, so the same of an array and a pointer both store the same kinds of data.
Any array type will implicitly convert to a pointer of the type stored in the array. The pointer is constructed to point to the first element of the array. This conversion happens whenever arrays are used in an expression where arrays are not expected, but pointers are:
1#include <iostream>
2
3int main() {
4 int a[3] = {13, 21, 35};
5 int* p = a;
6
7 std::cout << sizeof a << '\n' // prints size of array
8 << sizeof p << '\n'; // prints size of a pointer
9
10 for(int n: a) { // okay: arrays can be used in range-for loops
11 std::cout << n << ' '; // prints elements of the array
12 }
13 // for(int n: p) { // error: no range for looping on a pointer
14
15
16 // arrays and pointers share the same semantics
17 std::cout << '\n'
18 << *a << '\n' // prints the first element
19 << *p << '\n' // same
20 << *(a + 1) << ' ' << a[1] << '\n' // prints the second element twice
21 << *(p + 1) << ' ' << p[1] << '\n'; // same
22}
This behavior applies to function calling as well:
1#include <iostream>
2
3// print first element of array using pointer dereference
4void g(int (&a)[3]) {
5 std::cout << *a << '\n';
6}
7
8// print first element of array using array semantics through pointer
9void f(int* p) {
10 std::cout << p[0] << '\n';
11}
12
13int main() {
14 int a[3] = {13, 21, 35};
15 int* p = a;
16
17 // where arrays are acceptable, but pointers aren't, only arrays may be used
18 g(a); // okay: function takes an array by reference
19 // g(p); // error: pointers do not implicitly convert to arrays
20
21 // where pointers are acceptable, but arrays aren't, both may be used:
22 f(a); // okay: function takes a pointer
23 f(p); // okay: function takes a pointer
24}
Run It
This example shows many of the concepts we have discussed so far in one spot.
1#include <iostream>
2
3int main()
4{
5 using std::cout;
6 int data[5] = {8, 13, 21, 34, 55};
7
8 int* p1 = data;
9 int* p2 = data + 1;
10 int i1 = * data + 1;
11 int i2 = * (data + 1);
12 int i3 = * p2 + 1;
13
14 cout << "The address of data is: " << data << ", " << &data << ", " << p1 << '\n';
15 cout << "The 1st val in data is: " << * data << ", " << * p1 << '\n';
16 cout << "The address of p1 is: " << &p1 << '\n';
17
18 cout << "The address of data[1] is: " << (data+1) << ", " << p2 << '\n';
19 cout << "The 2nd val in data is: " << * (data+1) << ", " << * p2 << ", " << i2 << '\n';
20
21 cout << "8+1 equals: " << (* data +1) << ", " << i1 << ", " << * p1 + 1 << '\n';
22 cout << "13+1 equals: " << * (data +1)+1 << ", " << i3 << ", " << * p2 + 1 << '\n';
23
24 cout << "(*data + 1): \t" << (* data) << "\t\t" << (* data + 1) << '\n';
25 cout << "*(data + 1): \t" << (* data) << "\t\t" << * (data + 1) << '\n';
26 cout << "(&data + 1): \t" << (* (&data)) << " " << (&data + 1) << '\n';
27
28 cout << "Print dataay address locations:\n";
29 for (int i=0; i<5; ++i) {
30 cout << (data+i) << ", "; // each location is 4 bytes larger
31 }
32 cout << std::endl;
33
34 int array_bytes = sizeof data;
35 int int_bytes = sizeof (int);
36 int array_size = array_bytes / int_bytes;
37
38 cout << "size: " << array_size
39 << "\n# bytes in array: " << array_bytes
40 << "\n# bytes in 1 int: " << int_bytes << '\n';
41
42 return 0;
43}
5.4.1. Array indexing pitfalls¶
Pitfall #1
Arrays perform absolutely no bounds checking.
Read that again.
Good.
Now consider that no compiler will complain about this code:
1int* p = int[3];
2p[0] = 3; // OK
3p[2] = 5; // OK
4p[99] = 8; // oops! where did we write this?
5p[-7] = 8; // or this!
No compiler will inform you that on line 4 we just wrote an 8
at a location 96 positions past the end of the array.
Nor will it inform you that on line 5, we just wrote to a location
7 positions before the beginning of the array.
Most pointer examples you see will never attempt to use operator[]
to index a pointer that is not an array.
This is a good thing, but as you might expect, if you make a mistake,
the compiler has nothing to offer:
int n = 5;
int* p = &n;
int x = p[99] + 2;
Even with all compiler warnings enabled, most compilers will emit nothing at all.
No compiler will inform you that
we just accessed a piece of memory 98 ints past the one you own.
Whatever is stored there, we then added 2 to it
and assigned that value to y.
The compiler doesn't even know p is a pointer to just one int.
Most programmers know better than to make errors this large. We're just demonstrating here that even big mistakes can be completely ignored by the compiler. What is for more common is an off by one error where your array index or pointer address is wrong only by 1. Accessing even a single byte outside your valid memory bounds is still an error and one of the most common errors in C and C++ programs.
Pitfall #2
From the standard:
The definition of the subscript
operator[]is thatE1[E2]is identical to(*((E1)+(E2))). Because of the conversion rules that apply to the binaryoperator+, ifE1is an array object (equivalently, a pointer to the initial element of an array object) andE2is an integer,E1[E2]designates theE2-th element ofE1(counting from zero).
Note
What the standard doesn't repeat here is that addition commutes, that is
\(a+b = b+a\).
A side-effect of this fact is that for any array and index pair a[i],
then a[i] must be equivalent to i[a].
1#include <iostream>
2using std::cout;
3
4int main() {
5 int a[4] = {3, 5, 8, 13};
6 cout << "Print each array element 4 times:\n";
7 for (int i=0; i<4; ++i) {
8 cout << a[i] << ' '
9 << *(a+i) << ' '
10 << *(i+a) << ' '
11 << i[a] << '\n';
12 }
13}
Although the standard does not strictly prohibit this syntax, doesn't mean you should use it.
This pitfall is only a problem when using arrays of type int with easily confused variable names.
The lesson: use variables appropriate for the scope.
In this case, perhaps a single letter (a) for the array was too short.
5.4.2. Arrays of type char¶
In the C language,
the abstract idea of a string is implemented with an array of characters.
Arrays of char that are null terminated are commonly called C strings.
In older C and C++ code using C strings, it's common to see code that uses the null terminator in the C string as a loop exit condition:
1#include <stdio.h>
2
3// an old C idiom to copy a 'string'
4int main (int argc, char** argv) {
5 char a[] = "Hello World!";
6 char b[13];
7
8 // print one char at a time
9 int i;
10 for (i=0; i<12;++i) putchar(a[i]);
11 printf("\n");
12
13
14 char* p1 = a;
15 char* p2 = b;
16 for (int i=0; a[i]; ++i) p2[i] = p1[i];
17
18 printf("copy:\n");
19 printf("%s\n", p2); // print chars until '\0' detected
20 return 0;
21}
Code like this can fail if the source string contains any embedded null characters. The risk is that this code works fine 99% of the time, but fails when working with character data from an uncontrolled source (a network or socket interface, for example).
Try This!
Run the previous example, but modify it, replacing the 'Hello World' with 'Hello\0World'. What happens?
What warnings does the compiler display?
More to Explore
MITRE Common Weakness Enumerations Off by one error