指针函数:返回指针的函数

指针函数,说白了就是返回值是一个指针的函数。嗯,这名字听着挺绕,但理解起来其实不难——普通函数返回 int、返回 char,指针函数返回的是地址。

我刚开始学的时候,总觉得这玩意儿跟函数指针是一回事。后来踩了坑才明白:指针函数是函数,函数指针是指针,完全两个概念。今天咱们只聊指针函数。

指针函数的定义与声明

先看个最简单的例子:

int *getMax(int *a, int *b) {
    return (*a > *b) ? a : b;
}

注意看返回类型——int *,这就是指针函数。它返回的是一个指向整型数据的地址。

我个人习惯把星号紧挨着函数名写,这样读起来更顺:「getMax 是一个函数,它返回 int 指针」。当然你也可以写成 int* getMax(...),风格问题,不影响编译。

什么时候需要指针函数?

我在项目中遇到过好几次这样的场景:你需要从一个函数里返回一大块数据。比如读取配置文件,返回一个字符串数组;或者解析网络包,返回一个结构体指针。

这时候如果返回值本身,就得复制整个数据块,效率低得吓人。返回指针就优雅多了——只传地址,不传数据。

举个实际例子:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *duplicateString(const char *src) {
    if (src == NULL) return NULL;
    
    char *copy = (char *)malloc(strlen(src) + 1);
    if (copy != NULL) {
        strcpy(copy, src);
    }
    return copy;
}

int main() {
    char *result = duplicateString("Hello, Pointer!");
    if (result != NULL) {
        printf("复制结果: %s\n", result);
        free(result);  // 别忘了释放!
    }
    return 0;
}

这个 duplicateString 就是典型的指针函数。它从堆上分配内存,把字符串复制进去,然后返回这块内存的地址。

核心注意事项:不要返回局部变量的地址

⚠️ 这是新手最容易犯的错误,没有之一。

看这段代码:

int *badFunction() {
    int localVar = 42;
    return &localVar;  // 危险!
}

为什么会这样?localVar 是局部变量,存在栈上。函数一结束,栈帧就被回收了。你返回的那个地址,指向的是一块「已经还给系统」的内存。

我曾经在一个嵌入式项目里犯过这个错。当时写了一个传感器数据读取函数,返回了局部数组的地址。程序跑起来偶尔正常,偶尔出乱码,查了两天才定位到问题。嗯,从那以后我养成了一个习惯:每次写指针函数,先问自己一句「这个指针指向的内存,函数退出后还在不在?」

那应该返回什么?

安全的做法有三种:

方式 说明 示例
返回堆内存 用 malloc/calloc 分配,调用者负责 free 上面的 duplicateString
返回静态变量地址 用 static 修饰,生命周期贯穿整个程序 static int arr[10]; return arr;
返回传入的指针 函数参数传进来的地址,直接返回 上面的 getMax

我个人最常用的是第一种——堆内存。灵活,可控。但代价是调用者必须记得 free,否则就内存泄漏了。

💡 小技巧: 如果你写了一个返回堆内存的函数,最好在注释里明确写清楚「调用者需要释放返回值」。团队协作时,这能省去很多扯皮。

指针函数与 NULL 检查

指针函数返回的是地址,那就可能返回 NULL。调用方拿到返回值后,一定要做空指针判断。

我见过太多 crash 是因为没检查 NULL。你想想看,函数内部 malloc 失败了,返回 NULL;调用方直接拿这个 NULL 指针去解引用——程序当场崩掉。

char *result = duplicateString(NULL);
// 如果函数内部没处理 NULL 输入,这里就可能出问题
// 即使函数处理了,也可能返回 NULL(比如 malloc 失败)
if (result != NULL) {
    printf("%s\n", result);
    free(result);
} else {
    printf("复制失败\n");
}

指针函数与 const 搭配

有时候你希望返回的指针指向的内容不能被修改。这时候可以加 const:

const char *getErrorMessage(int errorCode) {
    static const char *messages[] = {
        "成功",
        "参数错误",
        "内存不足",
        "未知错误"
    };
    
    if (errorCode >= 0 && errorCode < 3) {
        return messages[errorCode];
    }
    return messages[3];
}

这个函数返回的是 const char *,调用方只能读,不能写。我在写固件库时经常用这种模式——把错误码映射表封装起来,外部只能查,不能改。

知识体系总览

下面这张图把指针函数的核心知识点串起来了:

指针函数 定义 返回值是指针的函数 用途 返回大块数据/动态内存 ⚠️ 注意事项 不要返回局部变量地址 安全返回方式 ① 堆内存 (malloc) ② 静态变量地址 ③ 返回传入的指针 调用规范 必须做 NULL 检查 堆内存记得 free 注意 const 修饰 核心原则:指针有效,内存可控

避坑指南

  • 不要返回局部数组——数组名就是地址,但局部数组在函数退出后就失效了。
  • 不要返回局部结构体变量的地址——道理同上,栈上分配的东西,函数结束就没了。
  • 多线程环境下慎用静态变量——静态变量是全局一份的,多个线程同时调用会有竞争。
  • 返回堆内存时,文档里要写清楚谁负责释放——我见过一个项目,三个人写了三个不同的内存管理约定,最后谁也搞不清该不该 free。

指针函数本身不复杂,复杂的是它背后的内存管理。你只要记住一条铁律:函数返回的指针,必须指向函数退出后仍然有效的内存。做到这一点,指针函数就是你手里的利器。


公众号:蓝海资料掘金营,微信deep3321