C Pointers Explained
Category Programming Techniques
Preface: Understanding Complex Types
To understand pointers, you will inevitably encounter some complex types, so let me first explain how to fully understand a complex type. In fact, it is quite simple to understand complex types. There are many operators in a type, and they also have precedence like ordinary expressions, which is the same as the precedence of operations. So I have summarized the following principles: start from the variable name, combine according to the operator precedence, and analyze step by step.
Now let's start with simple types and analyze gradually:
int p;
-- This is a regular integer variable.int *p;
-- Start from p, first combine with*
, indicating that p is a pointer, and then combine with int, indicating that the type of the content pointed to by the pointer is int. So p is a pointer that returns integer data.int p[3]
-- Start from p, first combine with[]
, indicating that p is an array, and then combine with int, indicating that the elements in the array are integers, so p is an array composed of integer data.int *p[3];
-- Start from p, first combine with[]
because its precedence is higher than*
, so p is an array, then combine with *, indicating that the elements in the array are of pointer type, and then combine with int, indicating that the content pointed to by the pointer is of integer type, so p is an array composed of pointers that return integer data.int (*p)[3];
-- Start from p, first combine with *, indicating that p is a pointer, then combine with [] (the step with "()" can be ignored, it's just to change precedence), indicating that the content pointed to by the pointer is an array, and then combine with int, indicating that the elements in the array are integers. So p is a pointer pointing to an array composed of integer data.int **p;
-- Start from p, first combine with *, indicating that p is a pointer, then combine with * again, indicating that the element pointed to by the pointer is a pointer, and then combine with int, indicating that the element pointed to by this pointer is integer data. Since second-level pointers and higher-level pointers are rarely used in complex types, we will not consider multi-level pointers in more complex types later, and only consider first-level pointers at most.int p(int);
-- Start from p, first combine with (), indicating that p is a function, then analyze inside the (), indicating that the function has an integer variable as a parameter, and then combine with the outer int, indicating that the function returns an integer data.int (*p)(int);
-- Start from p, first combine with the pointer, indicating that p is a pointer, then combine with (), indicating that the pointer points to a function, then combine with the int inside (), indicating that the function has an int parameter, and then combine with the outermost int, indicating that the function's return type is integer. So p is a pointer to a function with an integer parameter and an integer return type.int *(*p(int))[3];
-- You can skip this type for now, it's too complex. Starting from p, first combine with (), indicating that p is a function, then go inside () and combine with int, indicating that the function has an integer variable as a parameter, then combine with the outer *, indicating that the function returns a pointer, then to the outermost layer, first combine with [] indicating that the returned pointer points to an array, then combine with *, indicating that the elements in the array are pointers, and then combine with int, indicating that the pointers point to integer data. So p is a function with an integer parameter that returns a pointer to an array composed of integer pointer variables.
That's about it, and that's our task. Understanding these types makes other types a piece of cake for us. However, we generally don't use too complex types as they greatly reduce the readability of the program. Please use them sparingly, the types mentioned above are enough for us to use.
I. A Detailed Discussion on Pointers
A pointer is a special variable that stores a value interpreted as an address in memory. To understand a pointer, you need to understand four aspects of it: the type of the pointer, the type pointed to by the pointer, the value of the pointer or the memory area pointed to by the pointer, and the memory area occupied by the pointer itself. Let's explain each one separately.
First, let's declare a few pointers as examples:
Example When a pointer ptrold is incremented (or decremented) by an integer n, the result is a new pointer ptrnew. The type of ptrnew is the same as the type of ptrold, and the type pointed to by ptrnew is also the same as the type pointed to by ptrold. The value of ptrnew will be increased (or decreased) by n times the size of the type pointed to by ptrold, in bytes. In other words, the memory area pointed to by ptrnew will move n times the size of the type pointed to by ptrold towards the higher (or lower) address direction compared to the memory area pointed to by ptrold. Pointers cannot be added together; this is an illegal operation because the result of the addition points to an unknown location and is meaningless. Pointers can be subtracted from each other, but they must be of the same type, which is generally used in arrays and will not be discussed further.
III. Operators & and *
Here, & is the address-of operator, and * is the dereference operator.
The result of &a
is a pointer, with the pointer's type being the type of a with an added *, and the type pointed to by the pointer is the type of a. The address pointed to by the pointer is, of course, the address of a.
The result of *p
is quite diverse. In summary, 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:
int a=12; int b; int *p; int **ptr;
p=&a; //The result of &a is a pointer, the type is int*, the type pointed to is
//int, the address pointed to is the address of a.
*p=24; //The result of *p, here its type is int, the address it occupies is
//the address pointed to by p, obviously, *p is the variable a.
ptr=&p; //The result of &p is a pointer, this pointer's type is p's type with an added *,
//here it is int**. The type pointed to by this pointer is p's type, here it is int*.
//The address pointed to by this pointer is the address of the pointer p itself.
*ptr=&b; //*ptr is a pointer, and the result of &b is also a pointer, and these two pointers
//have the same type and type pointed to, so using &b to assign to *ptr is unproblematic.
**ptr=34; //The result of *ptr is the thing pointed to by ptr, here it is a pointer,
//doing another * operation on this pointer, the result is a variable of type int.
IV. Pointer Expressions
If the result of an expression is a pointer, then this expression is called a pointer expression.
Here are some examples of pointer expressions:
Example:
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.
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, the pointer expression also has the four elements that a pointer has: 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.
Well, when the result pointer of a pointer expression has clearly occupied the memory of the pointer itself, then this pointer expression is a lvalue; otherwise, it is not a lvalue. In example seven, &a is not a lvalue because it has not yet occupied explicit memory. *ptr is a lvalue because the pointer *ptr has already occupied memory; in fact, *ptr is the pointer pa, and since pa already has its own place in memory, *ptr certainly has its own place as well.
V. The Relationship Between Arrays and Pointers
The name of an array can actually be seen as a pointer. See the example below:
int array[10]={0,1,2
In this example, the function fun calculates the sum of the ASCII values of each character in a string. As previously mentioned, the name of an array is also a pointer. When str is passed as an actual argument to the formal parameter s in the function call, the value of str is actually passed to s, and the address pointed to by s is consistent with the address pointed to by str. However, str and s each occupy their own storage space. Performing a self-increment operation of 1 on s inside the function does not mean that str is also incremented by 1 at the same time.
---
## Eight, Pointer Type Conversion
When we initialize a pointer or assign a value to a pointer, the left side of the assignment operator is a pointer, and the right side is a pointer expression. In the examples we have given before, in most cases, the type of the pointer and the type of the pointer expression are the same, and the type pointed to by the pointer and the type pointed to by the pointer expression are the same.
float f=12.3; float *fptr=&f; int *p;
In the above example, if we want the pointer p to point to the real number f, what should we do?
Is it with the following statement?
p=&f;
No. Because the type of pointer p is int *, and the type it points to is int. The result of the expression &f is a pointer, the type of the pointer is float *, and the type it points to is float.
The two are inconsistent, and the direct assignment method is not feasible. At least in my MSVC++6.0, the assignment statement for pointers requires the types on both sides of the assignment operator to be consistent, and the types pointed to are also consistent. I haven't tried it on other compilers, but you can try it. To achieve our goal, we need to perform a "forced type conversion":
p=(int*)&f;
If there is a pointer p, and we need to change its type and the type it points to to `TYPE *TYPE`, then the syntax is: `(TYPE *)p`
This forced type conversion results in a new pointer, the type of this new pointer is `TYPE *`, the type it points to is `TYPE`, and the address it points to is the address pointed to by the original pointer.
The original pointer p's attributes are not modified at all. (Remember this)
If a function uses a pointer as a formal parameter, then in the process of combining the actual and formal parameters in the function call statement, the types must be consistent, otherwise, a forced conversion is needed:
void fun(char); int a=125,b; fun((char)&a); void fun(chars) { char c; c=(s+3);(s+3)=(s+0);(s+0)=c; c=(s+2);(s+2)=(s+1);*(s+1)=c; }
Note that this is a 32-bit program, so the int type occupies four bytes, and the char type occupies one byte. The function fun reverses the order of the four bytes of an integer. Notice that in the function call statement, the result of the actual parameter &a is a pointer, its type is int *, and the type it points to is int. The type of this pointer in the formal parameter is char *, and the type it points to is char. In this way, in the process of combining the actual and formal parameters, we must perform a conversion from int * type to char * type.
**Combining this example, we can proceed as follows:**
Imagine the compiler's conversion process: the compiler first constructs a temporary pointer char *temp, then executes temp=(char *)&a, and finally passes the value of temp to s. So the final result is: the type of s is char *, the type it points to is char, and the address it points to is the first address of a.
We already know that the value of a pointer is the address it points to. In a 32-bit program, the value of a pointer is actually a 32-bit integer.
Can we assign the value of an integer directly to a pointer as the value of the pointer? Like the following statement:
unsigned int a; TYPE *ptr; //TYPE is int, char, or structure types, etc. a=20345686; ptr=20345686; //Our goal is to make the pointer ptr point to the address 20345686
ptr=a; //Our goal is to make the pointer ptr point to the address 20345686 //Compile it. The results show that the last two statements are all wrong. So can't we achieve our