在研究JM8.6中内存分配模块(memalloc.c)时,看到如下代码(注意入参的byte ***):
如果C语言基础较好的话,对上面的实现也比较好理解。也即是双指针的初始化。
但是再看到下面:
以及再下面:
可真得花点时间去琢磨内存如何布局的,以及如何访问到期望位置的值。
本篇文章就准备将背景知识和上面的三个内存分配函数,介绍一下,以供有此困惑的人来理解。
其实,很早之前就有该想法,针对C语言的指针来做一次科普,但由于拖延症的缘故一直没动笔。
本篇是基础篇,后面会再开一篇来介绍上面三个函数的原理及内存布局。
1. 什么是指针?
所谓指针,就是某个地址空间,存储着一个值(指针值),这个值为某个内存地址。
如下图:(请原谅我拙劣的画图水平,拿win10自带的绘图工具画的。。。)
拿32位系统来进行说明,每个指针(不管什么类型的指针——内置类型或自定义类型)的sizeof都为4Bytes,并且为
了高效访问,一般都是4字节对齐的(存储ptr值的这段内存地址addr2,打印其值,最后两个二进制位为0)。
上图中,内存空间addr2中存储着一个指针值——ptr(ptr的值为addr1),即这个ptr指针指向addr1这个地址空间。
2. 栈指针 & 堆指针
计算机系统中两种内存类型——stack和heap,stack为栈内存,如函数内临时变量、函数参数,heap为堆内存,如malloc
分配的空间,其从系统中获取,一般分配和回收使用伙伴算法(buddy)。
其中stack增长方向向下,heap增长方向向上,如下demo及打印:
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 void stack_test() 5 { 6 int tmp0; 7 int tmp1; 8 int tmp2; 9 printf("stack_addr: &tmp0=%p, &tmp1=%p, &tmp2=%p ", &tmp0, &tmp1, &tmp2); 10 } 11 12 void heap_test() 13 { 14 char *ptr0 = (char*)malloc(64); 15 char *ptr1 = (char*)malloc(64); 16 char *ptr2 = (char*)malloc(64); 17 printf("heap_addr: ptr0=%p, ptr1=%p, ptr2=%p ", ptr0, ptr1, ptr2); 18 } 19 20 int main(void) 21 { 22 stack_test(); 23 heap_test(); 24 }
从运行结果看,tmp0~2这种stack变量的地址,从高地址往低地址变化,而ptr0~2这种指向heap空间的值,从低往高变化。
然而,ptr0~2作为stack类型变量,其地址仍符合stack的增长方向(从高往低变化)。不信可以打印出&ptr0, &ptr1, &ptr2的值。
3. 如何给一个指针变量赋值?
使用如下方式:
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 void mem_alloc(char **pp)
5 {
6 *pp = (char*)malloc(64);
7 }
8
9 int main()
10 {
11 char *ptr0;
12 char *ptr1;
13 char *ptr2;
14
15 char ch = 'A';
16 ptr0 = &ch; //ptr0指向stack变量ch
17 ptr1 = (char*)&ptr2; //ptr1指向ptr2这个stack变量的地址
18 mem_alloc(&ptr2); //给ptr2这个stack变量赋值,赋值为heap空间地址
19 printf("stack_addr: &ptr0=%p, &ptr1=%p, &ptr2=%p, &ch=%p
", &ptr0, &ptr1, &ptr2, &ch);
20 printf("ptr_val: ptr0=%p, ptr1=%p, ptr2=%p
", ptr0, ptr1, ptr2);
21 }
需要注意一点的是,如果通过函数调用给一个变量初始化,那么参数必须是该变量的地址,如18行的:mem_alloc(&ptr2);
为什么?
传地址才能修改该地址处的值(ptr2这个stack变量的内存地址空间中,所保存的值——指向哪儿),而传值只是进行了一份数据拷贝,
等调用的函数退出后,原先被拷贝的变量什么都没改变。
因此,如果想修改15行中ch这个stack变量的值,就传其地址:&ch;如果想修改13行的ptr2这个stack变量的值,也传其地址:&ptr2,
正如18行中所调用的。
4. 指针、双指针、三指针、四指针
指针也可以称为单指针,类似于这种:void* ptr; 如上面代码中的ptr0,ptr1,ptr2。
双指针为指向指针的指针,类似于:void** pptr; 如第4行中的形参:void mem_alloc(char **pp)。
三指针其实又多了一个*,形如:void*** ppptr; 可以理解为一个指针指向一个双指针,而该三指针变量值(该变量内存空间中的值)为第一个指针值。
四指针也又多了一个*,形如:void**** pppptr; 再多一道中转。
画个图来表达:
其中,存储字符的空间我故意画小,因为其大小为1Byte,而指针的大小为4Bytes。