在研究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\n", &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\n", 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\n", &ptr0, &ptr1, &ptr2, &ch);
20     printf("ptr_val: ptr0=%p, ptr1=%p, ptr2=%p\n", 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。