Week 9

Pointers & Arrays

Because an array name is the same as the address of its first element, you can use

it as a pointer value. The crucial difference between arrays and pointers in C++ comes into play when variables are declared.
int array[5] = {0};
int *p;

A memory picture of an array and a pointer. The distinction between these is memory allocation. The first reserves five int values (on the stack or in the static storage area); the second reserves space for a pointer. The name array is an address, not a pointer.

Bart Simpson at the board again!

A memory picture of an array and a pointer. If you define an array, you have storage to work with; if you declare a pointer variable, that variable is not associated with any storage until you initialize it. The simplest way to initialize a pointer to an array is to assign the array name to the pointer variable:

int array[5] = {0};
int *p = array;

Now, the pointer p contains the same address used for array.

Week 9

Pointer Arithmetic & Arrays

You can change the location where a pointer points by incrementing or decrementing it. You can also generate new pointer values by adding or subtracting integers from an existing pointer. The effective address depends upon the base type of the pointer.

Given 4-byte int and 8-byte double, if you add 1 to an integer address, the new address produced is 4 bytes larger than the original pointer value; if you add 1 to a double address, the new address is 8 bytes larger.

int array[] = {1, 2, 3, 4, 5};
int *p = array;     // p <- &array[0]
p = array + 1;      // p <- &array[1]
p = array + 4;      // p <- &array[4]

Of course, unlike a pointer, you cannot increment or decrement an array name.

Week 9

Dereferencing Arrays

You can also dereference an array, just like a regular pointer, using the regular dereferencing operator. Here are some examples (note the parentheses):

int array[] = {1, 2, 3, 4, 5};
cout << *array;         // array[0]
cout << *(array + 2);   // array[2]
cout << *array + 2;     // array[0] + 2

Similarly, you can combine dereferencing with address arithmetic, by using the subscript operator; you can use the subscript operator on both pointers and arrays. All of these expressions are true.

int array[] = {1, 2, 3, 4, 5};
int *p = array;
*array == *p;             // true
array[0] == p[0];         // also true
array[3] = *(p + 3);      // true again
4[array] = *(4 + array);  // also true

In fact, in C++, the subscript operator is just shorthand for a combination of address arithmetic along with dereferencing the resulting address.

address[offset] -> offset[address] -> *(address + offset)
Week 9

Arrays & Functions

You cannot pass an array by value to a function as you can a vector. While you can pass an array to a function by reference, you'll almost never do that. Instead, we need to learn about a new way of passing parameters: pass by address.

Recall that an array name is an address, which you may store inside a pointer.

int array[5];
int *p = array;

This is the secret to writing functions that process arrays:

  • Create a function with a pointer as a parameter. You may declare this pointer as int a[], indicating that you intend to initialize it with the address of the first element in an array.
  • Call the function, supplying the name of an array as the argument.

Here are two prototypes. The first uses the square brackets to declare the pointer variable a. The second uses the normal pointer parameter syntax. Both have identical meaning when as a parameter declaration.

int aSum(const int a[], size_t size);
int aSum(const int *a, size_t size);

With "pointer notation", the star comes before the name, while with "array notation", the brackets come after. A common error, for Java programmers moving to C++, is to write the prototype like this, int aSum(const int[] a, size_t size); which is a syntax error.

Week 9

Decaying Arrays

A decaying mansion.

When you pass an array to a function, we say that the array "decays to a pointer". This is similar to what happens with primitive types in this case:

int n = 3.14;

The int variable n cannot store the fractional portion of 3.14, so it truncates the number and stores 3.

When you pass an array name to a function, and it is converted into a pointer, it also loses certain information; specifically, it loses the ability to determine the allocated size of the array inside the function.

When you declare the array, the compiler "knows" the allocated size of the array:

int array[] = {...};
size_t kLen = sizeof(array) / sizeof(array[0]);   // OK;

However, you pass that array to a function, you cannot use the same code.

void f(const int a[]){ 
  size_t kBug = sizeof(a) / sizeof(a[0]);   // ERROR
}
That means we must calculate an array size when the array is created, and then supply it when calling the function.
Week 9

Arrays & Const

Arrays passed to a function act as if the array was passed by reference. That can be dangerous, because the function may inadvertently modify the caller's argument.

for (size_t i = 0; i < len; ++i)
{
    sum += a[i];
    a[i] = sum;
}

This function was intended to sum all the elements in an array. If you were distracted and inadvertently used an assignment operator instead of the comparison operator, as on line 4, the function would still produce the correct sum, but mistakenly destroy the values in the array passed to it.

Not a good thing. To fix this, use the same technique you used for pass-by-reference:

  • If a function intends to modify the array (initialization, shifting, sorting, etc.) then do not use const in front of the formal parameter. (Since you are passing by address, you will never use &.)
  • If a function does not intend to modify the array (counting, summing, printing, etc.) then always use const in front of the parameter.
double average(const int a[], size_t len);