Skip to content

LV007-参数传递

一、参数传递

主要有三种传递参数的方式,分别是值传递,地址传递和全局变量。

1. 值传递

1.1 是什么?

值传递,其实就是进行复制传递。调用函数将实参传递给被调用函数,被调用函数将创建同类型的形参并用实参初始化。形参是新开辟的存储空间,因此,在函数中改变形参的值,不会影响到实参。

1.2 实例分析

c
#include <stdio.h>

void fun1(int, int);

int main(int argc, const char *argv[]) 
{
	int x = 3;
	int y = 6;

	printf("x = %d, &x = %p\n", x, &x);
	printf("y = %d, &y = %p\n", y, &y);

	fun1(x, y);

	printf("x = %d, &x = %p\n", x, &x);
	printf("y = %d, &y = %p\n", y, &y);
    return 0;
}

void fun1(int a, int b)
{
	printf("----- start fun1!-----\n");
	printf("a = %d, &a = %p\n", a, &a);
	printf("b = %d, &b = %p\n", b, &b);
	printf("----- change-----\n");
	int temp = 0;
	temp = a;
	a = b;
	b = temp;
	printf("a = %d, &a = %p\n", a, &a);
	printf("b = %d, &b = %p\n", b, &b);
	printf("-----end fun1!-----\n");
	return;
}

然后在终端运行以下命令:

shell
gcc test.c -Wall # 编译程序
./a.out          # 执行可执行程序

接着我们会在终端中看到以下输出信息:

shell
sumu@virtual-machine:~/hk/alpha$ gcc main.c -Wall
sumu@virtual-machine:~/hk/alpha$ ./a.out 
x = 3, &x = 0x7ffee0c3b950
y = 6, &y = 0x7ffee0c3b954
----- start fun1!-----
a = 3, &a = 0x7ffee0c3b91c
b = 6, &b = 0x7ffee0c3b918
----- change-----
a = 6, &a = 0x7ffee0c3b91c
b = 3, &b = 0x7ffee0c3b918
-----end fun1!-----
x = 3, &x = 0x7ffee0c3b950
y = 6, &y = 0x7ffee0c3b954

上述例子中实参与形参关系如下图所示:

image-20260228142510796

可以发现,实际参数 x 和 y 的值并没有受到任何影响。

2. 地址传递

2.1 是什么?

那我们想要在函数中修改传入参数的数据,就可以使用地址传递。

地址传递方式是按地址传递,实参为变量的地址,而形参为同类型的指针。被调用函数中对形参的操作,将直接改变实参的值(被调用函数对指针的目标操作,相当于对实参本身的操作)

2.2 实例分析

c
#include <stdio.h>

void fun1(int *, int *);

int main(int argc, const char *argv[]) 
{
	int x = 3;
	int y = 6;
	printf("x = %d, &x = %p\n", x, &x);
	printf("y = %d, &y = %p\n", y, &y);

	fun1(&x, &y);

	printf("x = %d, &x = %p\n", x, &x);
	printf("y = %d, &y = %p\n", y, &y);
    return 0;
}

void fun1(int *a, int *b)
{
	printf("----- start fun1!-----\n");
	printf("*a = %d, a = %p, &a = %p\n", *a, a, &a);
	printf("*b = %d, b = %p, &b = %p\n", *b, b, &b);
	printf("----- change-----\n");
	int temp = 0;
	temp = *a;
	*a = *b;
	*b = temp;
	printf("*a = %d, a = %p, &a = %p\n", *a, a, &a);
	printf("*b = %d, b = %p, &b = %p\n", *b, b, &b);
	printf("-----end fun1!-----\n");
	return;
}

然后在终端运行以下命令:

shell
gcc test.c -Wall # 编译程序
./a.out          # 执行可执行程序

接着我们会在终端中看到以下输出信息:

shell
sumu@virtual-machine:~/hk/alpha$ gcc main.c -Wall
sumu@virtual-machine:~/hk/alpha$ ./a.out 
x = 3, &x = 0x7ffc33791a40
y = 6, &y = 0x7ffc33791a44
----- start fun1!-----
*a = 3, a = 0x7ffc33791a40, &a = 0x7ffc33791a08
*b = 6, b = 0x7ffc33791a44, &b = 0x7ffc33791a00
----- change-----
*a = 6, a = 0x7ffc33791a40, &a = 0x7ffc33791a08
*b = 3, b = 0x7ffc33791a44, &b = 0x7ffc33791a00
-----end fun1!-----
x = 6, &x = 0x7ffc33791a40
y = 3, &y = 0x7ffc33791a44

上述例子中实参与形参关系如下图所示:

image-20260228143814354 可以发现,实际参数 x 和 y 的值也被交换了,在这种情况下,直接操作的是 x 和 y 所在地址的数据。

3. 全局变量传参

3.1 是什么?

前边我们知道,全局变量一旦定义,会在程序的任何地方可见,但是,全局变量的值可能会在任何一个函数中被修改,一经修改,就会影响其他所有使用它的地方,而且使用全局变量传递参数的先后顺序不同也可能会影响最终结果。所以 一般不建议使用这种传参方式

3.2 实例分析

c
#include <stdio.h>

int n = 3;
int fun1();

int main(int argc, const char *argv[]) 
{
	int sum = 0;
	sum = fun1();
	printf("sum = %d\n", sum);
    return 0;
}

int fun1()
{
	printf("----- start fun1!-----\n");
	int count = 0;
	int i = 0;
	for(i = 0; i <= n; i++)
	{
		count++;
	}
	printf("-----end fun1!-----\n");
	return count;
}

然后在终端运行以下命令:

shell
gcc test.c -Wall # 编译程序
./a.out          # 执行可执行程序

接着我们会在终端中看到以下输出信息:

shell
----- start fun1!-----
-----end fun1!-----
sum = 4

二、值作为地址

前面我们知道传参可以使用地址传递,那我们要是不传地址,直接传一个 a ,又会怎样呢?理论上来说,我们 传进去的是一个地址,地址无非就是一个很大的数罢了, a 的值不也是个数嘛,不也可以当做地址来用?我们来尝试一下:

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

void test(unsigned long vaddr)
{
    int *p = NULL;
	printf("%5s-->%2d--> &vaddr=%p, vaddr=%lx\n", __func__, __LINE__, &vaddr, vaddr);
	p = (int *)vaddr;
    printf("%5s-->%2d--> &p=%p, p=%p, *p=%d\n", __func__, __LINE__, &p, p, *p);
    *p = 5;
    printf("%5s-->%2d--> &p=%p, p=%p, *p=%d\n", __func__, __LINE__, &p, p, *p);
}

int main(int argc, char *argv[])
{
	int a = 3;
    unsigned long va = (unsigned long)&a;
	printf("%5s-->%2d--> &a=%p, a=%d, va=%lx, &va=%p, sizeof(va)=%ld\n", __func__, __LINE__, &a, a, va, &va, sizeof(va));
	test(va);
	printf("%5s-->%2d--> &a=%p, a=%d, va=%lx, &va=%p, sizeof(va)=%ld\n", __func__, __LINE__, &a, a, va, &va, sizeof(va));
	
	return 0;
}

我们可以得到以下输出信息:

shell
sumu@virtual-machine:~/hk/alpha$ gcc main.c -Wall
sumu@virtual-machine:~/hk/alpha$ ./a.out 
 main-->19--> &a=0x7ffefd1b6dcc, a=3, va=7ffefd1b6dcc, &va=0x7ffefd1b6dd0, sizeof(va)=8
 test--> 8--> &vaddr=0x7ffefd1b6d88, vaddr=7ffefd1b6dcc
 test-->10--> &p=0x7ffefd1b6d90, p=0x7ffefd1b6dcc, *p=3
 test-->12--> &p=0x7ffefd1b6d90, p=0x7ffefd1b6dcc, *p=5
 main-->21--> &a=0x7ffefd1b6dcc, a=5, va=7ffefd1b6dcc, &va=0x7ffefd1b6dd0, sizeof(va)=8

我们可以画个图:

image-20260228160150123

其实这里还是相当于下面的操作:

c
int *p;
p = a;
//或者就是
int *p = a;

这里用了一个中间变量来吧 a 的地址存起来,通过值传递的方式传递到函数内部,本质还是传递的 a 的地址。

Tips:我们可以提前获取到一个合法地址,通过这种方式传递到函数内部,这样可以通过值传递的方式直接操作这个合法地址的中的数据。

三、字符指针问题

上边是一个变量作为值传递的时候可以灵活使用,那一个字符指针传递的的时候会有什么不同之处吗,字符指针的指针变量可以指向一个字符串的首地址,指针名就可以代表这个字符串,我自己理解的就是,这个字符指针变量指向字符串的时候,既是一个指针,又代表了字符串的值,它在进行传递的时候会是怎样的呢?

1. 字符指针的使用

来看一个实例:

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

int main(int argc, char *argv[])
{
	char *str = "hello";
	printf("%5s-->%2d--> &str=%p\n", __func__, __LINE__, &str);
	printf("%5s-->%2d-->  str=%s\n", __func__, __LINE__, str);
	printf("%5s-->%2d--> *str=%c\n", __func__, __LINE__, *str);
	
	return 0;
}

经过编译,运行可执行程序,我们会的到下边的打印信息:

shell
 main--> 8--> &str=0x7ffc14a12830
 main--> 9-->  str=hello
 main-->10--> *str=h

对字符指针 str 进行 *str 操作的话取到的是整个字符串的首字符,而 str 就直接代表了整个字符串。

2. 字符指针名传给函数

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

void test(char *p)
{
	printf("%5s-->%2d--> &p=%p\n", __func__, __LINE__, &p);
	printf("%5s-->%2d-->  p=%p\n", __func__, __LINE__, p);
	printf("%5s-->%2d-->  p=%s\n", __func__, __LINE__, p);
	printf("%5s-->%2d--> *p=%c\n", __func__, __LINE__, *p);
	printf("%5s-->%2d-->  p[0]=%c\n", __func__, __LINE__, p[0]);
	printf("%5s-->%2d--> &p[0]=%p\n", __func__, __LINE__,  &p[0]);
}

int main(int argc, char *argv[])
{
	char *str = "hello"; // 这里指向字符串常量,字符串常量无法被修改
	printf("%5s-->%2d--> &str=%p\n", __func__, __LINE__, &str);
	printf("%5s-->%2d-->  str=%s\n", __func__, __LINE__, str);
	printf("%5s-->%2d-->  str=%p\n", __func__, __LINE__, str);
	printf("%5s-->%2d--> *str=%c\n", __func__, __LINE__, *str);
	printf("%5s-->%2d-->   str[0]=%c\n", __func__, __LINE__,  str[0]);
	printf("%5s-->%2d--> &str[0]=%p\n", __func__, __LINE__,  &str[0]);
	test(str);
	return 0;
}

这样进行编译运行后,我们会得到以下信息:

shell
 main-->18--> &str=0x7ffde7028a40
 main-->19-->  str=hello
 main-->20-->  str=0x400892
 main-->21--> *str=h
 main-->22-->   str[0]=h
 main-->23--> &str[0]=0x400892
 test--> 7--> &p=0x7ffde7028a18
 test--> 8-->  p=0x400892
 test--> 9-->  p=hello
 test-->10--> *p=h
 test-->11-->  p[0]=h
 test-->12--> &p[0]=0x400892

传参的时候就相当于:

c
char *p;
p = str;
// 或者就是
char *p = str;

关系就如下图所示:

image-20220918084341313

3. 字符指针取地址后传递

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

void test(char *p)
{
	printf("%5s-->%2d--> &p=%p\n", __func__, __LINE__, &p);
	printf("%5s-->%2d-->  p=%p\n", __func__, __LINE__, p);
	printf("%5s-->%2d-->  p=%s\n", __func__, __LINE__, p);
	printf("%5s-->%2d--> *p=%c\n", __func__, __LINE__, *p);
	printf("%5s-->%2d-->  p[0]=%c\n", __func__, __LINE__, p[0]);
	printf("%5s-->%2d--> &p[0]=%p\n", __func__, __LINE__,  &p[0]);
}

int main(int argc, char *argv[])
{
	char *str = "hello"; // 这里指向字符串常量,字符串常量无法被修改
	printf("%5s-->%2d--> &str=%p\n", __func__, __LINE__, &str);
	printf("%5s-->%2d-->  str=%s\n", __func__, __LINE__, str);
	printf("%5s-->%2d-->  str=%p\n", __func__, __LINE__, str);
	printf("%5s-->%2d--> *str=%c\n", __func__, __LINE__, *str);
	printf("%5s-->%2d-->   str[0]=%c\n", __func__, __LINE__,  str[0]);
	printf("%5s-->%2d--> &str[0]=%p\n", __func__, __LINE__,  &str[0]);
	test(&str);
	return 0;
}

我们编译运行后,会得到以下输出信息:

shell
 main-->18--> &str=0x7ffe24d193d0
 main-->19-->  str=hello
 main-->20-->  str=0x400892
 main-->21--> *str=h
 main-->22-->   str[0]=h
 main-->23--> &str[0]=0x400892
 test--> 7--> &p=0x7ffe24d193a8
 test--> 8-->  p=0x7ffe24d193d0
 test--> 9-->  p=@
 test-->10--> *p=�
 test-->11-->  p[0]=�
 test-->12--> &p[0]=0x7ffe24d193d0

传参的时候就相当于:

c
char *p;
p = &str;
// 或者
char *p = &str;

关系如下图:

image-20220918084915141

四、二级指针参数

这部分的实例会用到内存的申请和释放,可以看后面的笔记。

1. 什么是二级指针?

前面学习指针的时候已经学习过了。二级指针是指向指针的指针,也就是指针的地址。在 C 语言中,如果一级指针指向一个变量的地址,那么二级指针就指向这个一级指针的地址。

c
int a = 10;           // 普通变量
int *p = &a;          // 一级指针,指向变量 a 的地址
int **pp = &p;        // 二级指针,指向指针 p 的地址

二级指针的定义形式为:数据类型 **指针名;

2. 为什么需要二级指针参数?

C语言使用的是是值的传递,所以在函数参数传递中,直接传递一级指针只能修改指针所指向的内容,如果我们需要在函数内部 修改指针本身(而不是指针指向的内容),就需要使用二级指针作为参数。

2.1 一级指针参数的局限性

2.1.1 一个实例

当使用一级指针作为参数时,我们只能修改指针指向的内容,而不能修改指针本身的值:

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

void get_memory(char *p)
{
    printf("开辟内存前:");
	printf("&p=%p, p=%s, p=%p\n", &p, p, p);
	p = (char *)malloc(8);
	printf("开辟内存后:");
	printf("&p=%p, p=%s, p=%p\n", &p, p, p);
	strcpy(p, "sumu");
	printf("p = %s\n", p);
}

int main(int argc, char *argv[])
{
	char *pstr = NULL;
	printf("&pstr=%p, pstr=%s, pstr=%p\n", &pstr, pstr, pstr);
	get_memory(pstr);
	printf("pstr = %s\n", pstr);
	printf("%s\n", pstr);
	free(pstr);
	return 0;
}

编译运行后会看到:

shell
sumu@virtual-machine:~/hk/alpha$ gcc main.c -Wall
sumu@virtual-machine:~/hk/alpha$ ./a.out 
&pstr=0x7fffd1a47800, pstr=(null), pstr=(nil)
开辟内存前:&p=0x7fffd1a477d8, p=(null), p=(nil)
开辟内存后:&p=0x7fffd1a477d8, p=, p=0x563977710670
p = sumu
pstr = (null)
段错误 (核心已转储)

可以看到,虽然函数内部修改了指针 p 的值,但实参 pstr 并没有受到影响。

2.1.2 分析
  • 在 malloc 分配内存前

image-20260304125001011

这个时候创建了形参 p,并且 p 的值为实参 pstr 的值。形参 p 和实参 pstr 都为空,这里其实是因为传入的 pstr 的值为空才导致 p 也为空。

  • malloc 分配内存后

image-20260304125053894

p 指向了 malloc 申请的 8 个字节内存,但是这只是指针 p 的值被改变了。

  • 函数调用结束

image-20260304125331921

函数调用结束后,形参 p 被销毁,此时堆上的内存未释放,而且 pstr 的值没有受到任何影响。

2.2 二级指针参数的作用

怎么解决上面的问题?我们在函数中分配内存,目的是为了使用这个内存。其实有两种方案,一种是吧这个函数改成指针函数,返回一个指向申请内存的指针。另一种方式是使用二级指针作为参数,我们可以在函数内部修改指针本身。

2.2.1 修改实例
c
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

void get_memory(char **p)
{
    printf("开辟内存前:\n");
	printf("&p=%p,p=%p,*p=%p,*p=%s\n", &p, p, *p, *p);
	*p = (char *)malloc(8);
	printf("开辟内存后:\n");
	printf("&p=%p,p=%p,*p=%p,*p=%s\n", &p, p, *p, *p);
	strcpy(*p, "sumu");
}

int main(int argc, char *argv[])
{
	char *pstr = NULL;
	printf("&pstr=%p, pstr=%s, pstr=%p\n", &pstr, pstr, pstr);
	get_memory(&pstr);
	printf("pstr = %s\n", pstr);
	free(pstr);
	return 0;
}

编译运行后会看到:

shell
sumu@virtual-machine:~/hk/alpha$ gcc main.c -Wall
sumu@virtual-machine:~/hk/alpha$ ./a.out 
&pstr=0x7ffea4cf1f00, pstr=(null), pstr=(nil)
开辟内存前:
&p=0x7ffea4cf1ec8,p=0x7ffea4cf1f00,*p=(nil),*p=(null)
开辟内存后:
&p=0x7ffea4cf1ec8,p=0x7ffea4cf1f00,*p=0x55fad1817670,*p=
pstr = sumu

现在实参 pstr 被成功修改了!

2.2.2 分析
  • 调用函数后,分配内存前

image-20260304140743319

c
char *pstr = NULL;
get_memory(&pstr); // void get_memory(char **p)

//这样传入参数后相当于
// char **p = &pstr;
char **p = NULL;
p = &pstr;
  • 分配内存后

image-20260304143438389

分配内存的时候,由于p里面存放的是pstr的地址,所以*p就是pstr指针的值,将这个*p赋值为申请到的内存地址,就意味着让pstr指向了这个地址。

  • 函数调用结束

image-20260304143558108

函数调用结束后,指针p被销毁,但是pstr依然指向的是申请到的内存的地址。

3. 二级指针参数的应用场景

3.1 动态内存分配

这是二级指针最常见的应用场景:

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

void create_array(int **arr, int size)
{
    *arr = (int *)malloc(size * sizeof(int));
    if (*arr != NULL) {
        for (int i = 0; i < size; i++) {
            (*arr)[i] = i * 10;  // 初始化数组
        }
        printf("数组创建成功,大小:%d\n", size);
    }
}

void print_array(int *arr, int size)
{
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main(int argc, char *argv[])
{
    int *my_array = NULL;
    int size = 5;
    
    create_array(&my_array, size);
    
    if (my_array != NULL) {
        printf("数组内容:");
        print_array(my_array, size);
        free(my_array);
    }
    
    return 0;
}

编译运行后会看到:

shell
sumu@virtual-machine:~/hk/alpha$ gcc main.c -Wall
sumu@virtual-machine:~/hk/alpha$ ./a.out
数组创建成功,大小:5
数组内容:0 10 20 30 40

3.2 修改指针指向

c
#include <stdio.h>

void change_pointer(int **pp, int *new_ptr)
{
    *pp = new_ptr;  // 修改指针的指向
    printf("函数内:*pp = %p, **pp = %d\n", *pp, **pp);
}

int main(int argc, char *argv[])
{
    int a = 10, b = 20;
    int *ptr = &a;
    
    printf("修改前:ptr = %p, *ptr = %d\n", ptr, *ptr);
    
    change_pointer(&ptr, &b);
    
    printf("修改后:ptr = %p, *ptr = %d\n", ptr, *ptr);
    
    return 0;
}

编译运行后会看到:

shell
sumu@virtual-machine:~/hk/alpha$ gcc main.c -Wall
sumu@virtual-machine:~/hk/alpha$ ./a.out
修改前:ptr = 0x7ffc8d5b97b4, *ptr = 10
函数内:*pp = 0x7ffc8d5b97b0, **pp = 20
修改后:ptr = 0x7ffc8d5b97b0, *ptr = 20

3.3 二维数组的动态分配

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

void create_2d_array(int ***arr, int rows, int cols)
{
    *arr = (int **)malloc(rows * sizeof(int *));
    for (int i = 0; i < rows; i++) {
        (*arr)[i] = (int *)malloc(cols * sizeof(int));
        for (int j = 0; j < cols; j++) {
            (*arr)[i][j] = i * cols + j;
        }
    }
    printf("二维数组创建成功:%d x %d\n", rows, cols);
}

void print_2d_array(int **arr, int rows, int cols)
{
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%3d ", arr[i][j]);
        }
        printf("\n");
    }
}

void free_2d_array(int **arr, int rows)
{
    for (int i = 0; i < rows; i++) {
        free(arr[i]);
    }
    free(arr);
    printf("二维数组内存已释放\n");
}

int main(int argc, char *argv[])
{
    int **matrix = NULL;
    int rows = 3, cols = 4;
    
    create_2d_array(&matrix, rows, cols);
    
    if (matrix != NULL) {
        printf("矩阵内容:\n");
        print_2d_array(matrix, rows, cols);
        free_2d_array(matrix, rows);
    }
    
    return 0;
}

编译运行后会看到:

shell
sumu@virtual-machine:~/hk/alpha$ gcc main.c -Wall
sumu@virtual-machine:~/hk/alpha$ ./a.out
二维数组创建成功:3 x 4
矩阵内容:
  0   1   2   3
  4   5   6   7
  8   9  10  11
二维数组内存已释放

4. 二级指针参数与一级指针参数的对比

特性一级指针参数二级指针参数
参数类型int *int **
传递方式传递变量的地址传递指针的地址
能否修改指针指向的内容✓ 可以✓ 可以
能否修改指针本身✗ 不能✓ 可以
主要用途修改变量的值修改指针的指向、动态内存分配
调用方式func(&var)func(&ptr)

5. 常见错误与注意事项

5.1 混淆 * 和 &

c
#include <stdio.h>

void wrong_example(int **pp)
{
    int a = 100;
    *pp = &a;  // 错误!a 是局部变量,函数结束后会被销毁
}

void correct_example(int **pp)
{
    *pp = (int *)malloc(sizeof(int));  // 正确!动态分配的内存
    if (*pp != NULL) {
        **pp = 100;
    }
}

int main(int argc, char *argv[])
{
    int *ptr = NULL;
    
    // wrong_example(&ptr);  // 不要这样做!
    // printf("*ptr = %d\n ", *ptr);  // 未定义行为
    
    correct_example(&ptr);
    if (ptr != NULL) {
        printf("*ptr = %d\n", *ptr);
        free(ptr);
    }
    
    return 0;
}

5.2 内存泄漏

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

void potential_leak(int **pp)
{
    *pp = (int *)malloc(sizeof(int));
    **pp = 100;
    // 忘记释放内存!
}

void safe_usage(int **pp)
{
    if (*pp != NULL) {
        free(*pp);  // 先释放之前的内存
    }
    *pp = (int *)malloc(sizeof(int));
    if (*pp != NULL) {
        **pp = 100;
    }
}

int main(int argc, char *argv[])
{
    int *ptr = NULL;
    
    potential_leak(&ptr);
    printf("*ptr = %d\n", *ptr);
    free(ptr);  // 需要在调用者处释放
    
    safe_usage(&ptr);
    printf("*ptr = %d\n", *ptr);
    free(ptr);
    
    return 0;
}

6. 实际应用示例:字符串处理函数

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

// 创建字符串的副本
void string_duplicate(char **dest, const char *src)
{
    if (src == NULL) {
        *dest = NULL;
        return;
    }
    
    *dest = (char *)malloc(strlen(src) + 1);
    if (*dest != NULL) {
        strcpy(*dest, src);
        printf("字符串复制成功:%s\n", *dest);
    }
}

// 修改字符串内容
void modify_string(char **str, const char *new_content)
{
    if (*str != NULL) {
        free(*str);  // 释放旧内存
    }
    
    if (new_content != NULL) {
        *str = (char *)malloc(strlen(new_content) + 1);
        if (*str != NULL) {
            strcpy(*str, new_content);
        }
    } else {
        *str = NULL;
    }
}

int main(int argc, char *argv[])
{
    char *my_str = NULL;
    
    // 创建字符串
    string_duplicate(&my_str, "Hello, World!");
    printf("当前字符串:%s\n", my_str);
    
    // 修改字符串
    modify_string(&my_str, "Hello, C Language!");
    printf("修改后字符串:%s\n", my_str);
    
    // 清空字符串
    modify_string(&my_str, NULL);
    if (my_str == NULL) {
        printf("字符串已被清空\n");
    }
    
    return 0;
}

编译运行后会看到:

shell
sumu@virtual-machine:~/hk/alpha$ gcc main.c -Wall
sumu@virtual-machine:~/hk/alpha$ ./a.out
字符串复制成功:Hello, World!
当前字符串:Hello, World!
修改后字符串:Hello, C Language!
字符串已被清空