Word Count: 14000 Content Quality Index: ⭐⭐⭐⭐⭐
Pointers are crucial in C. However, to fully understand pointers, one must not only be proficient in C but also have a basic knowledge of computer hardware and operating systems. Therefore, this article aims to explain pointers comprehensively.
Why Do We Need Pointers?
Pointers solve some fundamental problems in programming.
✅ The use of pointers allows different areas of code to easily share memory data. Of course, friends can also achieve the same effect by copying data, but this is often less efficient. For large data types like structures that occupy many bytes, copying can be performance-intensive. However, using pointers can effectively avoid this issue because any type of pointer occupies the same number of bytes (which can be 4 bytes, 8 bytes, or other possibilities depending on the platform).
✅Pointers enable the construction of complex linked data structures, such as linked lists and binary trees.
✅Some operations must use pointers, such as operations on heap memory allocation. Additionally, in all function calls in C, value passing is done “by value.” If we want to modify an object passed to a function, we must do so through the object’s pointer.

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 generally matches the CPU’s word length.
Generally speaking, 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; on a 32-bit machine, it is 32 bits. This is the maximum amount of data a computer can read or store in a single instruction. If it exceeds this value, the computer will perform multiple accesses. This is why we say that a 64-bit machine performs 64-bit data operations more efficiently than a 32-bit machine, as the 32-bit machine must perform two fetches and executions, while the 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 at that address via the data bus, with the transmission length being the bit width of the data bus. The bit width of the address bus determines the size of the memory space that the CPU can directly address. For example, if the CPU bus is 32 bits long, its maximum direct addressing space is 2^32 KB, which is 4 GB.
This is why we commonly say that a 32-bit CPU has a maximum memory limit of 4 GB (of course, in reality, it supports less than this value because part of the addressing space is mapped to 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 not within 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 a piece of data, it first transmits the location of the data to be stored or read via the address bus, and then transmits the data to be stored or read via the data bus. Generally, the bit width of int equals the width of the data bus, and the bit width of a pointer equals the width of the address bus.
Basic Access Unit of the Computer
Those who have studied C language know that among the basic data types, char has the smallest bit width, which is 8 bits. We can consider that the computer uses 8 bits, or 1 byte, as the basic access unit. Data smaller than one byte must be accessed through bit manipulation.
As shown in Figure 1, when a computer accesses data, it accesses it byte by byte, 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.
If you want to learn more, refer to the principles of computer organization and compilation.
The sizeof keyword is used by the compiler to calculate the length of certain data types 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!
We know that in C language, arrays are a type of type, specifically categorized into int type arrays, double type arrays, char arrays, etc. Similarly, the concept of pointers also refers to a class of data types, such as 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 in memory, which is the pointer. To save the address of a data item in memory, we need a pointer variable.
Therefore: a pointer is the address of program data in memory, and a pointer variable is used to store these addresses.
In my personal understanding, pointers can be understood as int types, except that they store memory addresses instead of ordinary data. We access data through this address value; assuming it is p, it means the data is stored at memory’s p-th byte.
Of course, we cannot perform various arithmetic operations like addition and subtraction on int type data, as the compiler does not allow it, because such errors are very dangerous!
Figure 2 describes pointers; the value of a pointer is the address where the data is stored, so we say that the pointer points to the data’s storage location.

We define a pointer in the following way:
We say p is a pointer to type type. The type can be any type; besides basic types like char, short, int, long, it can also be pointer types, such as int *, int **, or more levels of pointers, or structures, classes, or functions. 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 believe you understand.
struct xxx *, is a pointer to struct xxx type;
In fact, I mention all this just to help everyone not to be intimidated by things like int ***; as mentioned earlier, a pointer is simply a pointer to a certain type, and we only need to look at the last *; the preceding ones are just type indicators.
Those who are observant should have noticed that in the “What is a pointer” section, it has already been stated: the length of a pointer is equal to the CPU’s bit width. Most CPUs are 32-bit, so we say the length of a pointer is 32 bits, or 4 bytes! Note: the length of any pointer is 4 bytes, regardless of what type of pointer it is! (Of course, on a 64-bit machine, it should be 8 bytes…)
sizeof(p) will be 4, and Type can be any type: char, int, long, struct, class, int **…
In the future, when you see sizeof(char*), sizeof(int *), sizeof(xxx *), just write 4 for all of them; as long as it is a pointer, the length is 4 bytes, and do not be confused by the type!

Why Do Data in Programs Have Their Own Addresses?
To clarify this issue, we need to understand memory from the perspective of the operating system.
From the viewpoint of a computer technician, memory consists physically of a set of DRAM chips.
As a programmer, we do not need to understand the physical structure of memory; the operating system combines hardware like RAM with software to provide programmers with an abstraction for memory usage. This abstraction mechanism allows programs to use virtual memory instead of directly manipulating and using the actual physical memory. The entire collection of virtual addresses forms the virtual address space.
In the eyes of a programmer, memory should look like this.
This means that memory is a large, linear array of bytes (flat addressing). Each byte has a fixed size, made up of 8 bits. Most importantly, each byte has a unique number, starting from 0 and going up to the last byte. For example, in a 256 MB memory, there are 256 x 1024 x 1024 = 268435456 bytes, making the address range 0 to 268435455.
Since each byte in memory has a unique number, any variables, constants, and even functions used in a program, when loaded into memory, have their unique number, which is the address of that data. This is how pointers are formed.
Let’s illustrate this with code
#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;}
The value of a pointer is essentially the number of a memory unit (i.e., byte), so pointers are also integers from a numerical perspective, typically represented in hexadecimal. The value of a pointer (virtual address value) is stored using the size of a machine word. This means that for a computer with a machine word of w bits, its virtual address space is 0 to 2^w – 1, and the program can access up to 2^w bytes. This is why a 32-bit system like XP supports a maximum of 4 GB 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).
For simplicity, let’s analyze the storage model of the local variable int num = 97 from the previous example.
It is known that the type of num is int, which occupies 4 bytes of memory space, and its value is 97, with the address being 0028FF40. We will analyze from the following aspects.
The data in memory is the binary corresponding to the variable’s value; everything is binary. The binary of 97 is: 00000000 00000000 00000000 01100001, but when stored in little-endian mode, low-order data is stored at lower addresses, so it is reversed in the figure.
The data type in memory determines how many 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 reality, memory data is identified by address; there is no notion of a name in memory; this is just an abstraction mechanism provided by high-level languages to facilitate our manipulation of memory data. Furthermore, not all memory data in C has names; for example, memory allocated using malloc does not have a name.
4. Address of the Data in Memory
If a type occupies more than 1 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 in memory is used to identify this memory block.
5. Lifecycle of the Data in Memory
num is a local variable in the main function; therefore, when the main function is started, it is allocated on the stack memory, and when main execution ends, it is destroyed.
If a data item continues to occupy its memory, we say it is “alive”; if the memory it occupies is reclaimed, then this data “dies.” In C language, the lifecycle characteristics of program data are determined by their defined locations, types, and modifying keywords. In essence, the memory we use 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 evolves in the future, memory capacity is limited, so it is crucial to understand the lifecycle of every piece of program data.

Many interviews will ask about this:
Then they ask how much p’s value changes.
In fact, this can also be seen as testing 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 test result of a piece of code:
The reason for commenting out the char line is that cout<<(char*) will be treated as a string output rather than the address of char.
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)
See, the increased value is exactly sizeof(Type), right? For other types like struct, class, etc., I won’t verify; if you’re interested, you can verify it yourself.
Next, let’s compile such a piece of code and see how the compiler performs pointer addition:
Pay attention to the results in the comments; we see that the value of piv shows an addition of 4 (sizeof(int)), and then another addition of 16 (4*sizeof(int)).

Pointer Variables and Pointing Relationships
A variable used to store a pointer is a pointer variable. If pointer variable p1 stores the address of variable num, we say: p1 points to variable num, or we can say p1 points to the memory block where num is located; this pointing relationship is generally represented with arrows in diagrams.
In the diagram above, pointer variable p1 points to the memory block where num is located, which is the 4-byte memory block starting from address 0028FF40.

Defining Pointer Variables
In C language, when defining a variable, placing an asterisk (*) before the variable name converts the variable into a pointer variable of the corresponding type. Necessary parentheses may be added to avoid priority issues.
Extension: In C language, when defining a variable, placing typedef in front makes the variable name a type, i.e., a synonym for that type.
int a ; //int type variable aint *a ; //int* variableaint arr[3]; //arr is an array containing 3 int elementsint (* 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 double* p_double; //pointer to double type variable struct Student *p_struct; //pointer to struct type int(*p_func)(int,int); //pointer to 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

Two Important Properties of Pointers
Pointers are also a type of data, and pointer variables are also a type of variable, so pointers conform to the characteristics discussed earlier about variables and memory. Here, we emphasize two properties: the type of the pointer and the value of the pointer.
int main(void){ int num = 97; int *p1 = # char* p2 = (char*)(&num); printf("%d",*p1); //outputs 97 putchar(*p2); //outputs a return 0;}
The value of the pointer: It is easy to understand; as mentioned above, the variable num has an address value of 0028FF40, therefore the value of p1 is 0028FF40. The address of the data is used to locate and identify this data in memory, as the addresses of any two non-overlapping different data are different.
The type of the pointer: The type of the pointer determines how many bytes of memory it points to and how to interpret this byte information. Generally, the type of pointer variable should 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 parse from address 0028FF40, because p1 is an int type pointer, which occupies 4 bytes, so it will continuously take 4 bytes and interpret this 4-byte binary data as an integer 97.
*p2 : will parse from address 0028FF40, because p2 is a char type pointer, which occupies 1 byte, so it will continuously take 1 byte and interpret this 1-byte binary data as a character, namely ‘a’.
With the same address, due to different pointer types, the interpretation of the memory they point to varies, resulting in different data.

Since we have pointer variables, we need to let them store the addresses of other variables, using the & operator to obtain a variable’s address.
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 = # float* p_score = &score; int (*p_arr)[3] = &arr; int (*fp_add)(int ,int ) = add; //p_add is a pointer to function add return 0;}
In special cases, they do not necessarily need to use & to get addresses:
-
The value of the array name is the address of the first element of that array.
-
The value of the function name is the address of that function.
-
String literal constants as right values are the names of the character arrays corresponding to these strings, which is the address of this 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;}
Why do we need a pointer variable for a piece of data? Of course, to operate (read/write) the data it points to. Dereferencing a pointer allows us to access this memory data; the syntax for dereferencing is to place an asterisk (*) before the pointer.
Dereferencing a pointer essentially means: retrieving the memory data from the memory block pointed to by the pointer.
int main(void){ int age = 19; int*p_age = &age; *p_age = 20; //modify the memory data pointed to by the pointer through dereferencing printf("age = %d",*p_age); //read the memory data pointed to by the pointer printf("age = %d",age); return 0;}
Pointer assignment is similar to assigning values to int variables; it simply copies the address value to another. Assigning pointers is a form of shallow copy and is an efficient way to share memory data between multiple programming units.
int* p1 = & num;int* p3 = p1;//through pointers p1 and p3, we can read/write the memory data num; if two functions use p1 and p3 respectively, then these two functions share the data num.

Null Pointer (NULL Pointer)
NULL is a value defined by the C language standard, which is actually 0. However, to make it more meaningful, this macro is defined, meaning empty, indicating that it does not point to anything. You understand. But we will not discuss the difference between null and zero here.
In C language, assigning a pointer variable to NULL indicates a null pointer, while in C language, NULL is essentially ((void*)0); as mentioned earlier, a pointer can be understood as a special int, which always has a value; p=NULL actually means the value of p equals 0. For most machines, the address 0 cannot be directly accessed; setting it to 0 indicates that the pointer does not point anywhere. In C++, NULL is essentially 0.
In other words: no program data will be stored in the memory block at address 0, as 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 machine’s internal perspective, the actual value of a NULL pointer may differ; in this case, the compiler will handle the translation 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 specific pointer currently does not point to anything. For example, a function used to search for a specific value in an array may 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 unparalleled; 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 that can lead to various memory errors! For example:
The free function will not and cannot set p to NULL; code like the one below will cause memory segmentation errors:
Because, after the first free operation, the memory pointed to by p has already been released, but the value of p has not changed; the free function cannot change this value. When freeing it a second time, the memory area pointed to by p has already been released, making this address illegal, which will cause a segmentation error (at this point, the area pointed to by p has just been allocated again, but this probability is very low, and operating on such a memory area is very dangerous!).
However, the following code will not encounter such problems:
Because the value of p is set to NULL, the free function detects that p is NULL and will return directly without causing an error.
Here’s a little tip for memory release, which can effectively avoid various memory issues caused by forgetting to set pointers to NULL. This method is to define a custom memory release function, but the parameter passed is not the pointer, but the address of the pointer, and set it to NULL within this function, as follows:
After calling my_free, the value of p becomes 0 (NULL), and calling free multiple times will not result in errors!
Another effective way is to define a FREE macro that sets it to NULL within the macro. For example:
The execution results are the same as above, and there will be no segmentation errors:
(Regarding dynamic memory allocation, this is a relatively complex topic; if there’s an opportunity, I’ll dedicate a chapter to explain it to everyone. Writing a post takes a lot of time and effort, haha; those who have written should know very well, so just a quick note: reposting is allowed, please indicate the source, as we are all here to discuss issues with a spirit of sharing; the quality of writing does not require anything from you; please respect everyone’s labor results.)
Pointing to nothing, or not pointing to anything.

A pointer variable with a value of NULL, an unknown address value, or an address value that is inaccessible to the current application is considered a bad pointer. Dereferencing them will cause runtime errors, leading to unexpected program termination.
Any pointer variable must ensure it points to a valid, accessible memory block before dereferencing; otherwise, an error will occur. Bad pointers are one of the most frequent causes of C language bugs.
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 a pointer to 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}

Since void is an empty type, void* type pointers only save the pointer value, losing type information. We do not know what type of data it points to; we only specify the starting address of this data in memory. If we want to fully extract the pointed data, the programmer must correctly cast this pointer type before dereferencing. This is 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.
In essence, a void pointer is a pointer that can point to any type. Any type of pointer can be directly assigned to a void pointer without needing to cast.
Type a, *p=&a;(Type can be 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 example is the standard library’s free function. Its prototype is as follows:
The free function’s parameter can be any pointer; no one has ever seen a pointer in the free parameter that needed to be cast to void* right?
The malloc, calloc, and realloc functions also return void pointers because memory allocation only needs to know the size allocated and then return the address of the newly allocated memory. The pointer value is simply the address; it does not matter what type of pointer is returned; in fact, the result is the same because all pointer sizes are 32 bits (on a 32-bit machine), and its value is the memory address; the pointer type is only for the compiler to see, to correctly set the pointer value during compilation (see the pointer arithmetic chapter).
If the malloc function were set up like this:
It would be perfectly fine to set it up like this:
Type*malloc(size_t sz);
Using void pointers is more rigorous and aligns with our intuitive understanding. If you understand the pointer concepts I mentioned earlier, you will definitely understand this.
Structure pointers have special syntax: -> symbol.
If p is a structure pointer, you can access the structure’s members using p->member.
typedef struct{ char name[31]; int age; float score;}Student;int main(void){ Student stu = {"Bob" , 19, 98.0}; Student*ps = &stu; ps->age = 20; ps->score = 99.0; printf("name:%s age:%d",ps->name,ps->age); return 0;}

1. When the array name is used as a right value, 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. A pointer pointing to an array element supports increment and decrement operations. (In fact, all pointers support increment and decrement operations, but only when used in arrays does it make sense.)
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 pointing p to the next adjacent memory block of the same type that it originally pointed to.
Pointer subtraction can be performed between elements within the same array; the difference between the pointers equals the difference in their indices.
p[n][m] == *( *(p+n)+ m )
5. When using sizeof on an array name, it returns the total memory byte size occupied by the entire array. However, when assigning the 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 a separate 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;}

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, and the formal and actual parameters are only the same in value, not the same memory data object. This means that this data passing is unidirectional, 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 in this function only affects the local variable a, and when the function execution 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 is feasible in simple situations. However, if the return value has other uses (for example, returning the execution status of the function) or if there is more than one piece of data to return, the return value cannot solve the problem.
Passing the pointer of a variable can easily solve the above problems.
void change(int* pa){ (*pa)++; //because the address of age is passed, pa points to the memory data age. When dereferencing pa in the function, it will directly find the data age in memory and increase it by 1.}int main(void){ int age = 19; change(&age); printf("age = %d",age); // age = 20 return 0;}
Let’s revisit an old example of swapping two variable values using functions:
#include<stdio.h>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(&a,&b); //OK return 0;}//Error implementationvoid swap_bad(int a,int b){ int t; t=a; a=b; b=t;}//Correct implementation: through pointersvoid swap_ok(int*pa,int*pb){ int t; t=*pa; *pa=*pb; *pb=t;}
Sometimes, we pass pointers to functions not to change the objects they point to. Instead, we prevent this target data from being changed. Passing pointers is simply to avoid copying large data.
Consider a structure type Student. We 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 informationvoid show(const Student * ps){ printf("name:%s , age:%d , score:%.2f",ps->name,ps->age,ps->score); }
We are simply reading the information of the Student variable in the show function without modifying it. To prevent accidental modification, we use a constant pointer for constraint. Additionally, why do we use pointers instead of directly passing the Student variable?
From the structure definition, the size of the Student variable is at least 39 bytes. Therefore, directly passing the variable to the function would require copying at least 39 bytes of data, which is inefficient. Passing the pointer to the variable is much faster because under the same platform, the size of any pointer is fixed: X86 pointer is 4 bytes, X64 pointer is 8 bytes, which is far smaller than a Student structure variable.
Like ordinary variables, every function has its address, which we access by jumping to that address to execute the code corresponding to the function. The difference from accessing ordinary data is that functions have parameters and return values; during function calls, parameters need to be pushed onto the stack first, and after the call completes, they also need to be pushed onto the stack. Since functions are accessed by addresses, they can also be pointed to by pointers. In fact, every function name is a pointer, but it is a constant pointer and constant value, and its value cannot be changed, nor can the value pointed to be changed.
(Regarding constant pointers and pointer constants, when I have time, I’ll dedicate a chapter to explain the const concept, which 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, as the name suggests, is a function that will be called by others at the appropriate time. Callback functions are very useful when you want the function you call to operate using some method you define, for example, you want 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, which has the prototype:
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 qsort 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:
The benefit of defining it using typedef is that it allows you to use a shorter name to represent a type without always using lengthy code, which not only makes the code more concise and readable but also avoids the problem of easily making mistakes when writing code.I strongly recommend everyone to use typedef when defining structures, pointers (especially function pointers), and other complex structures.
Each function itself is also a type of program data, containing multiple execution statements, and is compiled into 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 they are stored in memory, functions also have their pointers.
In C language, when the function name is used as a right value, 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 points to function echo p("Hello "); //call the function through pointer p, equivalent to echo("Hello ") echo("World"); return 0;}
What does const modify? What is immutable?
If const is followed by a type, it skips the nearest atomic type and modifies the data that follows. (Atomic types are indivisible types, such as int, short, char, and types wrapped by typedef.)
If const is followed by data, it directly modifies that data.
int main(){ int a = 1; int const *p1 = &a; //const after *p1 effectively modifies p1, a cannot be modified through p1 const int*p2 = &a; //const after int type skips int, modifies p2, same effect as above int* const p3 = NULL; //const after data p3 means pointer p3 itself is const. const int* const p4 = &a; //both p4 cannot change the value of a, and p4 itself is also const int const* const p5 = &a; //same effect as above return 0;}typedef int* pint_t; //wrap int* type as pint_t, then pint_t is now a complete atomic typeint main(){ int a = 1; const pint_t p1 = &a; //similarly, const skips type pint_t, modifies p1, pointer p1 itself is const pint_t const p2 = &a; //const directly modifies p, same as above return 0;}
Deep Copy and Shallow Copy
If two programming units (e.g., two functions) work by copying the pointers of the data they share, this is shallow copying, as the actual data being accessed is not copied. If the accessed data is copied, with each unit having its own copy, and operations on the target data do not affect each other, it is called deep copying.
The difference between pointers and references: they are essentially the same thing. Pointers are commonly used in C language, while references are used in programming languages like Java and C# that encapsulate direct pointer operations at the language level.
Big-endian and Little-endian
1) Little-Endian means that low-order bytes are placed at low address ends of memory, while high-order bytes are placed at high address ends. This is commonly used in personal PCs, with Intel X86 processors being little-endian.
2) Big-Endian means that high-order bytes are placed at low address ends of memory, while low-order bytes are placed at high address ends.
Storing data in big-endian format aligns with human thinking, while storing data in little-endian format is beneficial for computer processing. Some machines support both big-endian and little-endian modes, allowing configuration to set the actual endianness.
Assuming the short type occupies 2 bytes and the storage address is 0x30.
//Test whether the machine uses little-endian. If so, return true, otherwise return false.//The basis for this method of identification is: in C language, the address of an object is the address of the lowest address byte among the bytes occupied by this object.bool isSmallIndain(){ unsigned int val = 'A'; unsigned char* p = (unsigned char*)&val; //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 data return *p == 'A';}
Disclaimer: The content of this article comes from the internet, and the copyright belongs to the original author. If there are any copyright issues, please contact us for removal.
《Embedded Systems Must Know CAN Bus, This Article is Enough!》
《Where to Go for Embedded Employment? 10 Major Military Central Enterprises in China, 1000+ Units! The Most Comprehensive Guide》
《4500 Words of Practical Knowledge | How to Write Embedded Linux Device Drivers?》
Every day, the hardworking little creator,
[Share, Like, View] Can you have a duck