指针的减法运算和指针在函数间的传递

来源:互联网 发布:python adodb下载 编辑:程序博客网 时间:2024/05/22 11:40

昨晚在微信上和老友讨论c语言指针的相关问题,得到点收获。他和我一样,都是2016年6月份毕业的,从事的也都是嵌入式软件开发工作。下来,我尝试着将讨论的内容讲清楚。

说明,下面测试程序会用到两个宏: NUM和ERRP,它们的原型为:

#define ERRP(con, ret, ...) do  \    if (con){                   \    printf(__VA_ARGS__);        \    ret;                        \}while(0)#define NUM                     5

1. 指针的减法运算

int* intPoint = NULL;    void* voidPoint = NULL;    intPoint = (int* )malloc(sizeof(int) * NUM);    ERRP(NULL == intPoint, return -1, "intPoint: malloc memory failed!!\n");    voidPoint = malloc(sizeof(int) * NUM);    ERRP(NULL == voidPoint, return -1, "voidPoint: malloc memory failed!!\n");    printf("voidPoint = %p, voidPoint + sizeof(int) * 5 = %p\n", voidPoint, voidPoint + sizeof(int) * 5);    printf("(voidPoint + 5 * sizeof(int)) - voidPoint = %d\n", (voidPoint + 5 * sizeof(int)) - voidPoint);    printf("intPoint = %p, intPoint + sizeof(int) * 5 = %p\n", intPoint, intPoint + sizeof(int) * 5);    printf("(intPoint + 5 * sizeof(int)) - intPoint = %d\n", (intPoint + 5 * sizeof(int)) - intPoint);

运行结果:
这里写图片描述
首地址为voidPoint(0x8350020)的堆空间,其第5个元素的首地址为0x8350034,它们之间相差的地址偏移是20;

首地址为intPoint(0x8350008)的堆空间,其第5个元素的首地址为0x8350058,它们之间相差的地址偏移是80但是打印出来的地址偏移值却都是20;

也许你会认为上面的减法运算可以进行数据的结合律得到的确实是20,但是在括号内的值保存在一个指针变量,然后再减法运算,得到的结果也是如此。所以可以推论,指针的减法运算并非单纯数值上的相减,而是:

两个指针相减,其结果是两个指针之间元素的数目。前提是两个指针指向同一个数组,也就是指向空间连续的一段内存空间。

也就是说:
(1) voidPoint类型的指针,其最后一个元素的首地址减首元素的首地址得到的20表示有20个void型数据元素,因为void型是空类型,编译器默认它占据1字节;
(2) intPoint类型的指针,其最后一个元素的首地址减首元素的首地址得到的20表示有20个int型数据元素,因为int型占据4字节,所以地址实际上相差80字节

由此可见,若通过指针对动态分配的内存空间进行赋值,那么就需要考虑指针的类型了。
我们知道,对指针加减1实质是加减指针的步长:int型指针加减4字节,char/void型指针加减1字节,所以

for (i = 0; i < NUM; i++)        *(intPoint + i) = i * 6; //intPoint是int*型,对该指针加1等于加sizeof(i)    //voidPoint是void*型,对该指针加1等于加sizeof(void),即1    //void*型指针可以接受任意类型的指针,这里是接受int型指针,所以对这块内存赋值的时候需要乘以sizeof(int)    //这样指针才能sizeof(int)为单位进行偏移    for (i = 0; i < NUM; i++)        *(int* )(voidPoint + i  * sizeof(int) ) = i * 5; 

2. 指针在函数间的传递

指针在函数间的传递,在之前的文章变量在函数间的传递有详细讲解过,但都是文字的解析,现想通过画图的形式,理解起来会更简单。

还是这个程序:

int main(void){    int *main_point = NULL;    main_point = (int *)malloc(sizeof(int) * NUM);    ERRP(NULL == main_point, return -1, "Malloc Memory failed!!\n");    //...    //调用FreeMem()函数来释放堆空间,并将指向该堆空间的指针置为NULL    return 0;   }

FreeMem要实现的功能是释放堆空间和将指向该堆空间的指针置为NULL,那么FreeMem函数要什么原型?其实现体又为什么?

2.1 假设FreeMem函数的原型为FreeMem(int free_point)

FreeMem(int free_point)的实现体为:

int FreeMem(int* free_point){    free(free_point);    free_point = NULL;    return 0;}

main函数对FreeMem函数的调用是

FreeMem(main_point);

假设动态分配的堆的首地址为0x60003,那么
(1)指针main_point为:
main_point
(2)当将指针main_point传给free_point之后,free_point为:
fee_point
free_point和main_point都为动态空间的地址0x60003,所以执行free(free_point)或者free(main_point)都能起到一样的效果,
即断开与动态内存的链接。
(3)但是free_point = NULL 和main_point = NULL的效果则是不同的:
free_point = NULL和main_point = NULL的效果
一个是0x10012地址为NULL,一个是0x10036地址为NULL,所以free_point = NULL不能使得原本指向动态分配的内存的首地址的指针指向NULL。
因此FreeMem函数的原型不能是接收指针,而是要接收指针的地址,即原型为FreeMem(int** free_point)。

2.2 FreeMem(int** free_point)的实现方式1

int FreeMem(int** free_point) //free_point是一个二级指针,它存放main_point的地址{    free(*free_point);    *free_point = NULL;    return 0;}

main函数对FreeMem的调用是

FreeMem(&main_point);

(1) 当main函数将main_point的地址传给free_point时候,free_point的内容为:

free_point
free_point等于0x10012,即main_point的地址,那么*free_point得到的0x60003,,它是即动态内存的首地址,
因此free(*ree_point)是正确的;
(2) *free_point = NULL等价于main_point指向了NULL,所以这是正确的。

2.3 FreeMem(int** free_point)的实现方式2

int FreeMem(int** free_point){    int* my_point = *free_point;    free(my_point);    my_point = NULL;    return 0;}

同理,main函数对FreeMem的调用是

FreeMem(&main_point);

(1) 定义一个局部指针my_point,my_point的内容为:
my_point
即my_point为main_point,它是0x60003,为动态空间的首地址,free(my_point)的使用没错;

(2) my_point = NULL等价于
my_point
也就是并不等价于main_point指向了NULL,所以不能这么写。

综上:FreeMem函数的原型为

int FreeMem(int** free_point){    free(*free_point);    *free_point = NULL;    return 0;}//或者int FreeMem(int** free_point){    int* my_point = *free_point;    free(my_point);    *free_point = NULL;    return 0;}
0 0
原创粉丝点击