C语言中二级指针作为函数参数的用法与原理

Elena
Elena
发布时间:2026-10-15 | 4人浏览过
原创

在使用C语言进行函数编程时,经常遇到需要通过子函数为外部变量或指针申请内存并返回的情况。由于C语言的传值特性,使用一级指针作为参数无法改变主调函数中指针变量的地址值,此时必须引入二级指针作为函数参数,通过对指针的地址进行操作来实现内存的动态分配与状态同步。

为什么一级指针不能用于申请动态内存

许多初学者尝试直接使用一级指针作为函数参数来申请动态内存,这在逻辑上是一个常见的误区。当函数参数是指针时,函数内部对形参指针的赋值仅会改变局部副本的指向,不会影响调用者的原始指针。

以下代码展示了使用一级指针申请的错误尝试:

void getMemory(char *p, int num) {
    // 这里只是改变了局部变量 p 的值,str 仍然是 NULL
    p = (char *)malloc(sizeof(char)*num);
}

int main() {
    char *str = NULL;
    getMemory(str, 20); 
    strcpy(str, "code"); // 崩溃:因为 str 仍是 NULL,非法访问
    return 0;
}

在上述例子中,p 是 str 的一个副本,修改 p 并不会改变 str。因此,正确的做法是使用二级指针,将指针变量的地址传递给函数。

使用二级指针正确分配内存

通过将指针变量的地址传入,我们在函数内部可以通过解引用二级指针来修改一级指针的指向。

void getMemory1(char **p, int num) {
    // p 保存的是 str 的地址,*p 即修改 str 本身的值
    *p = (char *)malloc(sizeof(char)*num);
}

int main() {
    char *str = NULL;
    getMemory1(&str, 20); // 传入指针变量的地址
    if (str != NULL) {
        strcpy(str, "code"); 
        printf("%s\n", str); // 成功输出 code
        free(str);
    }
    return 0;
}

二级指针修改结构体指针实例

除了动态内存分配,二级指针也常用于通过函数初始化结构体指针。这要求函数不仅能修改结构体的内容,更要确保外部的指针变量指向这块合法的内存区域。

定义结构体与内存管理函数

首先定义一个包含姓名和年龄的结构体,并编写对应的初始化和释放函数。注意在分配内存后,必须将新分配的地址通过二级指针赋回给实参。

#include <iostream>
#include <stdlib.h>
using namespace std;

struct Teacher {
    char name[20];
    int age;
};

// 使用二级指针修改变量的值
int getTeacher(Teacher **p) {
    Teacher *tmp = NULL;
    if (p == NULL)
        return -1;
        
    tmp = (Teacher *)malloc(sizeof(Teacher));
    if (tmp == NULL)
        return -2;
        
    tmp->age = 23;
    strcpy(tmp->name, "Tom");
    
    // 关键步骤:将临时指针指向的新内存地址赋给 *p
    // *p 代表实参 Teacher* pt 本身
    *p = tmp; 
    return 0;
}

void FreeTeacher(Teacher *p) {
    if (p == NULL) return;
    free(p);
}

int main() {
    Teacher *pt = NULL;
    if (getTeacher(&pt) == 0) {
        cout << "Teacher Age: " << pt->age << endl;
        FreeTeacher(pt);
    }
    return 0;
}

二级指针的概念与图解分析

理解二级指针的核心在于区分“指针的值”和“指针变量的地址”。

  • 指针是什么? 指针是程序数据在内存中的地址。
  • 指针变量: 是用来保存这些地址的普通变量。

举例说明:

int c = 2, d = 3;
int *pc = &c;     // pc 是一级指针,其值为 c 的地址
int **ppc = &pc;  // ppc 是二级指针,其值为 pc 的地址

在这个过程中:
1. *pc 是对 c 地址值的解引用,取出 c 的值(即 2);
2. *ppc 是对 pc 地址值的解引用,得到的是一级指针 pc 本身(即 c 的地址);
3. **ppc 是两次解引用,最终得到 c 的值。

多级指针解引用流程图示

为了更直观地理解这一过程,可以参考以下变量在内存中的关系图示:

图中展示了从三级到二级的映射关系。对于二级指针 **ppc:

我们可以观察到以下规律:
- 一级指针 pc 存储变量 c 的地址。
- 二级指针 ppc 存储指针 pc 的地址。
- 这种链式关系使得我们可以通过最外层的指针,一路向内修改底层的值。

二级指针作为函数实参填写规则

当一个函数的形参是二级指针(例如 void fun(int **p1))时,调用该函数时实参应该填写什么?

根据上述原理,**p1 最终代表一个具体的变量值。要得到这个值,我们需要:
1. 对实参进行第一次解引用,得到一个地址(这个地址必须是另一个指针的地址)。
2. 对该地址再次解引用,得到目标变量。

因此,如果形参是 **p1,实参必须填入一个一级指针变量的地址(&p)。同理,如果形参是一级指针 *p2,实参则是变量地址(&a)。

实战示例对比:

// 普通变量相加
int sum = fun2(a, b);       // 实参为变量值

// 指针变量相关运算
int a = 2, d = 3;
int *pc = &a;
int sum = fun1(&pc, &d);   // fun1 第一个形参为 int**,故实参用 &pc

在此例中,fun1 接收 pc 的地址,内部对其解引用获取 pc 的值(即 a 的地址),再解引用一次获取 a 的值,从而完成计算。

完整测试代码与运行结果

结合前面的理论,编写一段完整的调试代码来验证所有关系:

#include <stdio.h>
int c = 2, d = 3;
int *pc = &c;
int **ppc = &pc;

int fun1(int **p1, int *p2) {
    return (**p1 + *p2);
} 

int main() {
    int sum = fun1(&pc, &d);
    printf("sum=%d\n", sum);
    
    // 输出各项地址与值关系验证
    printf("c的地址=%p\n", &c);
    printf("pc指向的地址=%p\n", pc);
    printf("pc的地址=%p\n", &pc);
    printf("**(ppc)的值=%d\n", **(ppc));
    
    return 0;
}

上述程序的运行日志清晰地揭示了内存布局:

程序流程梳理如下:

总结而言,凡是涉及到多级指针解引用的场景,请记住:每次解引用(*),操作的对象都是一个地址。通过层层递进的地址传递与解引用,即可在不返回值的情况下,通过函数参数灵活地操控内存指针。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表