Detailed explanation of C language pointers (1) Super detailed~

Insert image description here


https://img-blog.csdnimg.cn/6a78491801a74bbd8d932659b06e11db.gif#pic_center

1. Memory and address

1.1 Memory

Before talking about memory and addresses, we want to have a real life case:

Suppose there is a dormitory building and you are placed in the building. There are 100 rooms upstairs, but the rooms are not numbered. One of your friends comes to play with you. If he wants to find you, he has to go to each building to find you, which is very inefficient. , but if we number each room according to the floor and the rooms on the floor, such as:

一楼:101102103...
二楼:201202203....
...

Withroom number, if your friend gets the room number, he can quickly find the room and find you.

In life, each room has a room number, which can improve efficiency and quickly find the room.

What if we compare the above example to calculations?

We know that when the CPU (Central Processing Unit) processes data, the required data is read from the memory, and the processed data will also be put back into the memory. When we buy a computer, the memory on the computer is 8GB/ 16GB/32GB, etc. How to manage these memory spaces efficiently?

In fact, the memory is divided into memory units, and the size of each memory unit is 1 byte.

Common units in computers(supplementary):

A bit can store a binary bit 1 or 0

1 bit - 比特位
2 byte - 字节
3 KB
4 MB
5 GB
6 TB
7 PB
1byte = 8bit
1KB = 1024byte
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
1PB = 1024TB

Among them, each memory unit is equivalent to a student dormitory, and one person can put , just like the 8-person room where classmates live, each person is a bit.

Each memory unit also has a number (this number is equivalent to the house number of the dormitory room). With the number of this memory unit, the CPU can quickly find a memory space.

In life, we call house numbers also called addresses. In computers, we call the number of a memory unit also called an address. In C language, address has a new name: Pointer.

Therefore we can understand it as:

Memory unit numberaddresspointer

1.2 How to understand addressing

When the CPU accesses a certain byte space in the memory, it must know where the byte space is in the memory. Because there are many bytes in the memory, the memory needs to be addressed (just like there are many dormitories and the dormitories need to be numbered) .

Addressing in computers does not record the address of each byte, but is done through hardware design.

Pianos and guitars do not have the message "Dorami sends Soracedo" written on them, but the player can still accurately find every position of every string. Why is this? Because the manufacturer has designed it at the instrument hardware level, and all players know it. The essence is an agreed consensus!

The same goes for hardware addressing

See the picture below for details:

First of all, we must understand that there are many hardware units in the computer, and the hardware units work together with each other. The so-called collaboration must at least be able to transfer data between each other.

But hardware and hardware are independent of each other, so how to communicate? The answer is simple, connect them with "lines".

There is also a large amount of data interaction between the CPU and memory, so the two must be connected with lines.

However, today we are concerned about a group of lines calledaddress bus

We can simply understand that a 32-bit machine has 32 address buses, and each line has only two states, indicating 0 or 1, [the presence or absence of electrical pulses], then one line can represent 2 meanings, and 2 lines can mean It can express 4 meanings, and so on. 32 address lines can represent 2^32 meanings, each meaning represents an address.

The address information is sent to the memory. In the memory, the data corresponding to the address can be found, the data is passed through the corresponding data, and then the data is transferred to the CPU internal register through the data bus.

2. Pointer variables and addresses

2.1 Get address operator (&)

After understanding the relationship between memory and address, let's go back to C language. Creating variables in C language is actually applying for space in memory, such as:

#include <stdio.h>
int main()
{
    
    
    int a = 10;
    return 0;
}

For example, the above code creates an integer variable a, and allocates 4 bytes in the memory to store the integer 10. Each byte has an address. The addresses of the 4 bytes in the picture above are:

1.0x006FFD6C  
2.0x006FFD6D 
3.0x006FFD6E  
4.0x006FFD6F 

So how do we get the address of a?

Here you have to learn an operator (&)-address operator

#include <stdio.h>
int main()
{
    
    
    int a = 10;
    &a;//取出a的地址
    printf("%p\n", &a);
    return 0;
}

Follow this drawing example and it will print: 006FFD6C

&a takes out the address of the smaller byte among the 4 bytes occupied by a.

Although the integer variable occupies 4 bytes, as long as we know the first byte address, we can It is also feasible to follow the clues to access the 4-byte data. of.

2.2 Pointer variable dereference operator (*)

2.2.1 Pointer variables

The address we get through the address operator (&) is a numerical value, such as: 0x006FFD6C. Sometimes this value also needs to be stored for later use, then we store such an address value Where is it? The answer is: pointer variable.

for example:

#include <stdio.h>
int main()
{
    
    
    int a = 10;
    int* pa = &a;//取出a的地址并存储到指针变量pa中
    return 0;
}

Pointer variables are also a kind of variables. This type of variable is used to store addresses. The value stored in the pointer variable will be understood as an address.

2.2.2 How to disassemble pointer types

We see that the type of pa is int*, how should we understand the type of pointer?

int a = 10;
int * pa = &a;

Here, the words **int*,* written on the left side of pa indicate that pa is a pointer variable, and the previous int indicates that pa points to an object of type integer.

See the picture below for detailed explanation:

So if there is a char type variable ch, what type of pointer variable should the address of ch be placed in?

char ch = 'w';
pc = &ch;//pc 的类型怎么写呢?

If we think about it carefully, we can also write the following code:

char ch='w';
char *pc=&ch

The * in front means that pc is a pointer, and the char in front means that the pc points to the ch variable, which is the char type .

Similarly, if you want to put the addresses of other types of variables in pointer variables, you can just follow the above operations!

It can be seen that the pointer variable is used to store the value of the address, and the value stored in the pointer variable will be used as the address.

2.2.3 Dereference operator

We save the address and will use it in the future, so how to use it?

In real life, we use addresses to find a room where items can be taken or stored.

It is actually the same in C language. As long as we get the address (pointer), we can pass the object pointed to by the address (pointer), so we must learn an operator here called Dereference operator (*).
Insert image description here

1 #include<stdio.h>
2 int main()
3 {
    
    
4     int a = 100;
5     int* pa = &a;
6     *pa = 0;
7     return 0;
8 }

The 6th line in the above code uses thedereference operator. *pa means to find through the address stored in pa. The space pointed to, pa, is actually the variable a; **So pa=0, this operator changes the variable a to 0.

Someone must be thinking, if the purpose here is to change a to 0, wouldn't it be enough to write a=0? Why do we have to use pointers?

In fact, the modification of a is handed over to the pa operation. In this way, there will be one more way to modify a, and the code will be more flexible. We will gradually understand this modification method later.

2.3 Size of pointer variables

In the previous content, we learned that a 32-bit machine assumes 32 address buses. The electrical signal coming out of each address line is converted into a digital signal, which is 1 or 0. Then we regard the binary sequence generated by the 32 address lines as An address, then an address is 32 bits and requires 4 bytes to store.

If the pointer variable is used to store the address, then the size of the pointer needs 4 bytes of space.

Similarly, for a 64-bit machine, assuming there are 64 address lines, an address is a binary sequence composed of 64 binary bits. It requires 8 bytes of space to store, and the size of the pointer is 8 bytes.

See the following code for details:

#include <stdio.h>
//指针变量的大小取决于地址的大小
//32位平台下地址是32个bit位(即4个字节)
//64位平台下地址是64个bit位(即8个字节)
int main()
{
    
    
    printf("%zd\n", sizeof(char *));
    printf("%zd\n", sizeof(short *));
    printf("%zd\n", sizeof(int *));
    printf("%zd\n", sizeof(double *));
    return 0;
}

Let's take a look at the running results of 32-bit and 64-bit pointer variable sizes respectively:

Conclusion

  • On a 32-bit platform, the address is 32 bits, and the pointer variable size is 4 bytes.
  • On a 64-bit platform, the address is 64 bits, and the pointer variable size is 8 bytes.
  • It should be noted that the size and type of pointer variables have nothing to do with each other. As long as variables of pointer type have the same size on the same platform.

3. Type and meaning of pointer variables

We know beforeThe size of a pointer variable has nothing to do with its type. As long as it is a pointer variable, the size is the same on the same platform. Why are there all kinds of pointers? What about type?

In fact, pointer types have special meaning, we will continue to learn next.

3.1 Dereferencing of pointers

In this regard, in the following two pieces of code, we mainly observe memory changes during debugging.

//代码1
#include <stdio.h>
int main()
{
    
    
    int n = 0x11223344;
    int *pi = &n;
    *pi = 0;
    return 0;
}

Insert image description here

//代码2
#include <stdio.h>
int main()
{
    
    
    int n = 0x11223344;
    char *pc = (char *)&n;
    *pc = 0;
    return 0;
}

Insert image description here
We can see through debugging that Code 1 will change all 4 bytes of n to 0, but code 2 only changes the first two bytes of n to 0.

So we can draw the followingconclusion:
The type of the pointer determines how much authority you have when dereferencing the pointer (one time operate on several bytes).
For example: the pointer dereference of char star can only access one byte, and the pointer dereference of short star can only access the first two bytes. The dereference of the pointer of int can access four bytes.
![](https://img-blog.csdnimg.cn/img_convert/e20d86e2fbdf471b0b21bd6f657c3ed8.png

3.2 Pointer±integer

Let’s look at a piece of code first, debug and observe the change of address~

#include <stdio.h>
int main()
{
    
    
	int n = 10;
	char* pc = (char*)&n;
	int* pi = &n;
	printf("%p\n", &n);
	printf("%p\n", pc);
	printf("%p\n", pc + 1);
	printf("%p\n", pi);
	printf("%p\n", pi + 1);
	return 0;
}

The result of running the code is as follows:

We can see that the pointer variable +1 of type char star skips 1 byte, while the pointer variable +1 of type int* skips 4 bytes.

It can be seen that this is the change brought about by the type of pointer variables.

Conclusion: The type of pointer determines how far (distance) the pointer moves forward or backward. Equivalent to the step size of the pointer.

4.const modified pointer

4.1 const modified variables

We all know that variables can be modified. If the address of the variable is given to a pointer variable, the variable can also be modified through the pointer variable. However, if we want a variable to have some restrictions and cannot be modified, How to do it? Then at this time we need to use the C language keyword const!

First let's take a look at the following lines of code:

#include <stdio.h>
int main()
{
    
    
	const int m = 100;
	m = 20;//m是可以修改的吗?
	printf("%d\n", m);
	return 0;
}

So can this code run? Let’s run it with vs~

img

From the picture, we can know that VS has already reported an error message on line 413, so we think that m in the above code cannot be modified. In fact, m is essentially a variable, but after being modified by const, in terms of syntax Restrictions have been added. As long as we modify m in the code, it will not comply with the grammatical rules and an error will be reported, making it impossible to modify m directly.

But if we bypass m and use the address of m, we can do it by modifying m, although doing so is breaking the grammatical rules.

For example: we can look at the following lines of code:

#include <stdio.h>
int main()
{
    
    
    const int m = 0;
    printf("m = %d\n", m);
    int*p = &m;
    *p = 20;
    printf("m = %d\n", m);
    return 0;
}

Output result:

From here we can see that this one has indeed been modified, but if we think about it carefully, why does m need to be modified by const? It is so that m cannot be modified, but if p can modify m through the address of m, this breaks the restriction of const, which is unreasonable, so p should not be able to modify m even if it gets the address of m, then what follows How to do it?

4.2 const modified pointer variable

Let’s take a look at the following code to analyze~

#include <stdio.h>
//代码1
void test1()
{
    
    
	int n = 10;
	int m = 20;
    int *p = &n;
    *p = 20;//ok?
    p = &m; //ok?
}
void test2()
{
    
    
    //代码2
    int n = 10;
    int m = 20;
    const int* p = &n;
    *p = 20;//ok?
    p = &m; //ok?
}
void test3()
{
    
    
    int n = 10;
    int m = 20;
    int *const p = &n;
    *p = 20; //ok?
    p = &m; //ok?
}
void test4()
{
    
    
    int n = 10;
    int m = 20;
    int const * const p = &n;
    *p = 20; //ok?
    p = &m; //ok?
}
int main()
{
    
    
    //测试无const修饰的情况
    test1();
    //测试const放在*的左边情况
    test2();
    //测试const放在*的右边情况
    test3();
    //测试*的左右两边都有const
    test4();
    return 0;
}

Let's first look at the situation where const is placed on the left side of *. What error will occur?

We can know from the error message on line 425 of vs that when const is placed to the left of int, the modification is *p, and the restriction is that p cannot be used to perform the dereference operation to change the content of n itself, and p itself can be changed

And if const is placed on the right side of *, what error will occur?

From the error message on line 426 of vs, we can know that when const is placed on the right side of *, the pointer variable itself is modified, which means that the content of the pointer variable p cannot be modified, but the content pointed to by p can be modified.

So if const is placed on the left and right of *, what error will occur?

From the error messages in lines 425 and 426 of vs, we can know that if the left and right sides of * are modified with const, then the pointer variables themselves p and the contents pointed to by p cannot be changed, which is equivalent to being pressed to death. dead.

Therefore, we can draw the followingconclusion:

When const modifies pointer variables

  • If Const is placed on the left side of *, it modifies the content pointed by the pointer, ensuring that the content pointed by the pointer cannot be changed through the pointer. But the content of the pointer itself is mutable.
  • If Const is placed on the right side of *, it modifies the pointer variable itself, ensuring that the content of the pointer variable cannot be modified, but the content pointed to by the pointer can be changed through the pointer.

5. Pointer arithmetic

There are three basic operations on pointers, namely:

  • pointer±integer
  • pointer - pointer
  • Relational operations on pointers

5.1 Pointer±integer

We all know that usually when we print elements in an array, we can print by accessing its subscript, as shown in the following figure:

So if we want to use pointers to print the elements in the array, what should we do?

We can think of it this way, because arrays are stored continuously in memory. As long as you know the address of the first element, you can easily find all subsequent elements.

1 int arr[10]={
    
    1,2,3,4,5,6,7,8,9,10}

The specific code implementation is as follows:

#include <stdio.h>
//指针+- 整数
int main()
{
    
    
    int arr[10] = {
    
    1,2,3,4,5,6,7,8,9,10};
    int *p = &arr[0];
    int i = 0;
    int sz = sizeof(arr)/sizeof(arr[0]);
    for(i=0; i<sz; i++)
    {
    
    
    	printf("%d ", *(p+i));//p+i 这里就是指针+整数
    }
    return 0;
}

Let’s analyze the above lines of code. First of all, this line of codeint *p=&arr[0], we first Put the address of the first element of the array into the integer pointer p.

And what does *(p+i) in the for loop mean?

We can know through debugging that each element in the array is an integer, is stored continuously, and occupies 4 bytes.

For example, let’s say that the pointer variable int *p gets the first element address 0x008FFAF4, because as we said before, &arr[0] takes out arr[0 ] The address of the smaller byte among the 4 bytes occupied by it.

And what happens if we add 1 to its address? Through analysis, we can know that since this P is a pointer of type int*, if it adds one, it will jump directly to 4 bytes. Since it is an integer pointer, if it adds one, it will jump directly. Passing an integer element means skipping 4 bytes. From the above picture, we can also know that p+i here also gets the address of the &arr[i] element.

So we can deduce this formula: p+n=n*sizeof(int)

Because p is an integer pointer, so p+n skips n*sizeof( int) bytes.

But what if we find the element we want to find through this address. We can perform the previous dereference* operation on (p+i), so that we can get the value of the corresponding element pointed to by the address.

The running results are as follows:

Of course, in this for loop, we can also let i loop ten times, then dereference *p, and then perform p++, and the essence of p++ is p=p+1.
In fact, it is the same as the above code. P plus an integer element are assigned to p, and then through the dereference operation, we find the value of the element pointed to by the address. In this way, we loop 10 times. You can also print the elements of the array.

The specific code and running examples are shown in the figure below:
Please add image description

Similarly, let us give you another example of a character pointer. Specifically, look at the following code:

#include <stdio.h>
int main(){
    
    

	char str[]="abcdef";
	char *pc=&str[0];
	while(*pc!='\0'){
    
    
		printf("%c",*pc);
		pc++;
	}

	return 0;
}

For example, I have defined a character array of str here.The str array stores abcdef and \0. In the same way, we first put str[0] first The address of the element is given to the star pc, and then because \0 is the end mark of the string.

Then we can write the loop condition as *pc! ='0', then we can print out all the abcdef characters in front of \0. Each cycle will dereference it, find the corresponding element and print it, and then pc++, add a char type to pc The pointer is assigned to pc until the character f is printed out, and then pc++ points to the character \0, but \0 is ='\0', which obviously does not meet the loop conditions, then it will jump out of the while loop. .

5.2 Pointer-pointer

Let’s take a look at the following line of code first. Let’s see what the result is.

#include <stdio.h>
int main(){
    
    

	int arr[10]={
    
    0};
	int ret=&arr[9]-&arr[0];
	printf("%d\n",ret);
	return 0;
}

Let’s take a look at the results~
Insert image description here

Then if I rewrite the 461 lines of code into int ret=&arr[0]-&arr[9];, what will happen? Let's run it and give it a try.

img

The result is -9, then we can draw the following conclusion:

The absolute value of the value obtained by pointer-pointer (address-address) is the number of elements between the pointer and the pointer.

And there is a prerequisite:Both pointers point to the same space.

Why do you say that?

Let’s take a look, Suppose I define a char type array ch, and then assign the values ​​​​of &arr[9]-&ch[5] to ret , and let it calculate the number of elements between the two arrays.

Let's take a look at the following lines of code:

#include <stdio.h>
int main(){
    
    

	int arr[10]={
    
    0};
	char ch[7]={
    
    0};
	int ret=&arr[9]-&ch[5];
	printf("%d\n",ret);
	return 0;
}

Let's first draw a picture to see where the addresses of arr[9] and ch[5] are.

Although I can find the addresses of these two arrays, the number of elements between them is uncertain. Secondly, is the number of elements in the two arrays calculated as int type or char type? Come and count? Isn't this just nonsense? Therefore these two calculations are meaningless.

After we have this concept, what is the use of pointers? So let’s give you another example?

Let’s first take a look at the following lines of code~

#include <stdio.h>
#include <string.h>

int main(){
    
    
	char arr[]="abcdef";
	int lens=strlen(arr);
	printf("%d\n",lens);
	return 0;
}

From the above lines of code we can know:
strlen calculates the length of the string. Here, strlen calculates the length of the arr array. Since the arr array stores strings, and \0 is the end mark of the string, strlen will count the characters before \0, and the calculation result is 6 .

We can also take a look at the introduction of the strlen function on the official function website~

If you are interested, you can click on the website to check the usage of this function:
C/C++ function official website

So if we want to use pointers to simulate and implement this strlen function, what should we do?
We can write this code like this:

#include <stdio.h>
#include <string.h>

int my_strlen(char* str) {
    
    
	int count = 0;
	while (*str != '\0') {
    
    
		count++;
		str++;
	}
	return count;
}

int main(){
    
    
	char arr[] = "abcdef";
	int lens =my_strlen(arr);
	printf("%d\n", lens);
	return 0;
}

First, let’s take a look at the following line of code

int lens =my_strlen(arr);

The essence of this code is to pass the character a to the my_strlen function. Why do you say this? Let's look at the picture below:
Insert image description here
From the picture, we can see: The array name is the address of the first element of the array. So in the my_strlen function parameter part, we can use the pointer form char*str to receive it, so that we can pass the address of the first element a.

When we pass the address of a into str, we can define a variable count, and then add the condition *str!='0’ in the while loop. But star str cannot always point to the address of 'a'.
So we need to add the statement str++ in the loop body until str points to '\0', that is to say, it is equal to '\0', then we jump out of the while loop, then to When return count is equivalent to returning the number of count.

The following is the result of running this code:
Please add image description
Of course, this code still needs to be modified, for example, it should be changed to the following:

#include <stdio.h>
#include <string.h>

int my_strlen(char* str) {
    
    
	char* pc =str;
	while (*str != 0)
		str++;
	return str - pc;
}

int main() {
    
    
	char arr[] = "abcdef";
	int lens =my_strlen(arr);
	printf("%d\n", lens);
	return 0;
}

In the function my_strlen part above, we first use char*str to receive the element of a, and then in the function body, we use char star pc to receive the str element a as its starting point mark, and then in In the while loop, as long as str!='\0', it will continue to add 1 until str points to '\0', and it will jump out of the loop body.
And because the two pointer variables pc and str are of char star type, the number of the middle interval elements is obtained by subtracting them.

The following is an animation demonstration, you can take a look~
Insert image description here
The result of running the code is as follows:
Insert image description here

Insert image description here

5.3 Relational operations on pointers

Let's take a look at this code:

//指针的关系运算
#include <stdio.h>
int main()
{
    
    
    int arr[10] = {
    
    1,2,3,4,5,6,7,8,9,10};
    int *p = &arr[0];
    int i = 0;
    int sz = sizeof(arr)/sizeof(arr[0]);
    while(p<arr+sz) //指针的大小比较
    {
    
    
        printf("%d ", *p);
        p++;
    }
    return 0;
}

From this code, we can see that we first use *p to receive the address of the first element &arr[0] of the array, and then use this line of code sizeof(arr)/sizof(arr[0] ) Calculate the number of elements in the array, and then assign the number of elements to the variable sz. Then in the while loop condition, we write the condition as (p<arr+sz) .
Some people may have questions, why do you write this?

In fact we can look at the picture below:

From the picture, we can know that because arr is the array name and it is the address of the first element of the array, then we can know that the pointer variable p and the arr array both point to the address of the first element of the array, because The index of the first element of the array is 0, and the index of the last element of the array is 9. Then the index of the last element is sz, which is 10.
So its address is equivalent to the address of the starting position + 10, and you can point to it by skipping 10 elements. And because the array name is the address of the first element of the array. Therefore, we can write the loop condition as while(p<arr+sz), and then in the loop body, we first dereference the address in p, find the object pointed to by the address, and then perform p++, because p itself is a Integer pointer, so each time p+1 skips an integer. Until the address of p is greater than or equal to the address of arr+sz, then it will break out of the loop.

So let’s take a look at the results of this code~

In fact we can change line 91 of the code to this:

int *p=arr;

Because the array name is the address of the first element of the array, so in fact the running results of these two codes are the same, we might as well do Take a look~



Okay, that’s all today’s content is shared here


If you think what the blogger said is good,


Welcome to support with one click and three links


Thanks! ! ! ! ! !

Insert image description here

Guess you like

Origin blog.csdn.net/m0_63564767/article/details/133561683