虚拟内存 虚拟内存作为一个中间层,避免程序直接与物理内存交互,因而程序中访问的内存地址不再是真实的物理内存地址,而是每个进程独有的虚拟内存地址,虚拟内存地址由操作系统映射到适当的物理内存地址上,至于如何映射与开发者无关。
在只启动一次的情况下,一个程序只可以拥有一个进程,多个程序的多个进程拥有结构相同的虚拟内存,在典型的32
位系统中,虚拟内存空间总大小为4GB
(64
位系统远大于4G
),其中包括用户空间 和内核空间
用户空间,占用虚拟内存空间3G
,包括代码段(Code Segment):也称为文本段,存储程序的机器码指令。通常是只读的,因为程序在运行时不应该修改自己的指令。 数据段(Data Segment):存储全局变量和静态变量。数据段分为初始化的数据段(Initialized Data Segment)和未初始化的数据段(Uninitialized Data Segment,也称为BSS段)。初始化的数据段包含在程序中明确初始化的全局和静态变量,而未初始化的数据段包含未初始化的全局和静态变量,这些变量会在程序运行时被初始化为零或空。 堆(Heap):动态内存分配的区域,由程序员进行手动管理。使用malloc()
、calloc()
、realloc()
等函数分配内存,并使用free()
函数释放内存。 栈(Stack):存储局部变量、函数参数和函数调用的返回地址。栈是一种后进先出(LIFO)的数据结构,它在函数调用时分配局部变量的空间,当函数返回时释放这些空间。 常量区(Constant Area):存储常量字符串,例如字符串字面量。这部分内存通常是只读的。 内存映射区域(Memory-mapped Region):存储与文件关联的内存映射区域。这包括共享内存等。 内核空间,占用虚拟内存空间1G
(0xC000_0000 ~ 0xFFFF_FFFF) 指针概念 在典型的32
位系统中,虚拟内存空间总大小为4GB
,因为4GB
=4,096MB
=4,194,304KB
=4,294,967,296byte
,即虚拟内存空间共有4,294,967,296
个字节; 8
位16
进制从0x0000_0000
到0xffff_ffff
刚好4,294,967,296
个数,每个数对应虚拟内存中的一个字节,将8
位16
进制中的每个数都作为虚拟内存的字节的内存地址;于是虚拟内存的内存地址范围从0x0000_0000
到0xffff_ffff
对于一个多字节的变量,例如一个占用4
字节内存的int
变量,假设其第一个字节储存在虚拟内存中的地址为0x7fc5_3001
,于是其最后一个字节的地址就为0x7fc5_3004
,那么它编号最小的内存地址0x7fc5_3001
就代表它本身的地址
1 2 3 4 5 6 7 8 9 10 int num = 1 ;int * pointer = &numprintf ("pointer = %p" , pointer);
指针变量 定义指针变量
取变量所在的内存地址
1 2 3 4 int num = 100 ;int * pointer = #printf ("pointer = %p" ,pointer);
取指针变量所指向的内存地址储存的值
1 2 3 4 5 6 7 int num1 = 100 ;int * pointer = &num1;int num2 = *pointer;printf ("pointer = %d" ,num2);
不同数据类型的指针只能存储对应类型的内存地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 char ch = 'A' ;char * ch_pointer = &ch;short int sh = 10 ;short int * sh_pointer = &sh;int in = 10 ;int * in_pointer = ∈long int lo = 10 ;long int * lo_pointer = &lo;float fl = 10.0f ;float * fl_pointer = &fl;double dou = 10.0 ;double * dou_pointer = &dou;
通用指针,可以储存任何变量类型的指针
1 2 int num = 100 ;void * pointer = #
指针和数组的关系 数组名代表数组首元素的内存地址,因此数组名可以直接赋值给指针变量,而实际上赋值时传递的是数组的地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int main () { int arr[] = { 1 ,2 ,3 ,4 ,5 }; int * pointer_1 = arr; int * pointer_2 = &arr[0 ]; printf ("pointer_1 = %p\n" , pointer_1); printf ("pointer_2 = %p\n" , pointer_2); return 0 ; }
数组名只可作为不可变的左值,因为数组名表示数组的起始地址,而指针声明时是左值,但也可以作为右值
左值(lvalue) 是指表达式结束后依然存在的持久化对象,通常是一个标识符(变量、数组元素、结构体成员等)。左值可以出现在赋值运算符的左边,表示一个可以被赋值的位置。在int x = 42;
中,x
是左值,因为它是一个标识符,可以出现在赋值运算符的左边, 右值(rvalue) 是指表达式结束后不再存在的临时对象,通常是一个常量、表达式的结果或临时计算得到的值。在int y = 10 + 5;
中,10 + 5
是右值,因为它是一个临时计算得到的值,不能被直接赋值; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int main () { int arr1[3 ] = { 1 ,2 ,3 }; int num = 10 ; int * pointer1 = # arr1 = pointer1; int arr2[] = arr1; int * pointer2 = arr1; pointer1 = pointer2; return 0 ; }
数组名不能进行自增或自减运算,而指针变量可以
数组首元素的指针自加 可以移动到数组第二个元素,此时指针指向第二个元素,两元素地址 十六进制值相差为一个数组元素的类型所占用字节 的大小,例如int
类型数组,那么两元素地址的十六进制值将相差4
字节。注意,指针自加 或+整数 的方式只有在数组元素中才有意义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 int main () { int arr[] = { 1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 }; int * pointer = arr; printf ("num = %d, pointer = %p\n" , *pointer, pointer); pointer++; printf ("num = %d, pointer = %p\n" , *pointer, pointer); pointer+=2 ; printf ("num = %d, pointer = %p\n" , *pointer, pointer); pointer--; printf ("num = %d, pointer = %p\n" , *pointer, pointer); return 0 ; }
同一数组两元素的两个指针可以相减,将会得到两个元素之间相差的元素的个数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int main () { int arr[] = { 1 ,2 ,3 }; int * pointer1 = &arr[0 ]; int * pointer2 = &arr[1 ]; int * pointer3 = &arr[2 ]; int minus_1 = (int )(pointer3 - pointer1); printf ("minus_1 = %d\n" , minus_1); int minus_2 = (int )(pointer2 - pointer1); printf ("minus_2 = %d\n" , minus_2); return 0 ; }
sizeof
操作符对指针和数组名使用的结果不同,
对数组名使用sizeof
得到的是整个数组的大小, 对指针变量使用sizeof
得到的是指针本身的大小; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int main () { int arr[] = { 1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 }; int * pointer = arr; printf ("%zu\n" , sizeof (arr)); printf ("%zu\n" , sizeof (pointer)); return 0 ; }
数组元素的指针加下标的方式也能调用数组中的元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int main () { int arr[] = { 1 ,2 ,3 ,4 ,5 }; int * pointer = arr; int num = pointer[1 ]; printf ("num = %d\n" , num); return 0 ; }
如果取数组中第二个元素arr[1]
的指针,并以arr[1]
的指针加下标的方式调用数组中的元素,那么调用arr[1]
将使用下标0
,调用其他元素的下标均在原数组下标基础上-1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int main () { int arr[] = { 1 ,2 ,3 ,4 ,5 }; int * pointer = &arr[1 ]; printf ("value1 = %d\n" , pointer[-1 ]); printf ("value2 = %d\n" , pointer[0 ]); printf ("value3 = %d\n" , pointer[1 ]); return 0 ; }
同理,如果取第三个元素arr[2]
的指针,用arr[2]
的指针加下标的方式调用数组中的元素,那么调用arr[2]
将使用下标0
,调用其他元素的下标均在元素组下标基础上-2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int main () { int arr[] = { 1 ,2 ,3 ,4 ,5 }; int * pointer = &arr[2 ]; printf ("value1 = %d\n" , pointer[-1 ]); printf ("value2 = %d\n" , pointer[0 ]); printf ("value3 = %d\n" , pointer[1 ]); return 0 ; }
其实我发现如果指针指向变量,也可以用像数组一样,取指针指向的变量的值
1 2 3 4 5 6 7 8 9 int main () { int num = 10 ; int * pointer = # printf ("pointer = %d\n" , pointer[0 ]); return 0 ; }
指针数组 由指针组成的数组,其本质上还是一个数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int main () { int * p[5 ]; int arr[5 ] = { 1 ,2 ,3 ,4 ,5 }; for (size_t i = 0 ; i < 5 ; i++) { p[i] = &arr[i]; } for (size_t i = 0 ; i < 5 ; i++) { printf ("pointer = %p\n" , p[i]); printf ("num = %d\n" , *p[i]); } return 0 ; }
二级指针(多重指针) 指针作为一个占用4
字节内存的数据,它也有自己的内存地址,那么指针的内存地址也可以用另一个指针记录,如下代码pointer_pointer
指向指针pointer
的地址
1 2 3 4 5 6 7 8 9 10 11 12 13 int main () { int num = 100 ; int * pointer = # printf ("pointer = %p\n" , pointer); int ** pointer_pointer = &pointer; printf ("pointer_pointer = %p\n" , pointer_pointer); return 0 ; }
字符指针 字符串有下面四种常见写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 int main () { char str1[] = { 'h' ,'e' ,'l' ,'l' ,'o' ,'\0' }; char str2[] = {"hello" }; char str3[] = "hello" ; char * str4 = "hello" ; printf ("str1=%s, str2=%s, str3=%s, str4=%s\n" , str1, str2, str3, str4); str1[0 ] = 'H' ; str2[0 ] = 'H' ; str3[0 ] = 'H' ; str4 = "Hello" ; printf ("str1=%s, str2=%s, str3=%s, str4=%s\n" , str1, str2, str3, str4); return 0 ; }
字符串指针即便转换成char
数组,依然不能够修改指定的某个元素值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int main () { char * str = "hello" ; test1(str); return 0 ; } void test1 (char arr[]) { arr[0 ] = 'H' ; printf ("arr[0] = %c\n" , arr[0 ]); }
给char
类型的指针直接赋值字符串,相当于将一个char
数组构成的字符串赋值给指针,不同的是这样的指针是可以修改指定元素值的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int main () { char str1[] = { 'h' ,'e' ,'l' ,'l' ,'o' ,'\0' }; char * str4 = str1; printf ("str1=%s, str2=%s\n" , str1, str4); str4[0 ] = 'H' ; printf ("str1=%s, str2=%s\n" , str1, str4); return 0 ; }
数组指针 数组指针 不同于指针数组 ,指针数组是一个数组,其中的每个元素都是指针,数组指针是指向数组的指针,它指向数组的首元素地址
数组指针通常以下面这种格式定义
1 数据类型(*数组指针变量名)[长度] = &数组名
由于数组名代表数组首元素的内存地址,所以也可以省略取地址符号&
1 数据类型(*数组指针变量名)[长度] = 数组名
一维数组的指针
(*pointer)
表示pointer
是一个指向数组的指针,指向的数组包含4
个int
类型元素;解引用*pointer
将提供整个数组,(*pointer)[0]
将给出数组的第一个元素;不过其实一维数组完全没必要用这样的数组指针定义,直接把数组名赋值给一个与数组元素相同类型的指针就行了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int main () { int arr[5 ] = { 1 ,2 ,3 ,4 }; int (*pointer)[] = &arr; printf ("pointer = %p\n" , pointer); printf ("*pointer = %p\n" , *pointer); printf ("*pointer[0] = %p\n" , &((*pointer)[0 ])); printf ("%d, %d, %d, %d\n" , (*pointer)[0 ], (*pointer)[1 ], (*pointer)[2 ], (*pointer)[3 ]); return 0 ; }
用指针调用数组中的元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int main () { int arr[5 ] = { 1 ,2 ,3 ,4 }; int (*pointer)[] = &arr; int * arr_p = *pointer; printf ("%d\n" , *arr_p); arr_p++; printf ("%d\n" , *arr_p); return 0 ; }
二维数组的指针
当arr
是如下的二维数组时,pointer
依然是一个指向包含4
个int
类型元素的数组的指针;解引用*pointer
将提供二维数组第一行的数组,(*pointer)[0]
将给出第一行数组的第一个元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 int main () { int arr[3 ][4 ] = { {1 , 2 , 3 , 4 }, {5 , 6 , 7 , 8 }, {9 , 10 , 11 , 12 } }; int (*pointer)[4 ] = arr; int * arr_0 = *pointer; printf ("%d, %d, %d, %d\n" , arr_0[0 ], arr_0[1 ], arr_0[2 ], arr_0[3 ]); pointer++; int * arr_1 = *pointer; printf ("%d, %d, %d, %d\n" , arr_1[0 ], arr_1[1 ], arr_1[2 ], arr_1[3 ]); pointer++; int * arr_2 = *pointer; printf ("%d, %d, %d, %d\n" , arr_2[0 ], arr_2[1 ], arr_2[2 ], arr_2[3 ]); return 0 ; }
如果不使用移动指针的方式,也可以用指针直接调用二维数组中的元素,通过pointer[i][j]
的方式,访问不同数组中的元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 int main () { int arr[3 ][4 ] = { {1 , 2 , 3 , 4 }, {5 , 6 , 7 , 8 }, {9 , 10 , 11 , 12 } }; int (*pointer)[4 ] = arr; int * arr_0 = pointer[0 ]; printf ("%d, %d, %d, %d\n" , arr_0[0 ], arr_0[1 ], arr_0[2 ], arr_0[3 ]); int * arr_1 = pointer[1 ]; printf ("%d, %d, %d, %d\n" , arr_1[0 ], arr_1[1 ], arr_1[2 ], arr_1[3 ]); int * arr_2 = pointer[2 ]; printf ("%d, %d, %d, %d\n" , arr_2[0 ], arr_2[1 ], arr_2[2 ], arr_2[3 ]); return 0 ; }
三维数组的指针
当arr
是三维数组时,pointer
是一个指向二维数组的指针,指向的二维数组中包含了3
个一维数组,每个一维数组又包含4
个int
类型的元素;通过pointer++
可以移动指针到下一个二维数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 int main () { int arr[2 ][3 ][4 ] = { { {1 , 2 , 3 , 4 }, {5 , 6 , 7 , 8 }, {9 , 10 , 11 , 12 } }, { {13 , 14 , 15 , 16 }, {17 , 18 , 19 , 20 }, {21 , 22 , 23 , 24 } } }; int (*pointer)[3 ][4 ] = arr; int * arr_0_0 = (*pointer)[0 ]; printf ("%d, %d, %d, %d\n" , arr_0_0[0 ], arr_0_0[1 ], arr_0_0[2 ], arr_0_0[3 ]); int * arr_0_1 = (*pointer)[1 ]; printf ("%d, %d, %d, %d\n" , arr_0_1[0 ], arr_0_1[1 ], arr_0_1[2 ], arr_0_1[3 ]); int * arr_0_2 = (*pointer)[2 ]; printf ("%d, %d, %d, %d\n" , arr_0_2[0 ], arr_0_2[1 ], arr_0_2[2 ], arr_0_2[3 ]); pointer++; int * arr_1_0 = (*pointer)[0 ]; printf ("%d, %d, %d, %d\n" , arr_1_0[0 ], arr_1_0[1 ], arr_1_0[2 ], arr_1_0[3 ]); int * arr_1_1 = (*pointer)[1 ]; printf ("%d, %d, %d, %d\n" , arr_1_1[0 ], arr_1_1[1 ], arr_1_1[2 ], arr_1_1[3 ]); int * arr_1_2 = (*pointer)[2 ]; printf ("%d, %d, %d, %d\n" , arr_1_2[0 ], arr_1_2[1 ], arr_1_2[2 ], arr_1_2[3 ]); return 0 ; }
通过pointer[i][j][k]
的方式,直接访问不同二维数组中的元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 int main () { int arr[2 ][3 ][4 ] = { { {1 , 2 , 3 , 4 }, {5 , 6 , 7 , 8 }, {9 , 10 , 11 , 12 } }, { {13 , 14 , 15 , 16 }, {17 , 18 , 19 , 20 }, {21 , 22 , 23 , 24 } } }; int (*pointer)[3 ][4 ] = arr; int * arr_0_0 = pointer[0 ][0 ]; printf ("%d, %d, %d, %d\n" , arr_0_0[0 ], arr_0_0[1 ], arr_0_0[2 ], arr_0_0[3 ]); int * arr_0_1 = pointer[0 ][1 ]; printf ("%d, %d, %d, %d\n" , arr_0_1[0 ], arr_0_1[1 ], arr_0_1[2 ], arr_0_1[3 ]); int * arr_0_2 = pointer[0 ][2 ]; printf ("%d, %d, %d, %d\n" , arr_0_2[0 ], arr_0_2[1 ], arr_0_2[2 ], arr_0_2[3 ]); int * arr_1_0 = pointer[1 ][0 ]; printf ("%d, %d, %d, %d\n" , arr_1_0[0 ], arr_1_0[1 ], arr_1_0[2 ], arr_1_0[3 ]); int * arr_1_1 = pointer[1 ][1 ]; printf ("%d, %d, %d, %d\n" , arr_1_1[0 ], arr_1_1[1 ], arr_1_1[2 ], arr_1_1[3 ]); int * arr_1_2 = pointer[1 ][2 ]; printf ("%d, %d, %d, %d\n" , arr_1_2[0 ], arr_1_2[1 ], arr_1_2[2 ], arr_1_2[3 ]); return 0 ; }
个人一点小看法
如果将一维数组视作是一个仅包含一个元素的二维数组,即将int arr[] = { 1,2,3,4,5 }
视作int arr[] = { { 1,2,3,4,5 } }
,那么二维数组和一维数组的用法以及定义就相互吻合了 而且如果不这样的话,无法解释pointer[i]
不能调用其指向的一维数组int arr[5] = { 1,2,3,4 }
中的元素的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int main () { int arr[1 ][4 ] = { { 1 ,2 ,3 ,4 } }; int (*pointer)[4 ] = arr; int * arr_0 = *pointer; printf ("%d, %d, %d, %d\n" , arr_0[0 ], arr_0[1 ], arr_0[2 ], arr_0[3 ]); return 0 ; }
用指针直接调用的方式,使用pointer[i][j]
访问一维数组中的元素;即使arr
是个一维数组,也要通过这种方式,而不是pointer[i]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int main () { int arr[1 ][4 ] = { { 1 ,2 ,3 ,4 } }; int (*pointer)[4 ] = arr; int * arr_0 = pointer[0 ]; printf ("%d, %d, %d, %d\n" , arr_0[0 ], arr_0[1 ], arr_0[2 ], arr_0[3 ]); return 0 ; }
同样二维数组也可以视作仅包含一个元素的三维数组,只不过这种情况下二维数组的指针(*pointer)[0]
不是一个左值,所以无法通过移动指针位置的方式选择二维数组中的一维数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 int main () { int arr[1 ][3 ][4 ] = { { {1 , 2 , 3 , 4 }, {5 , 6 , 7 , 8 }, {9 , 10 , 11 , 12 } } }; int (*pointer)[3 ][4 ] = arr; int * arr_0 = (*pointer)[0 ]; printf ("%d, %d, %d, %d\n" , arr_0[0 ], arr_0[1 ], arr_0[2 ], arr_0[3 ]); int * arr_1 = (*pointer)[1 ]; printf ("%d, %d, %d, %d\n" , arr_1[0 ], arr_1[1 ], arr_1[2 ], arr_1[3 ]); int * arr_2 = (*pointer)[2 ]; printf ("%d, %d, %d, %d\n" , arr_2[0 ], arr_2[1 ], arr_2[2 ], arr_2[3 ]); return 0 ; }
通过pointer[i][j][k]
的方式,直接访问不同二维数组中的元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 int main () { int arr[1 ][3 ][4 ] = { { {1 , 2 , 3 , 4 }, {5 , 6 , 7 , 8 }, {9 , 10 , 11 , 12 } } }; int (*pointer)[3 ][4 ] = arr; int * arr_0 = pointer[0 ][0 ]; printf ("%d, %d, %d, %d\n" , arr_0[0 ], arr_0[1 ], arr_0[2 ], arr_0[3 ]); int * arr_1 = pointer[0 ][1 ]; printf ("%d, %d, %d, %d\n" , arr_1[0 ], arr_1[1 ], arr_1[2 ], arr_1[3 ]); int * arr_2 = pointer[0 ][2 ]; printf ("%d, %d, %d, %d\n" , arr_2[0 ], arr_2[1 ], arr_2[2 ], arr_2[3 ]); return 0 ; }