C Pointer
Although I haven’t usually used the pointers in recent years, sometimes I would be lost in C pointers when meeting some complex C pinter code.
Whenever I don’t understand, I need to search and learn the C pointers again. This time, I want to conclude C pointers and list the most common C pointer usages and mistakes we usually make.
Special Pointers NULL
and void *.
In C language, we have two particular pointers, NULL
and void *,
the NULL
means the pointer has not initialized. When you access a NULL
pinter, there will be nothing to happen. Notice that the NULL must be capital. The null is a standard identifier in C.
In many standard libraries, they will check input pinter params. If their values are NULL, the function will prompt warnings or returns directly. So I suggest that we should also check input pinter params. Dong this can make our functions more robust.
When should we use the NULL pointer? The most common condition is when you declare a pinter now and assign a value to it later. If you declare a pointer like below, you would get an error when writing it or get a strange value when reading it.
1 | int main() { |
If you assign a NULL to the pointer str
when declaring it, the printf
will directly output (null)
and return the function.
1 | int main(){ |
In fact, the NULL
is a macro defined in stdio.h
, its detailed define is #define NULL ((void *)0)
. The NULL
points to a void point whose address is 0
. The low part of the memory addresses is generally reserved, so the system can easily confirm whether the address is available.
The void
is another particular keyword. A void *
pinter is a valid pointer, but you don’t know its pinter type. The dynamic memory allocating function of C will return a void pointer.
We need manually transform its type when using the pointer.
1 | int main(){ |
Normal pinter & Array pointer
We all have learned the name of an array is the pointer that points to the beginning address of the array, we may think the pointer and the array are the same, but I’m afraid that’s not right. Arrays have their types
1 |
|
An array contains a set of values, but it doesn’t have any end flag. When we set the a' to the
p,the
pis just an
int pointer, its address point to the first item of the array. We use
sizeofto get the size of the point
p` can only get the memory size of an int pointer. Wherever it points to, you can’t calculate the length of an array.
If the type of point p is int *
, then the type of point a is int [6]
. The int [6]
is an array type, which means there are six integer values in a variable, so when we use sizeof to get the size of the pointer a, we get the result is the total size of six integers.
In some cases, An array type would be automatically converted to a normal pointer. For example, we read values through array subscript or pass an array to a function.
1 | int main() { |
The a' in
a[1]is a normal integer pointer. The compiler will convert the subscript to the expression
*(a + 1), you can consider the
[]is an operator, its expression is
x[y] = *(x + y), so you can rewrite the expression
a[1]to
1[a]`, they have the same effect, but the latter may be unreadable.
The other situation is you pass an array to a function.
1 |
|
Whatever type you define the param, you cannot get the length of the array. The compiler always passes a pointer, not copying the whole array to a function. An array can have a large number of items. If we copy its value when passing it to a function will waste a lot of memory. If you want to get the length of an array, you should pass the length through another param.
1 |
|
Understand a complex pointer
Before analyzing a complex pointer, we review some common pointer types.
int *p
: The p is a int pointerint **p
: The p is a pointer pointer, the p point to another pointer.int p[n]
: The p is an array, you can make it as a int pointer in calculation.int *p[n]
: The p is an array, The type of its items is int pointer.int (*p)[n]
: The p is a pointer, it point to an int array.int (*p)()
: The p is a pointer, it point to a function.
We also should know the precedence of the operators in pointer expression.
()
: highest precedencesuffix [] and ()
: medium precedence (array and function)prefix *
: lowest precedence
In the pointer int *p[n]
, the precedence of []
if higher than the *
, you can rewrite the expression to int *(p[n])
, so we know the p is an array.
Practice
-
char *(* c[10])(int **p)
- Find the variable
char *(*
c[10])(int **p)
. We know the c is a pointer array. Its items are pointers. - The suffix
(int **p)
means it’s a function. The function needs a pointer param pointing to an int array and returning a char pointer. - Finally, we can get the c is a pointer array, its items are function pointers, the type of the function is
char *f(int **p)
.
- Find the variable
-
int (*(*(*p)(int *))[5])(int *)
This expression is more complex, but it isn’t difficult for you if you can remember the rules above.- Find the variable
int (*(*(*
p)(int *))[5])(int *)
. We know the p is a pointer. - The suffix
int (*(
*(*p)
(int *))[5])(int *)
means the p is a function pinter. It needs an int pointer param and returns a pointer. - What does the result pinter point to?
int (
*(*(*p)(int *))
[5])(int *)
It pointed to a pointer array, the length of the array is 5. - The type of item in the pointer array is the function pointer. The function needs an int pointer param and returns an int value.
- Find the variable
Conclusion
The pointer is the most crucial concept in C. It’s very flexible but always makes questions more complex. In fact, the basis of many languages is using pointers. Understanding pointers will help us to understand some data structures and algorithms further.