T.TAO
Back to Blog
/6 min read/Others

C++ Programming #3 Pointers and Array

C++ Programming #3 Pointers and Array

This note mainly covers key concepts of pointers and arrays in C++.

Pointers

Basic Concepts

A pointer (指针) is an important concept in programming languages. A pointer stores a memory address and directly accesses data in memory. Important things bear repeating three times.

The value of a pointer is an address! The value of a pointer is an address! The value of a pointer is an address!

The value at the address a pointer points to is the actual data. To put it simply, a pointer is like a house number—we use the house number to refer to the person living inside.

Declaration and Usage

The syntax for declaring a pointer to a variable of some type is:

Plain Texttype * ptr;

// or type* ptr;
// or type *ptr;

All three formats above are valid and express exactly the same meaning. Formally, type* emphasizes that ptr is of pointer type, while type * emphasizes that (*ptr) is of type type. The operator (*) is overloaded here to indicate that the variable (ptr) is a pointer.

For example, to declare a pointer to an int variable:

Plain Textdouble *ptr;

Note that although a pointer's value is an address, and addresses can often be treated as integers, a pointer is not an integer type. Integers can be added, subtracted, multiplied, and divided; addresses can perhaps be added or subtracted, but multiplying or dividing two addresses is meaningless. Therefore, simply assigning an integer to a pointer is meaningless. However, you can make it meaningful through explicit casting.

Plain Textint *pt;
// pt = 0xB8000000; // type mismatch error
pt = (int *)0xB8000000; // type match

Address Operator &

For a value variable, we can use & to expose its address. Then we can assign that address to a pointer.

Plain Textint *ptr;
int value = 5;
ptr = &value;

After that, the following values will be equal:

Plain Text*ptr == value;
ptr == &value;

Using Pointers to Track Allocated Memory

The limitation of using the address operator (&) is that the pointer must correspond to a value variable. Value variables are allocated memory by the program when declared, but often the pointers we use are not paired with a value type. At runtime, we can manually allocate memory on the heap and then use a pointer to point to that heap location.

In C, we commonly use the library function malloc(). In C++, malloc() can also be used, but the new keyword is used more often. For example:

Plain Textint *ptr = new int;

// C type
// int *ptr = (int *)malloc(sizeof(int));
// or if you know sizeof(int) == 4
// int *ptr = (int *)malloc(4);

Here, new is followed by the type keyword int, then new finds a memory block of the correct size and returns its address, which becomes the value of ptr. For malloc, you must manually specify the size of int and perform the appropriate type cast; otherwise, the pointer may not behave according to the type of the value (we will discuss this in detail later).

In C/C++, once we request a new memory block with new (or malloc), we must release it when it is no longer needed; otherwise, a Memory Leak (内存泄漏) occurs. Release is done with delete for new and free for malloc. They must be used in pairs: every new must have a matching delete, and every malloc must have a matching free.

Plain Textint *ptr = new int;
//... after some codes using int
delete ptr;

Note that what is released is the memory block, not the pointer. The variable ptr still exists and cannot be redeclared. You can point it to a new memory block.

Also, do not try to free a memory block that has already been freed; the result is undefined in C++. Nor should you use delete to free memory obtained by declaring a variable (the address associated with the & operator).

Arrays

Creation and Release

Static arrays

In C++, creating a static array is straightforward:

Plain TexttypeName arrayName [arraySize];

Here, arraySize cannot be a variable; it must be a constant or a value marked as const. Therefore, we say the array is static.

Examples of static array declaration and initialization are shown below; any of these formats can be used:

Plain Textint cards[4] = {3, 4, 5, 6};
int hands[] = {3, 4, 5, 6}; // compiler will automatically count the size of the array

float balance[100] {}; // all elements set to 0;
double earnings[4] {1, 2, 3, 4};

Dynamic arrays

In C++, if you create an array using the static array approach above, memory is allocated for it when the program is compiled. Whether the program uses the array or not, it will occupy memory. This memory allocation strategy is called static binding (静态联编).

Alternatively, we can use the new keyword to create a dynamic array. Dynamic arrays use dynamic binding; they are added to the program at compile time, and the program determines their length at runtime. When they are no longer needed, release them with delete followed by square brackets.

Plain Textint *parray = new int[10];
// ... after some codes using parray
delete[] parray;

Here, the [] in delete[] tells the program to release the entire array, not just the element pointed to by parray (the address of the first element). Note that we use the pointer operator (*) to denote the address of the array's first element.

Pointer Arithmetic

In C/C++, pointers and arrays are largely equivalent. This is due to pointer arithmetic.

Definition

For a pointer p of type T, suppose p points to address X. Then we have the following two identities:

Plain Text1. *(p+i) == X + sizeof(T) * i;
2. *(&Expr) == Expr;

Example: suppose we have the following array:

Plain Textint A[5] = {1,2,3,4,5};

The output of each print statement is shown in the comments below:

Plain Textcout << A;
// 0x7ffd9a04f880

cout << A[0];
// 1

cout << *A;
// 1

cout << &A[0];
// 0x7ffd9a04f880

cout << A+1;
// 0x7ffd9a04f884

cout << A[1];
// 2

cout << *(A+1);
// 2

cout << &A[1];
// 0x7ffd9a04f884

cout << &A[0] + sizeof(int) * 1;
// 0x7ffd9a04f884

This clarifies the relationship between arrays and pointers. C++ interprets an array name as a pointer—or equivalently, as the address of the array's first element. It interprets A[i] as *(A+i).

Array Address

When taking the address of the entire array, the array name is not interpreted as its address, but as the address of the whole array.

This is a bit subtle, but we can understand it through this example:

Plain Textshort B[10];

B is a short array of length 10. Assuming each short is 2 bytes, B occupies 20 bytes.

In this case:

  • B+1: adds 2 to the address (from the base address of B).
  • &B + 1: adds 20 to the address.