| 上一篇 | 下一篇 |
|---|---|
| C 程序运行时内存布局的动态变化 |
目 录
- [指针函数 和 函数指针](#指针函数 和 函数指针)
-
- 1)指针函数
- 2)函数指针
-
- 2.1)声明函数指针的语法
- [2.2)更简洁的写法(使用 typedef)](#2.2)更简洁的写法(使用 typedef))
- 2.3)完整代码演示
指针函数 和 函数指针
参考讲解视频:【指针】指针和函数,指针函数和函数指针,悬挂指针,堆内存和栈内存,超清晰讲解。_哔哩哔哩_bilibili
功能介绍(为什么要用指针函数和函数指针?):
指针函数用于返回地址(如动态分配内存或字符串),突破值返回限制;
函数指针用于将函数作为参数传递,实现回调、动态调用和解耦逻辑,提升代码灵活性与复用性。
1)指针函数
函数的返回值是指针类型,主体是函数
若函数的返回值是指针类型,这个函数就叫指针函数。 需注意:❌ 指针函数的返回值不可以是栈区变量的地址(1.3 部分讲解)!!
1.1)声明指针函数的语法
c
返回类型 *函数名(参数列表)
{
/* 函数体 */
}
例如:
c
int *func_pointer()
{
static int a=10;
int *pa = &a;
return pa;
}
1.2)完整代码演示
以动态分配内存(使用 malloc 从堆区手动分配内存)为例
c
#include <stdio.h>
#include <stdlib.h>
int* func1()
{
int *pa=(int*)malloc(sizeof(int));
*pa = 10;
return pa;
}
int* func2()
{
int *pb=(int*)malloc(sizeof(int));
*pb = 22;
return pb;
}
int main(void) {
int *pfunc1 = func1();
printf("pfunc1 = %p, *pfunc1=%d\n", pfunc1,*pfunc1);
int *pfunc2 = func2();
printf("pfunc2 = %p, *pfunc2=%d\n", pfunc2,*pfunc2);
printf("pfunc1 = %p, *pfunc1=%d\n", pfunc1,*pfunc1);
free(pfunc1);
free(pfunc2);
return 0;
}
运行结果如下:
c
/* 第一次运行 */
pfunc1 = 00000210f0771450, *pfunc1=10
pfunc2 = 00000210f0771470, *pfunc2=22
pfunc1 = 00000210f0771450, *pfunc1=10
/* 第二次运行 */
pfunc1 = 00000210f0771450, *pfunc1=10
pfunc2 = 00000210f0771470, *pfunc2=22
pfunc1 = 00000210f0771450, *pfunc1=10
1.3)指针函数使用不当造成的悬空指针
在函数运行结束后,函数中定义的所有 栈区变量 ,都会被自动释放,除了 malloc 分配的、用 static 修饰的变量、全局变量除外。
所谓栈区变量是在函数内定义的 未经 static 修饰的局部变量和参数 ,存储在内存的栈(Stack)区。它们在函数调用时自动分配,函数返回时自动销毁,生命周期仅限于所在函数执行期间。当函数指针返回的是指向栈区变量地址的指针时,当这个函数被调用完,其有关的栈区内存就会被释放,指针也就变成了悬空指针。(有关栈区、堆区变量,可以看后续讲解)
错误代码示例:
c
#include <stdio.h>
#include <stdlib.h>
int* func1()
{
//int *pa=(int*)malloc(sizeof(int));
int *pa=NULL;
int a=10;
pa = &a;
return pa;
}
int* func2()
{
// int *pb=(int*)malloc(sizeof(int));
int *pb=NULL;
int b=10;
pb = &b;
return pb;
}
int main(void) {
int *pfunc1 = func1();
printf("pfunc1 = %p, *pfunc1=%d\n", pfunc1,*pfunc1);
int *pfunc2 = func2();
printf("pfunc2 = %p, *pfunc2=%d\n", pfunc2,*pfunc2);
printf("pfunc1 = %p, *pfunc1=%d\n", pfunc1,*pfunc1);
free(pfunc1);
free(pfunc2);
return 0;
}
c
/* 第一次运行 */
pfunc1 = 0000006c7dfffc74, *pfunc1=10
pfunc2 = 0000006c7dfffc74, *pfunc2=10
pfunc1 = 0000006c7dfffc74, *pfunc1=10
/* 第二次运行 */
pfunc1 = 000000ed531ffc94, *pfunc1=10
pfunc2 = 000000ed531ffc94, *pfunc2=10
pfunc1 = 000000ed531ffc94, *pfunc1=10
可以看到,不仅每次地址变化了,还由于函数调用完就被释放了,导致调用 func1 之后,func1 的变量所占内存都被释放了(虽然 a 所占内存被释放了,但 pfunc1 是 main() 的局部变量,仍指向 a 的地址,但已经变成悬空指针了),然后再调用 func2 的时候,程序从栈区正好又给 b 分到了以前 a 在的那个内存上(这个内存已经释放,可以被使用),然后再调用一次 func1 ,程序又给分配到以前那个内存上,最后就会发现程序运行混乱了。
2)函数指针
指向函数的指针变量,主体是指针
不论是变量、数组,还是函数,在内存中都会有一个对应的存储区域,函数在内存中存放的是它的可执行代码(机器指令) ,通常位于一个称为代码段或文本段的内存区域,那么它的地址就可以被指针指向。这个 指向函数的指针变量,就叫做函数指针 。
和数组名一样,函数的函数名有个特性:函数名本质上是一个指向其代码起始地址的常量指针:
c
/* 数组名是指向数组起始地址的常量指针 */
int array1[] = {0};
&array1 = array1;
/* 函数名是指向函数代码起始地址的常量指针 */
int func1()
{
int a = 10;
return a;
}
&func1 = func1;
2.1)声明函数指针的语法
c
返回类型 (*指针变量名)(参数类型列表);
返回类型 (*指针变量名)(参数类型列表) = NULL; // 一般初始化的时候,不知道赋值啥,就先赋值 NULL
(*指针变量名)这个括号不能省略,否则会变成"指针函数",()的优先级大于*,(*指针变量名)就是告诉编译器这是一个指针- 参数类型列表:是指向的函数的参数类型列表,多个形参就要写多个类型;
- 返回类型:是指向的函数的返回值类型。
例如:
c
int func2(int a)
{
int b = 11;
int c = a*b;
return c;
}
int (*pfunc)(int) = func2;
/* 也可以先声明,再赋值
int (*pfunc)(int) = NULL;
pfunc = func2; // pfunc = &func2;
*/
2.2)更简洁的写法(使用 typedef)
c
typedef 返回类型 (*指针变量名)(参数类型列表);
表示重定义了一个新类型名(类型名就叫指针变量名),代表指向函数的指针。
例如:
c
typedef int (*pfunc_type)(int, int);
pfunc_type padd = add;
printf("%d\n", padd(10, 5));
这样如果我们需要定义多个函数指针的时候,可以减少代码量。
2.3)完整代码演示
c
#include <stdio.h>
int func(int a) {
int b = 10;
int c = a + b;
return c;
}
int main(void) {
printf("------------------------------------------------------------------ 一般方法 ------------------------------------------------------------------\r\n");
int (*pfunc)(int) = NULL;
pfunc = func;
printf("func函数地址为:(pfunc)%p, (func)%p\n", pfunc,func);
int result1 = pfunc(10);
int result2 = func(10);
printf("result = (pfunc)%d, (func)%d\n", result1,result2);
printf("------------------------------------------------------------------ typedef ------------------------------------------------------------------\r\n");
typedef int(*pfunc_type)(int);
pfunc_type pfunc2 = func;
printf("func函数地址为:(pfunc)%p, (func)%p\n", pfunc2,func);
int result3 = pfunc2(20);
int result4 = func(20);
printf("result = (pfunc)%d, (func)%d\n", result3,result4);
return 0;
}
运行结果为:
c
------------------------------------------------------------------ 一般方法 ------------------------------------------------------------------
func函数地址为:(pfunc)00007ff7d1f61634, (func)00007ff7d1f61634
result = (pfunc)20, (func)20
------------------------------------------------------------------ typedef ------------------------------------------------------------------
func函数地址为:(pfunc)00007ff7d1f61634, (func)00007ff7d1f61634
result = (pfunc)30, (func)30