C语言二级指针用法之模拟句柄用途

先导

因为在学习中使用到了二级指针进行操作,故而作此总结

提前了解

句柄

  • 以下词条来自百度百科

句柄(Handle)是一个是用来标识对象或者项目的标识符,可以用来描述窗体、文件等,值得注意的是句柄不能是常量 。
Windows之所以要设立句柄,根本上源于内存管理机制的问题,即虚拟地址。简而言之数据的地址需要变动,变动以后就需要有人来记录、管理变动,因此系统用句柄来记载数据地址的变更。在程序设计中,句柄是一种特殊的智能指针,当一个应用程序要引用其他系统(如数据库、操作系统)所管理的内存块或对象时,就要使用句柄 。

二级指针

指针是C语言中最高深莫测的部分了,能够直接操作内存的这些指针如果使用得当的话可以完成很多很高效的代码。而二级或者多级指针则能够达到你之前想都不敢想的效果。

  • 以下来自于百度百科

A(即B的地址)是指向指针的指针,称为二级指针,用于存放二级指针的变量称为二级指针变量.根据B的不同情况,二级指针又分为指向指针变量的指针和指向数组的指针。

提出需求

这次总结的内容则是将指针当做句柄放进函数当形参是动态地申请空间来用作其他用途的。

首先,我们要知道,我们如果直接把指针变量的地址当做函数的实参,去在函数里面申请空间,由于形参定义是一级指针,是无法拿到动态申请的空间的,以下就是一个实例

 1#include <stdio.h>
 2
 3static void get_space(int* p)
 4{
 5    p = (int *)malloc(sizeof(int));
 6    *p = 16;
 7}
 8
 9int main()
10{
11    int a = 10;
12    int* pa = &a;
13    printf("*pa=%d \t pa=%p \n", *pa, pa);
14    
15    get_space(&pa);
16    printf("*pa=%d \t pa=%p \n", *pa, pa);
17    get_space(pa);
18    printf("*pa=%d \t pa=%p \n", *pa, pa);
19    return 0;
20}

运行得到以下结果

pointer level2 1

显然,这个结果正和上面所说的,在main函数传进去的实参,无论是指针变量还是地址,在函数里面的操作并不能通过一级指针的形参返回动态申请的空间,这样做很容易出现野指针现象。

原因其实很简单,在形参定义的一级指针变量只是有从实参传递过来的地址而已,无论是指针变量内的地址还是指针变量本身的地址,只是存在形参一级指针变量当中,正如上面表现的,不管指针变量p的内容是pa或者pa的地址,只要使用p去获取空间的地址,便会使得指针p的内容从pa或者pa的地址变成新空间的地址,而在main函数中的pa并没有被影响到。

注意:上面的情况不要尝试用*p配合实参是pa的地址来接受新空间,会造成段错误,因为那样就是用一个int型的变量去接受一个int*型的值了

那么,如果我想通过指针去在函数中获取动态的空间要如何操作

解决方案

其实通过上面的注意事项已经有一点眉目了,要想使用*符号来提取地址内容的方法来获得修改主函数传递的指针变量的内容权限的原理就是使得*p能够代表一个地址。由此可见,指针的指针那就是二级指针了,表现在代码中就是将上述的形参定义为二级指针并使用主函数的一级指针变量去传递参数。

 1#include <stdio.h>
 2
 3static void get_space(int** p)
 4{
 5    *p = (int *)malloc(sizeof(int));
 6    int* temp = *p;
 7    *temp = 66;
 8}
 9
10int main()
11{
12    int a = 10;
13    int* pa = &a;
14    printf("*pa=%d \t pa=%p \n", *pa, pa);
15
16    get_space(&pa);
17    printf("*pa=%d \t pa=%p \n", *pa, pa);
18    return 0;
19}

运行得到以下结果:

pointer level2 2

由此便能通过一个指针在函数中去动态申请空间并返回上一层中了。

应用场景

光是看上面的例子并不能了解这种方法的强大之处,但是模仿上面所说的句柄的思想,来举例说说这个方案的好处吧。

首先假设,你负责一个项目的某个模块的编写,需要编写出测试app模块实现以及该模块所需的协议,并且为了减少各个方法与模块之间的耦合性以及代码的简洁效率等因素不允许大量使用全局变量,但是你要完成这个模块有需要很多的变量来存储一些参数以供该模块实现内的各个方法进行调用,这时候要怎么解决呢?

结构体是解决这种变量多的一种方案,但是如果在模块内使用全局变量的话你又如何管理这些空间呢(如果里面有较大的空间占用),只能等程序结束时清除吗

这时候使用一个指针变量来保存一个动态空间的地址来进行操作显然更方便,也就是使用一个void*型指针变量来存储地址,在模块初始化时申请空间,模块进行功能时将地址下放,用完后动态地清理该空间。而这个void*型的指针就是本次提出的句柄的模仿。

光是空口说出有点不太好说明该方案的可行性,下面用一个小例子来证明:

  • 假设下面的主函数函数和其他函数不在同一文件中
 1#include <stdio.h>
 2#include <stdlib.h>
 3
 4
 5typedef struct couple_num
 6{
 7    int a;
 8    char b;
 9} couple_num_t;
10
11void get_space(void** a)
12{
13    printf("*a addr:%p\n", *a);
14    *a = (couple_num_t *) malloc (sizeof(couple_num_t));
15    printf("%p\n", a);
16    couple_num_t *p = *a;
17    p->a = 1000;
18    p->b = 'r';
19}
20
21void show(void *a)
22{
23    printf("*a addr:%p\n", a);
24    couple_num_t* temp = a;
25    printf("show:\n%d\n%c\n",temp->a, temp->b);
26}
27
28void release(void** a)
29{
30    free(*a);
31}
32
33int main()
34{
35    void *p = NULL;
36
37    printf("origin p addr:%p\n", p);
38
39    get_space(&p);
40    printf("after change p addr:%p\n", p);
41
42    couple_num_t* temp = p;
43    printf("temp:%d\n",temp->a);
44
45    show(p);
46
47    release(&p);
48    printf("after release p addr:%p\n", p);
49    
50    show(p);
51
52    getchar();
53
54    return 0;
55}

运行结果如下:

pointer level2 3

可以看到,使用这种方法,不用在主函数暴露结构体内容,也能够将结构体的地址空间进行掌握,高效的利用了指针的特性完成功能,使得各个方法之间相互影响减少,参数传递也简单,并且在使用完成后还能动态清除空间。

后记

这种方法在C语言下设计功能模块时特别有用,模仿了面向对象的句柄思想,减少模块间的耦合(不使用全局变量,各方法就不会无意间相互干扰了)。


impressionyang

版权

评论