函数间传递动态内存,C语言函数间动态内存的传递详解
跨函数使用动态内存很重要。所谓“跨函数使用动态内存”就是指“如何在主调函数中使用被调函数中动态分配的内存”。前面章节我们介绍了指针,其目的一是为了讲“动态内存分配”。第二个目的就是为了讲“跨函数使用动态内存”。
下面来写一个程序:
*p = 5
程序说明:
1) “int*p;”表示定义了一个 int* 型的指针变量 p,它只能指向 int 型变量,里面只能存放 int 型变量的地址,但此时它里面还没有内容,也就是说还没有初始化。那么 p 是什么时候被初始化的?当调用完 DynamicArray 函数后,DynamicArray 函数构建了一个动态的内存空间,且 p 指向了这个内存空间,此时 p 才被初始化。
2) p 虽然是指针变量,但指针变量也是变量,只要是变量,在程序执行时系统就会为其分配内存单元,所以 p 也有自己的地址。系统为 p 分配内存单元是自动的,而给 p 初始化却是程序员的事。所以不要把“p 的地址”和“p 里面存放的别的变量的地址”给弄糊涂了。
3) 函数调用时为什么传递的是 &p 而不是 p?我们可以试一试把实参改成 p,看看会怎么样:
*p = 2
首先,实参改成了 p,那么形参就不能再写 int**q 了。因为 p 是 int* 型,所以 q 也必须是 int* 型。同理,“*q=malloc(sizeof*q);”中赋值号左边的 q 前面的 * 也要掉。“**q=5;”也要改成“*q=5;”。
此时指针变量 p 指向变量 i,那么指针变量 q 也指向变量 i,即 q 中存放i的地址。但随后构建了一个动态内存,且 q 指向这个内存。q 中原本存放的i的地址被新的地址取代了,此时 q 不再指向 i,所以 *q 中的值的改变不会影响 i 的值。所以程序最后执行的结果 *p 还是等于 2。
这个实际上同前面讲的普通变量的传递是一样的。对于普通变量,如果想在被调函数中直接修改主调函数中变量的值,那么就必须要传递该变量的地址。对于指针变量也是一样的,如果想在被调函数中修改主调函数中定义的指针变量的指向,那么也要传递该指针变量的地址。因为修改指针变量的指向就是修改指针变量的值。
4) 为什么形参是 int**q,而不是 int*q?同样可以用两种方式理解:
5) 从该程序中也可以看出,动态分配的内存在函数调用结束后并没有被释放。通过输出结果可以看出,它里面存放的仍然是 5。这就是动态内存分配。
多级指针就是比较“绕”,难度其实不是很大。到底哪个存放的是哪个的地址,这个如果你一开始想不清楚的话可以用笔在纸上画一下。
最后需要讲的是,“跨函数使用动态内存”也可以不用多级指针,即直接返回被调函数中指向动态内存的指针变量,然后赋给主调函数中的指针变量就行了。下面把函数写下来。
*p = 5
这种方式相比前面使用多级指针的方式更好理解。而且在实际编程中,多是使用这种方式。但是需要注意的是,只有动态分配的内存空间的地址才能返回。这个程序中指针变量 q 所指向的内存空间是用 malloc 定义的,即动态分配的。所以函数调用结束后这段内存空间也不会被释放,因此返回它的地址才有意义。若 q 所指向的内存空间不是动态分配,而是在栈中静态分配的,那么就不能返回它的地址。因为函数调用结束后这段内存空间已经被释放了,不能使用,所以返回去也没有意义。
因此,在 C 语言中,在讲动态内存分配之前经常有一句话,叫作“永远不要返回局部变量的地址”。
下面来写一个程序:
# include <stdio.h> # include <stdlib.h> void DynamicArray(int **q); //函数声明 int main(void) { int *p = NULL; DynamicArray(&p); //函数调用 printf("*p = %d\n", *p); return 0; } void DynamicArray(int **q) //DynamicArray是“动态数组的意思” { *q = malloc(sizeof*q); **q = 5; return; }输出结果是:
*p = 5
程序说明:
1) “int*p;”表示定义了一个 int* 型的指针变量 p,它只能指向 int 型变量,里面只能存放 int 型变量的地址,但此时它里面还没有内容,也就是说还没有初始化。那么 p 是什么时候被初始化的?当调用完 DynamicArray 函数后,DynamicArray 函数构建了一个动态的内存空间,且 p 指向了这个内存空间,此时 p 才被初始化。
2) p 虽然是指针变量,但指针变量也是变量,只要是变量,在程序执行时系统就会为其分配内存单元,所以 p 也有自己的地址。系统为 p 分配内存单元是自动的,而给 p 初始化却是程序员的事。所以不要把“p 的地址”和“p 里面存放的别的变量的地址”给弄糊涂了。
3) 函数调用时为什么传递的是 &p 而不是 p?我们可以试一试把实参改成 p,看看会怎么样:
# include <stdio.h> # include <stdlib.h> void DynamicArray(int *q); //函数声明 int main(void) { int i = 2; int *p = &i; DynamicArray(p); //函数调用 printf("*p = %d\n", *p); return 0; } void DynamicArray(int *q) { q = malloc(sizeof*q); *q = 5; return; }输出结果是:
*p = 2
首先,实参改成了 p,那么形参就不能再写 int**q 了。因为 p 是 int* 型,所以 q 也必须是 int* 型。同理,“*q=malloc(sizeof*q);”中赋值号左边的 q 前面的 * 也要掉。“**q=5;”也要改成“*q=5;”。
此时指针变量 p 指向变量 i,那么指针变量 q 也指向变量 i,即 q 中存放i的地址。但随后构建了一个动态内存,且 q 指向这个内存。q 中原本存放的i的地址被新的地址取代了,此时 q 不再指向 i,所以 *q 中的值的改变不会影响 i 的值。所以程序最后执行的结果 *p 还是等于 2。
这个实际上同前面讲的普通变量的传递是一样的。对于普通变量,如果想在被调函数中直接修改主调函数中变量的值,那么就必须要传递该变量的地址。对于指针变量也是一样的,如果想在被调函数中修改主调函数中定义的指针变量的指向,那么也要传递该指针变量的地址。因为修改指针变量的指向就是修改指针变量的值。
4) 为什么形参是 int**q,而不是 int*q?同样可以用两种方式理解:
- 因为实参传递的是“指针变量的地址”,而指针变量的地址是指针的指针,基类型为int*型,所以形参的基类型也必须是int*型才能进行传递。
- 因为指针变量p的类型是int*型,所以&p的类型为int**型,所以形参也必须是int**型才能进行传递。
5) 从该程序中也可以看出,动态分配的内存在函数调用结束后并没有被释放。通过输出结果可以看出,它里面存放的仍然是 5。这就是动态内存分配。
多级指针就是比较“绕”,难度其实不是很大。到底哪个存放的是哪个的地址,这个如果你一开始想不清楚的话可以用笔在纸上画一下。
最后需要讲的是,“跨函数使用动态内存”也可以不用多级指针,即直接返回被调函数中指向动态内存的指针变量,然后赋给主调函数中的指针变量就行了。下面把函数写下来。
# include <stdio.h> # include <stdlib.h> int * DynamicArray(void) ; //函数声明 int main(void) { int *p = DynamicArray(); //函数调用 printf("*p = %d\n", *p); return 0; } int * DynamicArray(void) //DynamicArray是“动态数组的意思” { int *q = malloc(sizeof*q); *q = 5; return q; }输出结果是:
*p = 5
这种方式相比前面使用多级指针的方式更好理解。而且在实际编程中,多是使用这种方式。但是需要注意的是,只有动态分配的内存空间的地址才能返回。这个程序中指针变量 q 所指向的内存空间是用 malloc 定义的,即动态分配的。所以函数调用结束后这段内存空间也不会被释放,因此返回它的地址才有意义。若 q 所指向的内存空间不是动态分配,而是在栈中静态分配的,那么就不能返回它的地址。因为函数调用结束后这段内存空间已经被释放了,不能使用,所以返回去也没有意义。
因此,在 C 语言中,在讲动态内存分配之前经常有一句话,叫作“永远不要返回局部变量的地址”。
所有教程
- C语言入门
- C语言编译器
- C语言项目案例
- 数据结构
- C++
- STL
- C++11
- socket
- GCC
- GDB
- Makefile
- OpenCV
- Qt教程
- Unity 3D
- UE4
- 游戏引擎
- Python
- Python并发编程
- TensorFlow
- Django
- NumPy
- Linux
- Shell
- Java教程
- 设计模式
- Java Swing
- Servlet
- JSP教程
- Struts2
- Maven
- Spring
- Spring MVC
- Spring Boot
- Spring Cloud
- Hibernate
- Mybatis
- MySQL教程
- MySQL函数
- NoSQL
- Redis
- MongoDB
- HBase
- Go语言
- C#
- MATLAB
- JavaScript
- Bootstrap
- HTML
- CSS教程
- PHP
- 汇编语言
- TCP/IP
- vi命令
- Android教程
- 区块链
- Docker
- 大数据
- 云计算