单片机/C语言八股:(八)指针函数 和 函数指针

上一篇 下一篇
C 程序运行时内存布局的动态变化

目 录


指针函数 和 函数指针

参考讲解视频:【指针】指针和函数,指针函数和函数指针,悬挂指针,堆内存和栈内存,超清晰讲解。_哔哩哔哩_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

相关推荐
CHANG_THE_WORLD2 小时前
glog3 捕获Windows异常崩溃信号
windows·stm32·单片机
易水寒陈2 小时前
单片机的命令模式
单片机·命令模式
集芯微电科技有限公司2 小时前
700V/1.6A单通道GaN FET增强型驱动器具有零反向恢复损耗
人工智能·单片机·嵌入式硬件·深度学习·神经网络·机器学习·生成对抗网络
承前智3 小时前
Arduino1.8.19与stm32+ESP32的geek卸载及环境安装
stm32·单片机·嵌入式硬件
jimy13 小时前
字节流(XML、JSON、文件、网络、图像、加密…)必须用无符号语义unsigned char
xml·c语言·网络·json
全栈游侠3 小时前
STM32F103XX 05-时钟配置分析与自举程序
stm32·单片机·嵌入式硬件
8Qi83 小时前
LeetCode61. 旋转链表
c语言·数据结构·c++·算法·leetcode·链表·力扣
学嵌入式的小杨同学3 小时前
STM32 入门封神之路(四):GPIO 实战 + 寄存器深度拆解 ——LED 控制 + 按键检测全流程(含位操作 + 面试题)
stm32·单片机·嵌入式硬件·硬件架构·硬件工程·智能硬件·嵌入式实时数据库
撩妹小狗3 小时前
定时器PWM输出功能的使用
单片机·嵌入式硬件