The Difference Between Pointers and Array Notations in C/C++

Ilja Kostenko Oct 12, 2023
  1. C++ Array
  2. C++ Pointer
The Difference Between Pointers and Array Notations in C/C++

Pointers and arrays are undoubtedly one of the most important and complex aspects of C++. They support linked lists and dynamic memory allocation, and they allow functions to change the contents of their arguments.

C++ Array

An array is a set of elements of the same type accessed by the index - the ordinal number of the element in the array. For example:

int ival;

It defines ival as an int type variable and the instruction.

int ia[10];

It sets an array of ten int objects. Each of these objects, or array elements, can be accessed using the operation of taking an index.

ival = ia[2];

It assigns to the variable ival the value of an element of the array ia with index 2. Similarly

ia[7] = ival;

It assigns the ival value to the element with index 7.

An array definition consists of a type specifier, an array name, and a size. The size specifies the number of array elements (at least 1) and is enclosed in square brackets. The size of the array needs to be known already at the compilation stage, and therefore, it must be a constant expression, although it is not necessarily set by a literal.

The numbering of elements starts from 0, so for an array of 10 elements, the correct index range is not 1 to 10, but 0 to 9. Here is an example of sorting through all the elements of the array.

int main() {
  const int array_size = 10;
  int ia[array_size];
  for (int ix = 0; ix < array_size; ++ix) ia[ix] = ix;
}

When defining an array, you can explicitly initialize it by listing the values of its elements in curly brackets, separated by commas.

const int array_size = 3;
int ia[array_size] = {0, 1, 2};

If we explicitly specify a list of values, we may not specify the size of the array: the compiler itself will count the number of elements.

C++ Pointer

A pointer is an object containing the address of another object and allowing indirect manipulation of this object. Pointers are usually used to work with dynamically created objects, build related data structures, such as linked lists and hierarchical trees, and pass large objects– arrays and class objects– to functions as parameters.

Each pointer is associated with some type of data. Their internal representation does not depend on the internal type: both the size of memory occupied by an object of the pointer type and the range of values are the same. The difference is how the compiler perceives the addressable object. Pointers to different types may have the same value, but the corresponding types’ memory area may be different.

Here are some examples:

int *ip1, *ip2;
complex<double> *cp;
string *pstring;
vector<int> *pvec;
double *dp;

The pointer is indicated by an asterisk before the name. In defining variables by a list, an asterisk should be placed before each pointer (see above: ip1 and ip2). In the example below, lp is a pointer to an object of type long, and lp2 is an object of type long.

long *lp, lp2;

In the following case, fp is interpreted as a float object, and fp2 is a pointer to it:

float fp, *fp2;

Let a variable of type int be given:

int ival = 1024;

The following are examples of defining and using pointers to int pi and pi2.

[//] pi is initialized with the null address
int *pi = 0;
[//] pi2 is initialized with the address ival
int *pi2 = &ival;
[//] correct: pi and pi2 contain the ival address
pi = pi2;
[//] pi2 contains the null address
pi2 = 0;

A pointer cannot be assigned a value that is not an address.

[//] error: pi cannot take the value int
pi = ival

Similarly, you cannot assign a value to a pointer of one type that is the address of an object of another type if the following variables are defined.

double dval;
double *ps = &dval;

Then both assignment expressions given below will cause a compilation error.

[//] compilation errors
[//] invalid assignment of data types: int* <== double*
pi = pd
pi = &dval;

It’s not that the variable pi cannot contain the addresses of the object dval - the addresses of objects of different types have the same length. Such address mixing operations are deliberately prohibited because the compiler’s interpretation of objects depends on the type of pointer.

Of course, there are cases when we are interested in the value of the address itself, not the object to which it points (let’s say we want to compare this address with some other). To resolve such situations, we can introduce a special invalid pointer, which can point to any data type, and the following expressions will be correct:

[//] correct: void* can contain
[//] addresses of any type
void *pv = pi;
pv = pd;

The type of the object pointed to by the void* is unknown, and we cannot manipulate this object. All we can do with such a pointer is assign its value to another pointer or compare it with some address value.

To access an object with its address, you need to apply a dereference operation, or indirect addressing, indicated by an asterisk (*). For example,

int ival = 1024;
, ival2 = 2048;
int *pi = &ival;

We can read and store the value of ival by applying the dereference operation to the pointer pi.

[//] indirect assignment of the ival variable to the ival2 value
*pi = ival2;
[//] value indirect use of variable value and pH value value
*pi = abs(*pi); // ival = abs(ival);
*pi = *pi + 1; // ival = ival + 1;

When we apply the operation of taking an address (&) to an object of type int, we get a result of type int*

int *pi = &ival;

If the same operation is applied to an object of type int* (pointer to type int C) and we get a pointer to a pointer to type int and, i.e. type int**. int** is the address of an object that contains the address of an object of the int type.

Dereferencing ppi, we get an int* object containing the ival address. To get the ival object itself, the dereference operation must be applied to the PPI twice.

int **ppi = &pi;
int *pi2 = *ppi;
cout << "ival value\n"
     << "explicit value: " << ival << "\n"
     << "indirect addressing: " << *pi << "\n"
     << "double indirect addressing: " << **ppi << "\n"
     << end;

Pointers can be used in arithmetic expressions. Pay attention to the following example, where two expressions perform entirely different actions.

int i, j, k;
int *pi = &i;
[//] i = i + 2
*pi = *pi + 2;
[//] increasing the address contained in pi by 2
pi = pi + 2;

You can add an integer value to the pointer and also subtract from it. Adding 1 to the pointer increases its value by the size of the memory area allocated to the object of the corresponding type. If the type char occupies 1 byte, int – 4 and double - 8, then adding 2 to the pointers to the character, integer, and double will increase their value by 2, 8, and 16, respectively.

How can this be interpreted? If objects of the same type are located in memory one after another, then increasing the pointer by 1 will cause it to point to the next object. Therefore, arithmetic operations with pointers are most often used when processing >arrays; in any other case, they are hardly justified.

Here is a typical example of using address arithmetic when iterating through array elements using an iterator:

int ia[10];
int *iter = &ia[0];
int *iter_end = &ia[10];
while (iter != iter_end) {
  do_the event_ with_(*iter);

Related Article - C++ Pointer

Related Article - C++ Array