Understanding C Language Pointers: A Comprehensive Guide

Click on “Beginner Learning Visuals” above to choose to add “Star” or “Top

Essential knowledge delivered promptly.

Editor’s Recommendation

Pointers are crucial in C. To fully understand pointers, one must not only be proficient in C but also have fundamental knowledge of computer hardware and operating systems.

Reprinted from丨Embedded Intelligence Bureau

Understanding C Language Pointers: A Comprehensive Guide

Why Do We Need Pointers?

Pointers solve some fundamental problems in programming.
The use of pointers allows different regions of code to easily share memory data. Of course, friends can also achieve the same effect by copying data, but this is often inefficient. For large data types such as structures, copying consumes considerable performance. However, using pointers can effectively avoid this issue because any type of pointer occupies the same number of bytes (depending on the platform, it could be 4 bytes, 8 bytes, or other possibilities).
Pointers enable the construction of complex linked data structures, such as linked lists, binary trees, etc.
Some operations must use pointers. For example, manipulating heap memory allocated with malloc. Additionally, in all function calls in C, value passing is “by value.” If we want to modify the object passed in a function, we must do so through the pointer of that object.

Understanding C Language Pointers: A Comprehensive Guide

How Does a Computer Fetch Instructions from Memory?
A computer’s bus can be divided into three types: data bus, address bus, and control bus. Here, we will not describe the control bus. The data bus is used for transmitting data information. The bit width of the data bus is usually consistent with the CPU’s word length.
Generally, the bit width of the data bus is equal to the length of the current machine’s int value. For example, on a 16-bit machine, the length of int is 16 bits, while on a 32-bit machine, it is 32 bits. This is the maximum amount of data that a computer can read or write in a single instruction. If it exceeds this value, the computer will require multiple accesses. This is why we say that a 64-bit machine performs 64-bit data operations more efficiently than a 32-bit machine, because a 32-bit machine requires two fetches and operations, while a 64-bit machine only needs one!
The address bus is specifically used for addressing. The CPU accesses data through this address and then transmits the data located at that address via the data bus, with the transmission length being equal to the bit width of the data bus. The width of the address bus determines the size of the memory space that the CPU can directly address. For instance, if the CPU’s bus length is 32 bits, its maximum directly addressable space is 2^32 KB, which is 4 GB.
This is why we commonly say that the maximum memory limit supported by a 32-bit CPU is 4 GB (of course, in reality, it supports less than this value because part of the address space will be mapped to some external I/O devices and virtual memory. Now, with some new technologies, 32-bit machines can support more than 4 GB of memory, but this is beyond the scope of this discussion).
Generally, the width of a computer’s address bus and data bus is the same. When we say a 32-bit CPU, both the data bus and address bus widths are 32 bits.
When a computer accesses certain data, it must first transmit the location of the data to be stored or read through the address bus, and then transmit the data to be stored or read through the data bus. Generally, the bit count of an int type is equal to the width of the data bus, and the bit count of a pointer is equal to the width of the address bus.
Basic Access Unit of a Computer
Anyone who has studied C knows that among the basic data types in C, char has the smallest bit count, which is 8 bits. We can consider that a computer accesses data in units of 8 bits, or 1 byte. Data smaller than one byte must be accessed through bit manipulation.
Memory Access Methods
As shown in Figure 1, when a computer accesses data, it does so in units of bytes, so it can be considered that each access starts from the p-th byte. The length of the access will be calculated by the compiler based on the actual type, which will be discussed later.
Understanding C Language Pointers: A Comprehensive Guide
Memory Access Methods
If you want to learn more, refer to the principles of computer organization and compilation.
sizeof Keyword
The sizeof keyword is used by the compiler to calculate the length of certain types of data in bytes. For example:
sizeof(char)=1;         sizeof(int)=4;
The value of sizeof(Type) is calculated at compile time and can be considered a constant!

Understanding C Language Pointers: A Comprehensive Guide

What Is a Pointer?
We know that in C, an array is a type of data that can be specifically categorized into int arrays, double arrays, char arrays, etc. Similarly, the concept of pointers also refers to a type of data, including int pointer types, double pointer types, char pointer types, etc.
Typically, we use int type to store some integer data, such as int num = 97, and we also use char to store characters: char ch = ‘a’.
We must also understand that any program data loaded into memory has its address, which is the pointer. To store the address of a data item in memory, we need a pointer variable.
Thus: a pointer is the address of program data in memory, while a pointer variable is used to store these addresses.
Understanding C Language Pointers: A Comprehensive Guide
In my personal understanding, a pointer can be thought of as an int type, except that it stores the memory address instead of ordinary data. If we denote this address as p, it means that the data is stored at the p-th byte of memory.
However, we cannot perform various arithmetic operations on pointer data like we do with int types, as the compiler does not allow this, as it can be very dangerous!
Figure 2 illustrates pointers, the value of a pointer is the address where the data is stored, so we say that a pointer points to the location where the data is stored.
Understanding C Language Pointers: A Comprehensive Guide

Understanding C Language Pointers: A Comprehensive Guide

Length of Pointers
We define a pointer in this way:
Type *p;
We say that p is a pointer to type Type, where Type can be any type, including basic types such as char, short, int, long, etc., as well as pointer types, for example, int *, int **, or more levels of pointers, and it can also be structures, classes, or functions, etc. Thus, we say:
int * is a pointer to int type;
int **, which is (int *) *, is a pointer to int * type, which is a pointer to a pointer;
int ***, which is (int **) *, is a pointer to int ** type, which is a pointer to a pointer to a pointer;
… I think you should understand.
struct xxx *, is a pointer to struct xxx type;
Actually, saying all this is just to hope that when you see pointers, you won’t be intimidated by something like int ***, as mentioned earlier, pointers are just pointers to certain types, we only look at the last * sign, the preceding ones are just type types.
Careful readers should have noticed that in the section “What is a Pointer,” it has been stated that the length of a pointer is equal to the CPU’s bit count, and most CPUs are 32 bits, so we say that the length of a pointer is 32 bits, which is 4 bytes! Note: The length of any pointer is 4 bytes, regardless of the type of pointer! (Of course, on a 64-bit machine, you should measure it yourself, it should be 8 bytes…)
Thus:
Type *p;
sizeof(p) will yield 4, where Type can be any type, char, int, long, struct, class, int **…
In the future, when you see sizeof(char*), sizeof(int *), sizeof(xxx *), you should ignore it and always write 4; as long as it is a pointer, the length is 4 bytes, and do not be confused by the type!

Understanding C Language Pointers: A Comprehensive Guide

Why Do Program Data Have Their Own Addresses?
To clarify this issue, we need to understand memory from the perspective of the operating system.
The view of memory from a computer technician’s perspective is that memory physically consists of a set of DRAM chips.
Understanding C Language Pointers: A Comprehensive Guide
As a programmer, we do not need to understand the physical structure of memory; the operating system combines hardware such as RAM with software to provide an abstraction for memory usage to programmers. This abstraction mechanism allows programs to use virtual memory instead of directly manipulating and using the physically existing memory. The collection of all virtual addresses forms the virtual address space.
Understanding C Language Pointers: A Comprehensive Guide
From a programmer’s perspective, memory should look like this.
Understanding C Language Pointers: A Comprehensive Guide
This means that memory is a large, linear byte array (flat addressing). Each byte has a fixed size, consisting of 8 binary bits. The most crucial point is that each byte has a unique number, starting from 0 and going up to the last byte. In the above image, this is a 256MB memory, which has a total of 256x1024x1024 = 268435456 bytes, so its address range is 0 ~ 268435455.
Since every byte in memory has a unique number, the variables, constants, and even functions used in the program, when loaded into memory, each have their own unique number, which is the address of that data. This is how pointers are formed.
The following code illustrates this
#include <stdio.h>int main(void){    char ch = 'a';    int  num = 97;    printf("ch's address:%p",&ch);   //ch's address:0028FF47    printf("num's address:%p",&num);  //num's address:0028FF40    return 0;}
Understanding C Language Pointers: A Comprehensive Guide
The value of a pointer is essentially the number of the memory unit (i.e., byte), so the pointer, viewed from a numerical perspective, is also an integer, typically represented in hexadecimal. The value of a pointer (virtual address value) is stored using the size of a machine word. For a computer with a machine word of w bits, its virtual address space is 0~2^w – 1, allowing the program to access a maximum of 2^w bytes. This is why a 32-bit system like XP supports a maximum of 4GB of memory.
We can roughly illustrate the storage of variables ch and num in the memory model (assuming char occupies 1 byte and int occupies 4 bytes).
Understanding C Language Pointers: A Comprehensive Guide

Understanding C Language Pointers: A Comprehensive Guide

Variables and Memory
For simplicity, we will analyze the storage model of the local variable int num = 97 from the above example.
Understanding C Language Pointers: A Comprehensive Guide
It is known that the type of num is int, which occupies 4 bytes of memory space, and its value is 97, with an address of 0028FF40. We will analyze from the following aspects.
1. Data in Memory
The data in memory is the binary corresponding to the variable’s value, which is all in binary. The binary of 97 is: 00000000 00000000 00000000 01100001, but when stored in little-endian mode, the low-order data is stored at the lower address, so it is drawn in reverse in the diagram.
2. Data Type in Memory
The data type in memory determines the number of bytes this data occupies and how the computer interprets these bytes. The type of num is int, so it will be interpreted as an integer.
3. Name of the Data in Memory
The name of the data in memory is the variable name. In fact, all memory data is identified by its address; there is no concept of memory names, which is just an abstraction provided by high-level languages to facilitate our manipulation of memory data. Moreover, in C, not all memory data has a name, for example, the heap memory allocated using malloc has no name.
4. Address of the Data in Memory
If a type occupies more than one byte, its variable’s address is the address of the byte with the smallest address value. Therefore, the address of num is 0028FF40. The address of memory is used to identify this memory block.
5. Lifecycle of the Data in Memory
num is a local variable in the main function, so when the main function is started, it is allocated on the stack memory, and when main finishes executing, it dies.
If a data continuously occupies its memory, we say it is “alive”; if the memory it occupies is reclaimed, then that data “dies.” The lifecycle characteristics of program data in C are determined by factors such as their defined positions, types of data, and modifying keywords. In reality, the memory we use in our programs is logically divided into: stack area, heap area, static data area, and method area. The data in different areas has different lifecycle characteristics.
No matter how computer hardware develops in the future, memory capacity is limited, so understanding the lifecycle of every piece of program data is very important.

Understanding C Language Pointers: A Comprehensive Guide

Pointer Arithmetic
Many interviews will test this:
Type *p; p++;
Then they will ask how much p’s value changed.
In fact, this can also be seen as testing the basic knowledge of the compiler. Therefore, p’s value does not simply change by +1; the compiler actually performs an operation of adding sizeof(Type) to p.
Let’s look at a piece of code’s test results:
Understanding C Language Pointers: A Comprehensive Guide
The reason for commenting out the char line is that cout<<(char*) will be treated as a string output, not as the address of char)
Execution results:
Understanding C Language Pointers: A Comprehensive Guide
Observing the results, we can see that their growth results are:
2(sizeof(short))         4(sizeof(int))        4(sizeof(long))         8(sizeof(long long))         4(sizeof(float))         8(sizeof(double))         12(sizeof(long double)
Indeed, the increment value is sizeof(Type)! For other types like struct, class, etc., you can verify it yourself if you’re interested.
Next, we will compile such a piece of code to see how the compiler performs pointer addition operations:
Understanding C Language Pointers: A Comprehensive Guide
Assembly results:
Understanding C Language Pointers: A Comprehensive Guide
Understanding C Language Pointers: A Comprehensive Guide
Note the results in the comments; we see that the value of piv is shown to have increased by 4 (sizeof(int)), then by 16 (4*sizeof(int)).

Understanding C Language Pointers: A Comprehensive Guide

Pointer Variables and Pointing Relationships
A pointer variable is one that saves a pointer. If the pointer variable p1 saves the address of the variable num, we say: p1 points to the variable num, which can also be said as p1 points to the memory block where num is located; this pointing relationship is generally represented by an arrow in diagrams.
Understanding C Language Pointers: A Comprehensive Guide
In the above image, the pointer variable p1 points to the memory block where num is located, which starts from address 0028FF40 and occupies 4 bytes of memory.

Understanding C Language Pointers: A Comprehensive Guide

Defining Pointer Variables
In C language, when defining a variable, if you place an asterisk (*) before the variable name, that variable becomes a pointer variable corresponding to the defined variable type. If necessary, parentheses must be added to avoid priority issues.
Extension: In C language, when you write typedef at the very beginning of a definition, that variable name becomes a type, which is a synonym for that type.
int a ; //int type variable a
int *a ; //int* variable
typedef struct Student { char name[31]; int age; float score; } Student;
int (* arr )[3]; //arr is a pointer variable pointing to an array containing 3 int elements
//-----------------Various types of pointers------------------------------
int* p_int; //pointer to int type variable
float* p_float; //pointer to float type variable
struct Student *p_struct; //pointer to structure type
int(*p_func)(int,int); //pointer to a function returning int with 2 int parameters
int(*p_arr)[3]; //pointer to an array containing 3 int elements
int** p_pointer; //pointer to a pointer to an int variable

Understanding C Language Pointers: A Comprehensive Guide

Two Important Properties of Pointers
Pointers are also a type of data, and pointer variables are also a kind of variable. Therefore, pointer data also conforms to the characteristics mentioned earlier about variables and memory. Here, we want to emphasize two properties: the type of the pointer and the value of the pointer.
int main(void){    int num = 97;    int *p1  = &amp;num;    char* p2 = (char*)(&amp;num);
    printf("%d",*p1);    //outputs  97    putchar(*p2);          //outputs  a    return 0;}
The value of a pointer: This is easy to understand. As seen in the above variable num, its address value is 0028FF40, so the value of p1 is 0028FF40. The address of the data in memory is used to locate and identify this data, as the addresses of any two non-overlapping different data in memory are different.
The type of a pointer: The type of a pointer determines the number of bytes that the pointer points to and how to interpret that byte information. Generally, the pointer variable’s type must match the type of the data it points to.
Since the address of num is 0028FF40, both p1 and p2 have the value 0028FF40
*p1 : will start parsing from address 0028FF40, since p1 is an int type pointer, it will take the next 4 bytes continuously and interpret these 4 bytes of binary data as an integer 97.
*p2 : will start parsing from address 0028FF40, since p2 is a char type pointer, it will take the next 1 byte continuously and interpret this 1 byte of binary data as a character, that is, ‘a’.
With the same address, because of different pointer types, the interpretation of the memory it points to differs, resulting in different data.

Understanding C Language Pointers: A Comprehensive Guide

Getting Addresses
Since we have pointer variables, we need to let them store the addresses of other variables, using the & operator to obtain the address of a variable.
int add(int a , int b){    return a + b;}
int main(void){    int num = 97;    float score = 10.00F;    int arr[3] = {1,2,3};
    //-----------------------
    int* p_num = &amp;num;    float* p_score = &amp;score;    int (*p_arr)[3] = &amp;arr;               int (*fp_add)(int ,int )  = add;  //p_add is a pointer to the function add    return 0;}
Special cases do not necessarily require using & to get the address:
  • The value of an array name is the address of the first element of that array.
  • The value of a function name is the address of that function.
  • String literal constants, when used as rvalues, are the names of the character arrays corresponding to that string, which is the address of that string in memory.
int add(int a , int b){    return a + b;}
int main(void){    int arr[3] = {1,2,3};    //-----------------------
    int* p_first = arr;    int (*fp_add)(int ,int )  =  add;    const char* msg = "Hello world";    return 0;}

Understanding C Language Pointers: A Comprehensive Guide

Dereferencing Addresses
What do we need a pointer variable for? Of course, to operate (read/write) the data it points to. Dereferencing a pointer allows us to access the memory data, and the syntax for dereferencing is to add an asterisk (*) before the pointer.
Dereferencing a pointer essentially means extracting the memory data from the memory block pointed to by the pointer.
int main(void){    int age = 19;    int*p_age = &amp;age;    *p_age  = 20;  //modify the memory data pointed to by the pointer
    printf("age = %d",*p_age);   //read the memory data pointed to by the pointer    printf("age = %d",age);
    return 0;}

Understanding C Language Pointers: A Comprehensive Guide

Pointer Assignment
Pointer assignment is similar to assigning int variables; it involves copying the value of the address to another. Pointer assignment is a shallow copy, which is an efficient way to share memory data between multiple programming units.
int* p1  = &amp; num;int* p3 = p1;
//Through pointers p1 and p3, both can read and write the memory data num; if two functions use p1 and p3 respectively, then these two functions share the data num.
Understanding C Language Pointers: A Comprehensive Guide

Understanding C Language Pointers: A Comprehensive Guide

Null Pointer (NULL Pointer)
NULL is a value defined by the C standard, which is actually 0; it is defined as a macro to make it appear more meaningful, indicating it does not point to anything. You understand. However, we will not discuss the difference between null and zero here.
In C, we assign a pointer variable to NULL to indicate a null pointer, while in C, NULL is actually ((void*)0), just like previously mentioned, a pointer can be understood as a special int; it always has a value, p=NULL actually means p’s value equals 0. For most machines, the address 0 cannot be accessed directly; setting it to 0 indicates that the pointer points to nowhere. In C++, NULL is actually 0.
In other words: no program data is stored in memory blocks at address 0; it is reserved by the operating system.
The following code is excerpted from stdlib.h
#ifdef __cplusplus     #define NULL    0#else         #define NULL    ((void *)0)#endif
Of course, from the internal perspective of the machine, the actual value of a NULL pointer may differ, and in this case, the compiler will be responsible for translating between the zero value and the internal value.
The concept of a NULL pointer is very useful; it provides a way to indicate that a particular pointer currently does not point to anything. For example, a function used to search for a specific value in an array might return a pointer to the found array element. If not found, it returns a NULL pointer.
In dynamic memory allocation, the significance of NULL is extraordinary; we use it to avoid memory being freed multiple times, causing frequent segmentation faults. Generally, after freeing or deleting dynamically allocated memory, the pointer should be immediately set to NULL to avoid dangling pointers, leading to various memory errors! For example:
Understanding C Language Pointers: A Comprehensive Guide
The free function does not and cannot set p to NULL; the following code will lead to a segmentation fault:
Understanding C Language Pointers: A Comprehensive Guide
Because after the first free operation, the memory pointed to by p has already been freed, but the value of p has not changed; the free function cannot change this value. When freeing again, the memory area pointed to by p has already been freed, and this address has become illegal; this operation will lead to a segmentation fault (at this time, the area pointed to by p just happened to be reallocated, but this probability is very low, and it is very dangerous to operate on such a memory area!).
However, the following code will not have such issues:
Understanding C Language Pointers: A Comprehensive Guide
Because the value of p has been set to NULL, the free function detects that p is NULL and will return directly without causing an error.
Here’s a little trick for memory release, which can effectively prevent various memory issues caused by forgetting to set pointers to NULL. This method is to define a custom memory release function, but instead of passing the pointer, pass the address of the pointer, and set it to NULL within this function, as shown below:
Understanding C Language Pointers: A Comprehensive Guide
Results:
Understanding C Language Pointers: A Comprehensive Guide
After calling my_free, the value of p becomes 0 (NULL), and calling free multiple times will not cause an error!
Another effective method is to define a FREE macro to set it to NULL within the macro. For example:
Understanding C Language Pointers: A Comprehensive Guide
The execution results are the same as above, and there will be no segmentation fault:
Understanding C Language Pointers: A Comprehensive Guide
(Regarding dynamic memory allocation, this is a relatively complex topic; I will open a chapter to explain it to everyone when I have the opportunity, as writing a post takes a lot of time and effort, haha, those who have written before should know this well. So, by the way, reposting is allowed; please indicate the source, as we are all here to discuss issues in the spirit of sharing. There is no need to demand anything from you, so please respect everyone’s labor results.)
Pointing to nothing, or not pointing to anything.

Understanding C Language Pointers: A Comprehensive Guide

Bad Pointers
A pointer variable that has a value of NULL, an unknown address value, or an address value that is not accessible by the current application is termed a bad pointer. You cannot perform dereferencing operations on them; otherwise, a runtime error will occur, causing the program to terminate unexpectedly.
Any pointer variable must ensure that it points to a valid, usable memory block before performing dereference operations; otherwise, it will result in an error. Bad pointers are one of the most frequent causes of bugs in C language.
The following code is an example of an error.

void opp(){     int*p = NULL;     *p = 10;      //Oops! Cannot dereference NULL}
void foo(){     int*p;     *p = 10;      //Oops! Cannot dereference an unknown address}
void bar(){     int*p = (int*)1000;      *p =10;      //Oops! Cannot dereference a pointer to an address that may not belong to this program}

Understanding C Language Pointers: A Comprehensive Guide

void* Type Pointer
Since void is an empty type, a void* type pointer only saves the value of the pointer, losing the type information; we do not know what type of data it points to, only that it specifies the starting address of that data in memory. To completely extract the pointed data, the programmer must correctly typecast this pointer before dereferencing it. Because the compiler does not allow direct dereferencing of void* type pointers.
Although literally, void means empty, a void pointer does not mean a null pointer; a null pointer refers to the NULL pointer mentioned above.
A void pointer actually means a pointer that can point to any type. Any type of pointer can be directly assigned to a void pointer without requiring typecasting.
For example:
Type a, *p=&amp;a;(Type equals char, int, struct, int *…)void *pv;pv=p;
As mentioned earlier, the advantage of a void pointer is that it can directly accept any pointer, which is very useful in certain situations. Therefore, some operations are the same for any pointer. Void pointers are most commonly used in memory management. The most typical and well-known is the free function from the standard library. Its prototype is as follows:
void free(void*ptr);
The free function’s parameter can be any pointer; has anyone ever seen a pointer in the free parameter that needs to be cast to void*?
The return values of malloc, calloc, realloc, etc., are also void pointers because memory allocation only requires knowledge of the allocation size and then returns the address of the newly allocated memory. The value of the pointer is the address; no matter what type of pointer is returned, the result is the same because all pointer lengths are actually 32 bits (on a 32-bit machine), and its value is the memory address; the pointer type is just for the compiler to see, so that the compiler can set the pointer value correctly during compilation (refer to the pointer arithmetic section). If the malloc function were set to the following prototype, it would be completely fine.
char*malloc(size_t sz);
In fact, setting it to
Type*malloc(size_t sz);
is also completely correct; the reason for using void pointers is that, as mentioned earlier, void pointers mean any pointer, making this design more rigorous and more aligned with our intuitive understanding. If you understand the pointer concepts I mentioned earlier, you should be able to grasp this.

Understanding C Language Pointers: A Comprehensive Guide

Structures and Pointers
Structure pointers have special syntax: -> symbol
If p is a structure pointer, you can access the members of the structure using p->【member】.
typedef struct{    char name[31];    int age;    float score;}Student;
int main(void){    Student stu = {"Bob" , 19, 98.0};    Student*ps = &amp;stu;
    ps-&gt;age = 20;    ps-&gt;score = 99.0;    printf("name:%s age:%d",ps-&gt;name,ps-&gt;age);    return 0;}

Understanding C Language Pointers: A Comprehensive Guide

Arrays and Pointers
1. When the array name is used as an rvalue, it is the address of the first element.
int main(void){    int arr[3] = {1,2,3};
    int*p_first = arr;    printf("%d",*p_first);  //1    return 0;}
2. Pointers to array elements support increment and decrement operations. (In fact, all pointers support increment and decrement operations, but only in arrays is it meaningful)
int main(void){    int arr[3] = {1,2,3};
    int*p = arr;    for(;p!=arr+3;p++){        printf("%d",*p);     }    return 0;}
3. p= p+1 means that p points to the next adjacent memory block of the same type as the original.
Within the same array, the pointers between elements can perform subtraction operations, and the difference between pointers equals the difference in indices.
4. p[n] == *(p+n)
p[n][m] == *( *(p+n)+ m )
5. When using sizeof on an array name, it returns the total memory bytes occupied by the entire array. When assigning an array name to a pointer and then using sizeof on the pointer, it returns the size of the pointer.
This is why when passing an array to a function, it is necessary to pass the number of array elements as another parameter.
int main(void){    int arr[3] = {1,2,3};
    int*p = arr;    printf("sizeof(arr)=%d",sizeof(arr));  //sizeof(arr)=12    printf("sizeof(p)=%d",sizeof(p));   //sizeof(p)=4
    return 0;}

Understanding C Language Pointers: A Comprehensive Guide

Functions and Pointers
Function Parameters and Pointers
In C language, actual parameters are passed to formal parameters by value, meaning that the formal parameters in the function are copies of the actual parameters; formal and actual parameters are the same only in value, not the same memory data object. This means that this data transmission is unidirectional, i.e., from the caller to the called function, and the called function cannot modify the passed parameters to achieve a return effect.
void change(int a){    a++;      //The change only affects the local variable a of this function, and when the function ends, a is destroyed; age remains unchanged.}
int main(void){    int age = 19;    change(age);    printf("age = %d",age);   // age = 19    return 0;}
Sometimes we can use the return value of a function to return data; this works in simple cases. However, if the return value has other purposes (such as returning the execution status of the function), or if there is more than one piece of data to return, the return value will not suffice.
Passing the pointer of a variable can easily solve the above problem.
void change(int* pa){    (*pa)++;   //because the address of age is passed, pa points to the memory data age. When dereferencing pointer pa in the function, it directly finds the data age in memory and increments it by 1.}
int main(void){    int age = 19;    change(&amp;age);    printf("age = %d",age);   // age = 20    return 0;}
Another common example is using functions to swap the values of two variables:
#include&lt;stdio.h&gt;void swap_bad(int a,int b);void swap_ok(int*pa,int*pb);
int main(){    int a = 5;    int b = 3;    swap_bad(a,b);       //Can't swap;    swap_ok(&amp;a,&amp;b);      //OK    return 0;}
//Error way
void swap_bad(int a,int b){    int t;    t=a;    a=b;    b=t;}
//Correct way: using pointers
void swap_ok(int*pa,int*pb){    int t;    t=*pa;    *pa=*pb;    *pb=t;}
Understanding C Language Pointers: A Comprehensive Guide
Understanding C Language Pointers: A Comprehensive Guide
Sometimes, we pass data to a function through pointers not to change the object it points to. Instead, we prevent the target data from being modified. Passing pointers is merely to avoid copying large data.
Consider a structure type Student. We will use the show function to output the data of the Student variable.
typedef struct{    char name[31];    int age;    float score;}Student;
//Print Student variable information
void show(const Student * ps){    printf("name:%s , age:%d , score:%.2f",ps-&gt;name,ps-&gt;age,ps-&gt;score);   }
We only read the information of the Student variable in the show function without modifying it; to prevent accidental modification, we use a constant pointer. Additionally, why do we use pointers instead of directly passing the Student variable?
From the structure definition, the size of a Student variable is at least 39 bytes, so passing the variable directly to the function would require copying at least 39 bytes of data, which is very inefficient. However, passing the pointer to the variable is much faster because under the same platform, the size of any pointer is fixed: X86 pointers are 4 bytes, and X64 pointers are 8 bytes, which is much smaller than a Student structure variable.
Function Pointers
Like ordinary variables, each function has its address; we execute code by jumping to this address for function calls. The difference is that functions have parameters and return values. During a function call, parameters are first pushed onto the stack, and after the call is completed, parameters are popped off the stack. Since functions are accessed by addresses, they can also be pointed to using pointers. In fact, every function name is a pointer, but it is a constant pointer, and its value cannot be changed, nor can the value it points to be altered.
(Regarding constant pointers and pointer constants, I will dedicate a chapter to explain the const concept when time allows, as it is also a very interesting topic…)
What are function pointers generally used for? Function pointers are most commonly used for callback functions. A callback function is a function that will be called by another function at the appropriate time. Callback functions are very useful when you want a function to use your specific method to operate, for example, when you expect a sorting function to use your defined comparison method for comparison.
Those with in-depth C programming experience should have encountered this. The C standard library uses it, for example, in the qsort function in the strlib.h header file, whose prototype is:
void qsort(void*__base, size_t __nmemb, size_t __size, int(*_compar)(const void *, const void*));
Where int(*_compar)(const void *, const void *) is the callback function used by the qsort function for data comparison. Below, I will provide an example to describe how the qsort function works.
Generally, we define function pointers in the following way:
typedef int(*compare)(const void *x, const void *y);
At this point, compare is a function that takes parameters of type const void *, const void * and returns an int type. For example:
Understanding C Language Pointers: A Comprehensive Guide
Understanding C Language Pointers: A Comprehensive Guide
The benefit of using typedef is that you can use a short name to represent a type without always needing to use lengthy code, making the code not only more concise and readable but also preventing errors in code writing.I strongly recommend that everyone use typedef when defining structures, pointers (especially function pointers), and other complex structures.
Every function itself is also a type of program data; a function contains multiple execution statements, and when compiled, it is essentially a collection of multiple machine instructions. When a program is loaded into memory, the machine instructions of the function are stored in a specific logical area: the code area. Since it is stored in memory, functions also have their pointers.
In C language, when a function name is used as an rvalue, it is the pointer to that function.
void echo(const char *msg){    printf("%s",msg);}
int main(void){    void(*p)(const char*) = echo;   //Function pointer variable pointing to echo function    p("Hello ");      //Call the function through pointer p, equivalent to echo("Hello ")    echo("World");   return 0;}
const and Pointers
What does const modify? What remains unchanged?
If const is followed by a type, it skips the nearest atomic type and modifies the data behind it. (Atomic types are types that cannot be further divided, such as int, short, char, and types wrapped by typedef)
If const is followed directly by a data, it modifies that data.
int main(){    int a = 1;    int const *p1 = &amp;a;        //const is after *p1, actually modifying a, thus *p1 cannot change a's value      const int*p2 =  &amp;a;        //const is after int type, thus skipping int, modifying *p2, same effect      int* const p3 = NULL;      //const is after data p3, meaning pointer p3 itself is const.      const int* const p4 = &amp;a;  //  Both p4 cannot change a's value, and p4 itself is also const      int const* const p5 = &amp;a;  //same effect       return 0;   }   typedef int* pint_t;  //Wrap int* type as pint_t, making pint_t a complete atomic type      int main()   {       int a  = 1;       const pint_t p1 = &amp;a;  //Similarly, const skips type pint_t, modifying p1, meaning pointer p1 itself is const       pint_t const p2 = &amp;a;  //const directly modifies p, same as above       return 0;   }
Deep Copy and Shallow Copy
If two programming units (such as two functions) work by copying the pointers of the data they share, this is a shallow copy because the actual data being accessed is not copied. If the accessed data is copied, and each unit has its own copy, operations on the target data do not affect each other, this is called a deep copy.
Understanding C Language Pointers: A Comprehensive Guide
Additional Knowledge
The difference between pointers and references. Essentially, they are the same thing. Pointers are commonly used in C language, while references are used in languages like Java and C# that encapsulate direct pointer operations at the language level.
Big-endian and little-endian
1) Little-Endian means that the low-order byte is placed at the low address end of memory, and the high-order byte is placed at the high address end. This is commonly used in personal PCs; Intel X86 processors use little-endian.
2) Big-Endian means that the high-order byte is placed at the low address end of memory, and the low-order byte is placed at the high address end.
Storing data in a big-endian manner aligns with human thinking, while storing data in a little-endian manner facilitates computer processing. Some machines support both big-endian and little-endian modes, which can be configured to set the actual endianness.
Suppose the short type occupies 2 bytes and is stored at address 0x30.
short a = 1;
As shown below:
Understanding C Language Pointers: A Comprehensive Guide
//Test whether the machine uses little-endian. If so, return true; otherwise return false
//This method determines based on the fact that in C language, the address of an object is the address of the first byte occupied by that object, which is the low address of the data.

bool isSmallIndain(){      unsigned int val = 'A';      unsigned char* p = (unsigned char*)&amp;val;  //In C/C++, for multi-byte data, taking the address is taking the address of the first byte of the data object, which is the low address.
      return *p == 'A';}
Material Source: Network, directly sourced from Yikou Linux, Tudaoshijushi
Copyright belongs to the original author. Only for the dissemination and learning discussion of technology. If there are copyright issues regarding the work, please contact me for removal.
Download 1: OpenCV-Contrib Extension Module Chinese Version Tutorial

Reply "OpenCV Extension Module Chinese Tutorial" in the backend of the "Beginner Learning Visuals" public account to download the first OpenCV extension module tutorial in Chinese available online, covering installation of extension modules, SFM algorithms, stereo vision, target tracking, biological vision, super-resolution processing, and more than twenty chapters of content.

Download 2: Python Vision Practical Projects 52 Lectures

Reply "Python Vision Practical Projects" in the backend of the "Beginner Learning Visuals" public account to download 31 vision practical projects, including image segmentation, mask detection, lane line detection, vehicle counting, eyeliner addition, license plate recognition, character recognition, emotion detection, text content extraction, face recognition, etc., to assist in quickly mastering computer vision.

Download 3: OpenCV Practical Projects 20 Lectures

Reply "OpenCV Practical Projects 20 Lectures" in the backend of the "Beginner Learning Visuals" public account to download 20 practical projects based on OpenCV, achieving advanced learning in OpenCV.

Group Chat

Welcome to join the reader group of the public account to communicate with peers. Currently, there are WeChat groups for SLAM, 3D vision, sensors, autonomous driving, computational photography, detection, segmentation, recognition, medical imaging, GAN, algorithm competitions, etc. (these will be gradually subdivided in the future). Please scan the WeChat number below to join the group, and note: "Nickname + School/Company + Research Direction", for example: "Zhang San + Shanghai Jiao Tong University + Visual SLAM". Please follow the format; otherwise, you will not be approved. After successful addition, you will be invited into relevant WeChat groups based on your research direction. Please do not post advertisements in the group, or you will be removed from the group. Thank you for your understanding~



Leave a Comment