Author: ggjucheng
Link: https://www.cnblogs.com/ggjucheng/archive/2011/12/13/2286391.html
Concept of Pointers
A pointer is a special 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’s explain each aspect.
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. This is the type that the pointer itself possesses. Let’s look at the types of the pointers in Example 1:
int *ptr; // The type of the pointer is int * char *ptr; // The type of the pointer is char * int **ptr; // The type of the pointer is int ** int (*ptr)[3]; // The type of the pointer is int(*)[3] int *(*ptr)[4]; // The type of the pointer 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 perspective, 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; // The type pointed to by the pointer is int char *ptr; // The type pointed to by the pointer is char int **ptr; // The type pointed to by the pointer is int * int (*ptr)[3]; // The type pointed to by the pointer is int()[3] int *(*ptr)[4]; // The type pointed to by the pointer is int *()[4]
The type pointed to by the pointer plays a significant role in pointer arithmetic.
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 “type of the pointer” 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, with a length of sizeof(type pointed to by the pointer). 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 by the pointer 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 by the pointer? 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, the pointer itself 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
A pointer can be added to or subtracted from 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, the pointer ptr is incremented by 1, and the compiler processes it as follows: it adds sizeof(int) to the value of pointer 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 in the higher address direction from the original address of variable a.
Since the length of the char type is one byte, originally ptr 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 by 1 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 pointer ptr, which in a 32-bit program is 5 times 4 = 20. Since addresses are measured in bytes, the address pointed to by ptr now moves 20 bytes in the higher address direction compared to the address pointed to by ptr after adding 5. 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 to an address 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 moves 20 bytes in the lower address direction compared to the original ptr.
To summarize, when a pointer ptrOld is added to 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 in the higher address direction compared to the memory area pointed to by ptrOld. When a pointer ptrOld is subtracted 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) bytes, meaning that the memory area pointed to by ptrNew will be moved n times sizeof(type pointed to by ptrOld) bytes in the lower address direction compared to 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* and the type pointed to being a’s type, and the address pointed to is the address of a. 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 its type is int, the address it occupies is the address pointed to by p, clearly, *p is variable a. ptr=&p;//&p results in a pointer, the type of this pointer is p's type plus *, here it is int**. The type pointed to by this pointer is p's type, which is int*. The address pointed to by this pointer is the address of pointer p itself. *ptr=&b;//*ptr is a pointer, &b's result is also a pointer, and since these two pointers have the same type and pointed-to types, assigning *ptr with &b is perfectly fine. **ptr=34;//*ptr's result is the thing pointed to by ptr, which is a pointer, and doing 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, pointer expressions also possess the four elements that pointers have: the type of the pointer, the type pointed to by the pointer, the memory area pointed to by the pointer, and the 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 yet occupied clear memory. *ptr is an lvalue because the pointer *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 <
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 the type being int [10], but if we consider array as a pointer, it points to the 0th element of the array, with the type being int *, and the type pointed to being the type of the array elements, which is int. Therefore, it is not surprising that *array equals 0. 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 the type being char*, and it points to the type char.
*str is also a pointer, with the type being char*, and it points to the type char, pointing 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 the type being char*, and it points to the type char.
*(str+1) is also a pointer, with the type being char*, and it points to the type char, pointing to the first character ‘H’ of the string “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 its type being TYPE [n]; second, it is a pointer, with its type being TYPE*, pointing to the type TYPE, which is the type of the array elements, and this pointer points to the memory area of the 0th element of the array. This pointer occupies its own separate memory area, and note that it 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 acts as 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 acts as a pointer, so the result of array+n is a pointer, with its type being TYPE*, pointing to the type TYPE, pointing to the n-th 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 its type being int (*)[10], pointing to the type int [10]. We initialize it with the address of 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};// Declared a structure object ss and initialized its three members to 20, 30, and 40. MyStruct *ptr=&ss;// Declared a pointer to the structure object ss. Its type is MyStruct*, and it points to type MyStruct. int *pstr=(int*)&ss;// Declared a pointer to the structure object ss. However, its type and the type it points to are different 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;// Accessed member a of ss. *(pstr+1);// Accessed member b of ss. *(pstr+2);// Accessed member c of ss.
Although I have debugged the above code on my MSVC++6.0, it should be noted 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;// Accessed the 0th element *(pa+1);// Accessed the 1st element *(pa+2);// Accessed 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, under certain compilation environments, it may require alignment, which may add several “padding bytes” between two 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, because there may be several padding bytes between members a and b, and it is possible that *(pstr+1) accesses 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);// Call the function through the function pointer.
You can also use pointers as function parameters. In the function call statement, you can use pointer expressions as actual parameters.