单片机/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

相关推荐
计算机安禾18 小时前
【数据结构与算法】第18篇:数组的压缩存储:对称矩阵、三角矩阵与稀疏矩阵
c语言·开发语言·数据结构·c++·线性代数·算法·矩阵
maverick_11119 小时前
【FPGA】关于两个数相加的“坑”
c语言·matlab·fpga开发
计算机安禾19 小时前
【数据结构与算法】第17篇:串(String)的高级模式匹配:KMP算法
c语言·数据结构·学习·算法·visual studio code·visual studio·myeclipse
水饺编程19 小时前
第4章,[标签 Win32] :SysMets3 程序讲解02,iVertPos
c语言·c++·windows·visual studio
恒森宇电子有限公司20 小时前
芯晞微CSM057 线性充电管理芯片 封装SOT23-6
单片机·嵌入式硬件
code_whiter20 小时前
C\C++5(内存管理)
c语言·c++
蓝凌y21 小时前
51单片机之LCD1602
单片机·嵌入式硬件·51单片机
HABuo21 小时前
【linux线程(二)】线程互斥、线程同步、条件变量详细剖析
linux·运维·服务器·c语言·c++·ubuntu·centos
Rabitebla21 小时前
归并排序(MergeSort)完全指南 —— 从原理到非递归实现
c语言·数据结构·c++·算法·排序算法
寒秋花开曾相惜21 小时前
(学习笔记)3.9 异质的数据结构(3.9.1 结构)
c语言·网络·数据结构·数据库·笔记·学习