Pointer, Array and Function

An array's name is a pointer, pointing to the first element (index 0) of the array. For example, suppose that numbers is an int array, numbers is a also an int pointer, pointing at the first element of the array. That is, numbers is the same as &numbers[0]. Consequently, *numbers is number[0]; *(numbers+i) is numbers[i].

int main() {
   const int SIZE = 5;
   int numbers[SIZE] = {11, 22, 44, 21, 41};  // An int array

   // The array name numbers is an int pointer, pointing at the
   // first item of the array, i.e., numbers = &numbers[0]
   cout << &numbers[0] << endl; // Print address of first element (0x22fef8)
   cout << numbers << endl;     // Same as above (0x22fef8)
   cout << *numbers << endl;         // Same as numbers[0] (11)
   cout << *(numbers + 1) << endl;   // Same as numbers[1] (22)
   cout << *(numbers + 4) << endl;   // Same as numbers[4] (41)
}

If numbers is an int array, it is treated as an int pointer pointing to the first element of the array. (numbers + 1) points to the next int, instead of having the next sequential address. Take note that an int typically has 4 bytes. That is (numbers + 1) increases the address by 4, or sizeof(int).

int numbers[] = {11, 22, 33};
int * iPtr = numbers;
cout << iPtr << endl;        // 0x22cd30
cout << iPtr + 1 << endl;    // 0x22cd34 (increase by 4 - sizeof int)
cout << *iPtr << endl;       // 11
cout << *(iPtr + 1) << endl; // 22 
cout << *iPtr + 1 << endl;   // 12

The operation sizeof(arrayName) returns the total bytes of the array. You can derive the length (size) of the array by dividing it with the size of an element (e.g. element 0).

int numbers[100];
cout << sizeof(numbers) << endl;     // Size of entire array in bytes (400)
cout << sizeof(numbers[0]) << endl;  // Size of first element of the array in bytes (4)
cout << "Array size is " << sizeof(numbers) / sizeof(numbers[0]) << endl;  // (100)

Passing Array In/Out of a Function

An array is passed into a function as a pointer to the first element of the array. You can use array notation (e.g., int[]) or pointer notation (e.g., int*) in the function declaration. The compiler always treats it as pointer (e.g., int*). For example, the following declarations are equivalent:

int max(int numbers[], int size);
int max(int *numbers, int size);
int max(int number[50], int size);

They will be treated as int* by the compiler, as follow. The size of the array given in [] is ignored.

int max(int*, int);

Array is passed by reference into the function, because a pointer is passed instead of a clone copy. If the array is modified inside the function, the modifications are applied to the caller's copy. You could declare the array parameter as const to prevent the array from being modified inside the function.

The size of the array is not part of the array parameter, and needs to be passed in another int parameter. Compiler is not able to deduce the array size from the array pointer, and does not perform array bound check.

int main() {
   const int SIZE = 4;
   int numbers[SIZE] = {11, 22, 33, 22};
   print(numbers, SIZE);
   cout << max(numbers, SIZE) << endl;
   replaceByMax(numbers, SIZE);
   print(numbers, SIZE);
}

// Return the maximum value of the given array (index notation).
// The array is declared const, and cannot be modified inside the function.
int max(const int arr[], int size) {
   int max = arr[0];
   for (int i = 1; i < size; ++i) {
      if (max < arr[i]) max = arr[i];
   }
   return max;
}

// Return the maximum value of the given array (pointer notation).
int max(const int *arr, int size) {
   int max = *arr;
   for (int i = 1; i < size; ++i) {
      if (max < *(arr+i)) max = *(arr+i);
   }
   return max;
}

// Replace all elements of the given array by its maximum value
// Array is passed by reference. Modify the caller's copy.
void replaceByMax(int arr[], int size) {
   int maxValue = max(arr, size);
   for (int i = 0; i < size; ++i) {
      arr[i] = maxValue;
   }
}

// Print the array's content
void print(const int arr[], int size) {
   cout << "{";
   for (int i = 0; i < size; ++i) {
      cout << arr[i];
      if (i < size - 1) cout << ",";
   }
   cout << "}" << endl;
}

Pass-by-Reference and sizeof

int main() {
   const int SIZE = 5;
   int a[SIZE] = {8, 4, 5, 3, 2};
   cout << "sizeof in main() is " << sizeof(a) << endl;
   cout << "address in main() is " << a << endl;
   fun(a, SIZE);
}

// Function definitions
void fun(const int *arr, int size) {
   cout << "sizeof in function is " << sizeof(arr) << endl;
   cout << "address in function is " << arr << endl;
}

sizeof in main() is 20
address in main() is 0x22fefc
sizeof in function is 4
address in function is 0x22fefc

The address of arrays in main() and the function are the same, as expected, as array is passed by reference.

In main(), the sizeof array is 20 (4 bytes per int, length of 5). Inside the function, the sizeof is 4, which is the sizeof int pointer (4-byte address). This is why you need to pass the size into the function.

Pointer Function

The name of a function is the starting address where the function resides in the memory, and therefore, can be treated as a pointer. We can pass a function pointer into function as well.

// Function-pointer declaration
return-type (* function-ptr-name) (parameter-list)

// Examples
double (*fp)(int, int)  // fp points to a function that takes two ints and returns a double (function-pointer)
double *dp;             // dp points to a double (double-pointer)
double *fun(int, int)   // fun is a function that takes two ints and returns a double-pointer

double f(int, int);      // f is a function that takes two ints and returns a double
fp = f;                 // Assign function f to fp function-pointer
int add(int n1, int n2) { return n1 + n2; }
int sub(int n1, int n2) { return n1 - n2; }

int arithmetic(int n1, int n2, int (*operation) (int, int)) {
   return (*operation)(n1, n2);
}

int main() {
   int number1 = 5, number2 = 6;

   // add
   cout << arithmetic(number1, number2, add) << endl;
   // subtract
   cout << arithmetic(number1, number2, sub) << endl;
}

Constant Pointer vs. Constant Pointed-to Data

Non-constant pointer to constant data: Data pointed to CANNOT be changed; but pointer CAN be changed to point to another data.

int i1 = 8, i2 = 9;
const int * iptr = &i1;  // non-constant pointer pointing to constant data
// *iptr = 9;   // error: assignment of read-only location
iptr = &i2;  // okay

Constant pointer to non-constant data: Data pointed to CAN be changed; but pointer CANNOT be changed to point to another data.


int i1 = 8, i2 = 9;
int * const iptr = &i1;  // constant pointer pointing to non-constant data
                         // constant pointer must be initialized during declaration
*iptr = 9;   // okay
// iptr = &i2;  // error: assignment of read-only variable

Constant pointer to constant data: Data pointed to CANNOT be changed; and pointer CANNOT be changed to point to another data.


int i1 = 8, i2 = 9;
const int * const iptr = &i1;  // constant pointer pointing to constant data
// *iptr = 9;   // error: assignment of read-only variable
// iptr = &i2;  // error: assignment of read-only variable

Non-constant pointer to non-constant data: Data pointed to CAN be changed; and pointer CAN be changed to point to another data.

int i1 = 8, i2 = 9;
int * iptr = &i1;  // non-constant pointer pointing to non-constant data
*iptr = 9;   // okay
iptr = &i2;  // okay

results matching ""

    No results matching ""