C语言 被调函数修改指针形参

我们都知道,如果想在函数中修改实参的值,那么,你传递给函数的应该是这个值的地址。如果你想修改的是个指针,那么是传递指针的地址(二级指针),还是传递该指针呢?

1. C语言中的函数调用

讲之前,先弄清楚一些概念:主调函数,被调函数,实参,形参。

void fun(int p)				// p 是形参,是在fun()内使用的形式参数
{
	p = 12}

void fun2(int *parray)				// p 是形参,是在fun()内使用的形式参数
{
	parray[3] = 5;
}

int main(void)
{
	int num = 0;
	int array[10] = { 0 };
	fun(num);				// main()是主调函数,主动调用fun()
							// fun()是被调函数,被main()调用
							// num 是实参,是main()传递给fun()的实际参数
	
	fun2(array);
		
	return 0;
}

C函数的所有参数均以“传值调用”的方式进行传递,这意味着函数将获得参数的一份拷贝,这也就是为什么很多初学者在刚学习函数的,企图修改形参值不成功的原因。(没错,就是我。。。)就是这样一个机制,保证了函数可以随心所欲的修改形参,而不会担心改变了形参的值。

如果实参是个数组名,并在被调函数内部使用间接运算符(*)或者是下标([ ])修改形参的数组元素,那么实际上修改的是实参的元素。被调函数将直接访问主调函数中所传递的数组,并不会对数组进行复制。这个行为叫做”传址调用”。

数组做实参的这种行为似乎和传值调用规则相悖。但并不是这样,首先,地址就是一串数字,只是标记了内存中的某个位置,而指针所存储的就是这串数字。本例中,假设数组array的地址是0x112233,由于数组名是个指针,传递给被调函数的是这个指针的一个拷贝,且根据传值调用,所以被调函数fun2() 的形参 parray 的值也是0x112233,而学过指针都知道,运用数组名的下标运算,可以改变数组中的某个值,所以在被调函数fun2()中,将数组的第四个元素修改了,而实参和形参指向的为同一块地址(均以0x112233开始,长度为10*sizeof(int)),所以数组array内的值就在被调函数中被修改了。

2. 被调函数修改指针所指向的值

让我们重新写一个小例子,添加打印,查看被调函数是如何修改实参的。

void fun(int *p)
{
	printf("p address:%p\n", p);
	*p = 12;
}

int main(void)
{
	int num = 2;
	printf("num address:%p\n", &num);
	fun(&num);

	return 0;
}

运行:
在这里插入图片描述
可以看到&num 和 p指向都是num的位置,所以形参p可以在被调函数中修改num的值。

这次换数组试试。

void fun(int *p)
{
	printf("p address:%p\n", p);
	p[3] = 12;
}

int main(void)
{
	int array[10] = { 0 };
	printf("array address:%p\n", array);
	fun(array);

	return 0;
}

运行:
在这里插入图片描述
指向地址相同,与上面同理。

3. 被调函数修改指针的值

之前的例子,都是修改指针所指向位置的值,这次我们来修改指针。

void fun(int *p)
{
	printf("p address:%p\n", p);
	p = (int *)malloc(sizeof(int));
	printf("p address:%p\n", p);
}

int main(void)
{
	int *ptmp = NULL;
	printf("ptmp address:%p\n", ptmp);
	fun(ptmp);

	printf("ptmp address:%p\n", ptmp);

	*ptmp = 15;


	free(ptmp);

	return 0;
}

插入断点,开始运行:
在这里插入图片描述

好,接着运行:
在这里插入图片描述
程序在这里挂掉了,分析一下。
根据之前的例子,我们知道被调函数传指针,形参和实参指向同一块内存。本例中,ptmp 初始值为NULL,也就是0x000000,所以刚传入fun()时,p也为NULL。接下来动态分配空间,p指向了一块不为NULL的地址。跳出函数,发现ptmp仍然为NULL,接着间接运算符对NULL进行运算,所以程序宕掉。

那为什么这次没有修改实参的值?其实文章开头给的几个例子,都是通过传递指针,来修改指针所指向空间,我们得到的结论是这种方法是可行的。那么来看这个例子,ptmp 指向NULL,fun()通过拷贝ptmp ,使p也指向NULL,随后malloc分配内存,p选择不指向NULL,转为指向新分配的内存(这个负心的p),虽然p在一顿操作,但是这一切都跟ptmp无关,你又没有改变ptmp的指向,所以ptmp依旧为NULL。
在这里插入图片描述
此外,由于p是局部变量,函数结束他的生命周期就结束了(活该!!!)唯一指向malloc分配内存的指针找不到了,导致没有显式的释放这块内存,造成内存泄漏。
所以得出结论:想在被调函数中修改一级指针的指向(包括malloc新内存或者free已有内存),传入一级指针是不可行的。

那么如何修改指针的值(指向)呢?简单,传二级指针就行了
由于是测试,就没写参数检测了,不过实战的时候参数检测是很重要的

void fun(int **p)
{
	printf("p address:%p\n", p);
	printf("*p address:%p\n\n", *p);
	*p = (int *)malloc(sizeof(int));
	printf("p address:%p\n", p);
	printf("*p address:%p\n\n", *p);
}

void funfree(int **p)
{
	printf("p address:%p\n", p);
	printf("*p address:%p\n\n", *p);
	free(*p);
	*p = NULL;
	printf("p address:%p\n", p);
	printf("*p address:%p\n\n", *p);
}

int main(void)
{
	int *ptmp = NULL;
	printf("ptmp address:%p\n", ptmp);
	printf("&ptmp address:%p\n\n", &ptmp);
	fun(&ptmp);
	printf("ptmp address:%p\n", ptmp);
	printf("&ptmp address:%p\n\n", &ptmp);

	funfree(&ptmp);

	return 0;
}

运行结果:
在这里插入图片描述
从结果上看,传递二级指针为实参,来修改一级指针的值是可行的。这个例子对于链表的创建和释放很有帮助,可以传递链表头的地址(二级指针)来进行创建和释放链表,就不需要再用返回值的方式。

4. 总结

一句话:
想在被调函数修改0级指针的值,实参传递1级指针,
想在被调函数修改1级指针的值,实参传递2级指针,
想在被调函数修改2级指针的值,实参传递3级指针,
想在被调函数修改3级指针的值,实参传递4级指针,

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值