Detailed Explanation of C++ Pointers

https://www.cnblogs.com/ggjucheng/archive/2011/12/13/2286391.html

Concept of Pointers

A pointer is a special type of variable that stores a value interpreted as an address in memory. To understand a pointer, one must grasp four aspects: the type of the pointer, the type it points to, the value of the pointer (or the memory area it points to), and the memory area occupied by the pointer itself. Let us explain each aspect separately.

First, let’s declare a few pointers for examples:

Example 1:

int* ptr;  
char* ptr;  
int** ptr;  
int(*ptr)[3];  
int*(*ptr)[4];

Type of Pointer

From a syntactical perspective, if you remove the pointer name from the pointer declaration statement, the remaining part is the type of the pointer itself. Let’s look at the types of the pointers in Example 1:


int *ptr; // Pointer type is int *  
char *ptr; // Pointer type is char *  
int **ptr; // Pointer type is int **  
int (*ptr)[3]; // Pointer type is int(*)[3]  
int *(*ptr)[4]; // Pointer type is int *(*)[4]

How about that? Isn’t it simple to find out the type of a pointer?

Type Pointed to by the Pointer

When you access the memory area pointed to by a pointer, the type pointed to by the pointer determines how the compiler interprets the contents of that memory area.

From a syntactical standpoint, you only need to remove the pointer name and the pointer declaration symbol * to find out the type pointed to by the pointer. For example:


int *ptr; // Type pointed to is int  
char *ptr; // Type pointed to is char  
int **ptr; // Type pointed to is int *  
int (*ptr)[3]; // Type pointed to is int()[3]  
int *(*ptr)[4]; // Type pointed to is int *()[4]

In pointer arithmetic, the type pointed to by the pointer plays a significant role.

The type of the pointer (i.e., the type of the pointer itself) and the type pointed to by the pointer are two distinct concepts. As you become more familiar with C, you will find that separating the concept of “type” associated with pointers into “pointer type” and “type pointed to by the pointer” is one of the key points to mastering pointers. I have read many books and found that some poorly written ones mix these two concepts, leading to contradictions and confusion.

Value of the Pointer

The value of a pointer is the value stored by the pointer itself, which the compiler interprets as an address, not a general value. In a 32-bit program, the value of all types of pointers is a 32-bit integer, as memory addresses in a 32-bit program are all 32 bits long.

The memory area pointed to by the pointer starts from the memory address represented by the pointer’s value and has a length of sizeof(type pointed to). In the future, when we say a pointer’s value is XX, it is equivalent to saying that the pointer points to a memory area starting at address XX; when we say a pointer points to a certain memory area, it is equivalent to saying that the pointer’s value is the starting address of that memory area.

The memory area pointed to by the pointer and the type pointed to by the pointer are two completely different concepts. In Example 1, the type pointed to is already defined, but since the pointer has not been initialized, the memory area it points to does not exist or is meaningless.

In the future, whenever you encounter a pointer, you should ask: What is the type of this pointer? What is the type pointed to? Where does this pointer point?

Memory Area Occupied by the Pointer Itself

How much memory does the pointer itself occupy? You can find out by using the sizeof function on the pointer’s type. In a 32-bit platform, a pointer occupies 4 bytes.

The concept of the memory occupied by the pointer itself is very useful when determining whether a pointer expression is an lvalue.

Pointer Arithmetic

Pointers can be incremented or decremented by an integer. The meaning of this operation is different from the usual addition or subtraction of values. For example:

Example 2:

char a[20];   
int *ptr = a;   
...   ...   
ptr++;

In the above example, the pointer ptr is of type int*, and it points to type int, initialized to point to the integer variable a. In the next line, ptr is incremented by 1, and the compiler processes it as follows: it adds sizeof(int) to the value of ptr, which in a 32-bit program is 4. Since addresses are measured in bytes, the address pointed to by ptr increases by 4 bytes from the original address of variable a.

Since the length of the char type is one byte, ptr originally pointed to the first four bytes starting from the 0th element of array a, and now it points to the four bytes starting from the 4th element of array a.

We can use a pointer and a loop to traverse an array, as shown in the example:

Example 3:


int array[20];  
int *ptr = array;  
...  // Code for assigning values to the integer array is omitted.  
for(i=0; i<20; i++)  {          (*ptr)++;      ptr++;}

This example increments the values of each element in the integer array by 1. Since the pointer ptr is incremented in each loop, it can access the next element of the array each time.

Let’s look at another example:

Example 4:

char a[20];  
int *ptr = a;  
...  ...  
ptr += 5;

In this example, ptr is incremented by 5, and the compiler processes it as follows: it adds 5 times sizeof(int) to the value of ptr, which in a 32-bit program is 5 times 4 = 20. Since addresses are measured in bytes, ptr now points to an address that has moved 20 bytes higher than the address pointed to before the increment. In this example, before adding 5, ptr pointed to the first four bytes starting from the 0th element of array a, and after adding 5, ptr points outside the valid range of array a.

Although this situation may cause problems in application, it is syntactically valid. This also demonstrates the flexibility of pointers.

If in the previous example, ptr were decremented by 5, the processing would be similar, except that ptr’s value would be decreased by 5 times sizeof(int), and the new ptr would point to an address that has moved 20 bytes lower than the original ptr.

To summarize, when a pointer ptrOld is incremented by an integer n, the result is a new pointer ptrNew, which has the same type as ptrOld, and ptrNew points to the same type as ptrOld. The value of ptrNew will be increased by n times sizeof(type pointed to by ptrOld). This means that the memory area pointed to by ptrNew will be moved n times sizeof(type pointed to by ptrOld) bytes higher than the memory area pointed to by ptrOld. Similarly, when a pointer ptrOld is decremented by an integer n, the result is a new pointer ptrNew, which has the same type as ptrOld, and ptrNew points to the same type as ptrOld. The value of ptrNew will be decreased by n times sizeof(type pointed to by ptrOld), meaning that the memory area pointed to by ptrNew will be moved n times sizeof(type pointed to by ptrOld) bytes lower than the memory area pointed to by ptrOld.

Operators & and *

Here, & is the address-of operator, and * is known as the “indirection operator” in textbooks. The result of &a is a pointer, with the type being a’s type plus *, and the type pointed to being a’s type, while the address pointed to is a’s address. The result of *p varies widely. In general, the result of *p is the thing pointed to by p, which has the following characteristics: its type is the type pointed to by p, and the address it occupies is the address pointed to by p.

Example 5:

int a = 12;  
int b;  
int *p;  
int **ptr;  
p = &a; // &a results in a pointer, type is int*, points to type int, address is a's address.  
*p = 24; // *p's result here is of type int, occupying the address pointed to by p, which is clearly variable a.  
ptr = &p; // &p results in a pointer, type is p's type plus *, here it is int**. The type pointed to is p's type, which is int*. The address pointed to is the address of pointer p itself.  
*ptr = &b; // *ptr is a pointer, &b also results in a pointer, and since both pointers have the same type and pointed-to type, assigning *ptr with &b is perfectly fine.  
**ptr = 34; // *ptr's result is the thing pointed to by ptr, which is a pointer, and performing another * operation on this pointer results in an int type variable.

Pointer Expressions

If the final result of an expression is a pointer, then that expression is called a pointer expression. Here are some examples of pointer expressions:

Example 6:

int a, b;  
int array[10];  
int *pa;  
pa = &a; // &a is a pointer expression.  
int **ptr = &pa; // &pa is also a pointer expression.  
*ptr = &b; // *ptr and &b are both pointer expressions.  
pa = array;  
pa++; // this is also a pointer expression.

Example 7:

char *arr[20];  
char **parr = arr; // If arr is considered a pointer, then arr is also a pointer expression.  
char *str;  
str = *parr; // *parr is a pointer expression.  
str = *(parr + 1); // *(parr + 1) is a pointer expression.  
str = *(parr + 2); // *(parr + 2) is a pointer expression.

Since the result of a pointer expression is a pointer, it also possesses the four elements that pointers have: pointer type, type pointed to, memory area pointed to, and memory occupied by the pointer itself.

Now, when a pointer expression’s result pointer has clearly occupied memory, that pointer expression is an lvalue; otherwise, it is not an lvalue. In Example 7, &a is not an lvalue because it has not occupied clear memory. *ptr is an lvalue because *ptr has already occupied memory; in fact, *ptr is pointer pa, and since pa already has its own position in memory, *ptr certainly has its own position.

Relationship Between Arrays and Pointers

If you are not clear about the declaration of arrays, please refer to my previous article “How to Understand Complex Type Declarations in C and C++”. The name of an array can actually be viewed as a pointer. Let’s look at an example:

Example 8:

int array[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, value;  
...  ...  
value = array[0]; // can also be written as: value = *array;  
value = array[3]; // can also be written as: value = *(array + 3);  
value = array[4]; // can also be written as: value = *(array + 4);

In the above example, generally speaking, the array name array represents the entire array, with type int [10], but if we consider array as a pointer, it points to the 0th element of the array, with type int *, and the type pointed to is the type of the array elements, which is int. Therefore, *array equals 0 is not surprising. Similarly, array + 3 is a pointer to the 3rd element of the array, so *(array + 3) equals 3. The same applies to others.

Example 9:

char *str[3] = {  "Hello, this is a sample!",  "Hi, good morning.",  "Hello world"  };  
char s[80];  
strcpy(s, str[0]); // can also be written as strcpy(s, *str);  
strcpy(s, str[1]); // can also be written as strcpy(s, *(str + 1));  
strcpy(s, str[2]); // can also be written as strcpy(s, *(str + 2));

In the above example, str is an array of three elements, each of which is a pointer pointing to a string. If we treat the pointer array name str as a pointer, it points to the 0th element of the array, with type char *, and it points to the type char.

*str is also a pointer, with type char *, pointing to the type char, and it points to the address of the first character of the string “Hello, this is a sample!”, which is the address of ‘H’. str + 1 is also a pointer, pointing to the 1st element of the array, with type char *, pointing to the type char.

*(str + 1) is also a pointer, with type char *, pointing to the type char, and it points to the first character ‘H’ of “Hi, good morning.”, and so on.

Now, let’s summarize the issue of array names. When an array TYPE array[n] is declared, the array name array has two meanings: first, it represents the entire array, with type TYPE [n]; second, it is a pointer, with type TYPE *, pointing to the type TYPE, which is the type of the array elements. This pointer points to the memory area of the 0th element of the array, and this pointer occupies its own separate memory area, which is different from the memory area occupied by the 0th element of the array. The value of this pointer cannot be modified, meaning expressions like array++ are incorrect.

In different expressions, the array name array can play different roles.

In the expression sizeof(array), the array name array represents the entire array, so the sizeof function measures the size of the entire array.

In the expression *array, array plays the role of a pointer, so the result of this expression is the value of the 0th element of the array. sizeof(*array) measures the size of the array element.

In the expression array + n (where n = 0, 1, 2, …), array plays the role of a pointer, so the result of array + n is a pointer, with type TYPE *, pointing to the type TYPE, and it points to the nth element of the array. Therefore, sizeof(array + n) measures the size of the pointer type.

Example 10:

int array[10];  
int (*ptr)[10];  
ptr = &array;

In the above example, ptr is a pointer, with type int (*)[10], pointing to type int [10]. We initialize it with the address of the entire array. In the statement ptr = &array, array represents the entire array.

This section mentions the sizeof() function, so let me ask, does sizeof(pointer name) measure the size of the pointer’s own type or the size of the type it points to? The answer is the former. For example:

int (*ptr)[10];

Then in a 32-bit program, we have:

sizeof(int(*)[10]) == 4  
sizeof(int [10]) == 40  
sizeof(ptr) == 4

In fact, sizeof(object) measures the size of the object’s own type, not the size of any other type.

Relationship Between Pointers and Structure Types

You can declare a pointer to a structure type object.

Example 11:

struct MyStruct  {  
int a;  
int b;  
int c;  
}  MyStruct ss = {20, 30, 40}; // Declares a structure object ss and initializes its three members to 20, 30, and 40.  
MyStruct *ptr = &ss; // Declares a pointer to the structure object ss. Its type is MyStruct*, pointing to type MyStruct.  
int *pstr = (int*)&ss; // Declares a pointer to the structure object ss. However, its type and pointed-to type differ from ptr.

How can we access the three member variables of ss through pointer ptr?

Answer:

ptr->a;  
ptr->b;  
ptr->c;

Now, how can we access the three member variables of ss through pointer pstr?

Answer:

*pstr; // Accesses member a of ss.  
*(pstr + 1); // Accesses member b of ss.  
*(pstr + 2); // Accesses member c of ss.

Although I have debugged the above code on my MSVC++6.0, it is important to note that using pstr to access structure members is not standard. To illustrate why it is not standard, let’s see how to access the elements of an array through pointers:

Example 12:

int array[3] = {35, 56, 37};  
int *pa = array;

The method to access the three elements of array using pointer pa is:


*pa; // Accesses the 0th element  
*(pa + 1); // Accesses the 1st element  
*(pa + 2); // Accesses the 2nd element

From a formatting perspective, it is indeed similar to the non-standard method of accessing structure members through pointers.

All C/C++ compilers always store the elements of an array in contiguous memory, with no gaps between elements. However, when storing the members of a structure object, depending on the compilation environment, it may require alignment (e.g., word alignment or double word alignment), which may add several “padding bytes” between adjacent members, leading to potential gaps between members.

Therefore, in Example 12, even if pstr accesses the first member variable a of structure object ss, it cannot guarantee that (pstr + 1) will access member b, as there may be several padding bytes between members a and b, and *(pstr + 1) might just access those padding bytes. This also demonstrates the flexibility of pointers. If your goal is to see whether there are padding bytes between structure members, this is indeed a good method.

The correct method to access structure members through pointers should be as shown in Example 12 using pointer ptr.

Relationship Between Pointers and Functions

You can declare a pointer to a function.

int fun1(char*, int);  
int (*pfun1)(char*, int);  
pfun1 = fun1;  
....  ....  
int a = (*pfun1)("abcdefg", 7); // Calls the function through the function pointer.

Leave a Comment