C语言 子函数调malloc申请内存返回给主函数使用——可行,但要注意!——修改、完全篇

一般情况,子函数中动态申请内存,将地址返回给主函数,理论上应该也是可以的,需要子函数返回动态内存地址,主函数实参是相应的地址变量即可。需要注意的是,需要传入二级指针,也就是主函数实参是指向地址变量的指针。那么实参即指向子函数内动态申请的内存,调用完后,需要释放内存,即释放实参即可。

 参考文章:

C语言进阶---动态内存管理_c语言怎么遍历动态申请里面的内容-CSDN博客

对文章中一些例子做一些修改、调试,并说明:

一、直接传一级指针,即将子函数中的动态申请内存的指针(值,即地址)传出来:

这其实和子函数返回int型等变量到主函数的int型等变量一样,其实是返回不了的。即子函数调用完其返回值到主函数中就失效了。

对应原文:

......

4.几个经典的面试题

4.1 题目1:
void Getmemory(char* p)//形参是实参的一份临时拷贝 p是指针变量 p指向NULL *p是NULL
{
	p = (char*)malloc(100);//p由指向NULL 变为指向malloc开辟空间的起始地址
}
void Test(void)
{
	char* str = NULL;
	GetMemory(str);//传的是实参
	strcpy(str, "helllo world");
	printf(str);
}
//程序会崩溃无法打印

......

编译并不报错,但运行时,strcpy将字符串变量的指针复制到NULL型指针,以及打印NULL指针指向的值,都会报错。

二、改为传二级指针:

对应原文:

......

更正:通过传实参的地址来找到实参在内存当中的位置然后再改变实参的值:

void GetMemory(char** p)//char**p接收str的地址,是一个二级指针
{
	*p = (char*)malloc(100);  //*p==str 由指向NULL 变为指向malloc开辟空间的起始地址
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str);  //传str的地址
	strcpy(str, "helllo world");
	printf(str);
	free(str);
	str = NULL;
}
int main()
{
	Test();
	return 0;
}
//程序会正常打印"hello world"

......

三、下面其实还是在讲返回值不是地址的问题,和(一)中道理是一样的:

对应原文:

......

另一个传地址的例子:

4.2题目2:

返回栈空间问题:

char* GetMemory(void)
{
	char p [] = "hello world";//栈空间开辟数组
	return p;//p放在寄存器当中,这个函数可以成功返回数组名p,也就是数组的首元素地址

}
void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}
int main()
{
	Test();
	return 0;
}
//栈空间在用完之后会被系统回收,因此创建的数组p在GetMemory调用完之后就已经被销毁无法使用了
//str属于是野指针去访问了一块被销毁的空间,因此会打印一些随机值

改为:

int* test()
{
	int a = 10;
	return &a;//a的地址被存放在寄存器当中
}
int main()
{
	int* p = test();//p接收到a的地址
	printf("%d\n", *p);//p已经是野指针  打印10 因为调用test的栈空间还没有被覆盖
	printf("%d\n", *p);// 打印随机值 有一次调用test函数,栈空间被新随机值给覆盖
	return 0;
}

......

四、主函数调用带动态申请内存返回的子函数后需要释放动态内存:

对应原文:

......

另外,上面所说子函数动态申请内存给主函数用,主函数在使用完要释放。释放后最好置空,否则,其是个野指针,可能会导致错误。

4.3题目3:
void GetMemory(char** p, int num)
{
    assert(*p);
	*p = (char*)malloc(num);
}
void test(void)
{
	char* str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
	//free释放
	free(str);
	str = NULL;
}
int main()
{
	test();
	return 0;
}

......

五、修改上面二级指针做形参程序,会发现一些好玩的:

#include"stdio.h"
#include"stdlib.h"
#include"string.h"

#include "assert.h" 


void GetMemory(int** p)  // int** p 接收p的地址,是一个二级指针
{
	int *ptr = (int*)malloc(100);  // p的值,指向malloc开辟空间的起始地址
	assert(ptr);
	printf("ptr=%x\n", ptr);  // 开辟空间首地址,十六进制形式

	ptr[0] = 1;  // 直接将开辟空间第一个元素赋值1
	*p = ptr;
	*(*p+1) = 2;  // malloc开辟空间起始地址*p偏移1的值,赋为1,也就是开辟空间第二个单元赋值1
	*(*p+2) = 3;  // 第三个单元赋值3
}

int main(void)
{
	int* pp = NULL;
	printf("&pp=%d\n", &pp);
	GetMemory(&pp);  //传pp的地址

	printf("pp=%x\n",pp);  // pp值是地址,即开辟空间首地址
	printf("*pp=%d\n", *pp);  // 开辟空间首地址取值
	printf("pp[0]=%d\n", pp[0]);  // 开辟空间首地址
	printf("*(pp+1)=%d\n", *(pp+1));
	printf("&pp=%d\n", &pp);

	free(pp);
	pp = NULL;

	return 0;
}

看调试结果及打印结果——

在子函数“ptr[0] = 1;”语句打断点,即实时看动态申请内存的起始地址(监视窗口看ptr值):

                             图1

看输出窗口打印此地址,看十六进制

                            图2

可以看到问题——因为定义ptr是int**类型变量,也就是指向int型指针的int型地址,所以其值是int型,即4个字节,而实际上,动态申请内存的首地址,虽然也是指向int型,但它本身值不是int型,可以看到图1中ptr的值22f1299a2f0长度超过4个字节。这里定义int** p,实际上相当于强制令指向int变量的地址值转换为int型!其本来是什么型呢?看监视窗口即知:

是int*型......这和几种常见的数据类型不同啊,占几个字节呢...这暂时还真不知道了。

同时,图2中,代码打印出的ptr值,刚好就是ptr实际值的最低4个字节1229a2f0,这到底只是打印不出来,还是会影响赋值呢?因为后续会把ptr赋值给p指向的单元,即:

*p = ptr;

而p的类型也是int**,也就是,它是int型的值,是地址,此地址指向的单元还是存的地址,是int变量的地址,只不过在主函数中,存的这个地址是空的。调子函数后,改变的正是这个地址。

继续,将断点打在主函数释放动态内存语句,即free语句:

可以看出打印出的这个地址正是上面ptr打印出的地址。而看变量pp监视:

其实值并不只是打印的4个字节,而是和实际的ptr值一样!所以,强制令地址值为int型,只是影响打印,而不影响使用!

注意,上面截图中,是因为子函数调用完ptr值并未改变,所以监视窗口ptr值依然存在且不变,实际上,在监视窗口再次添加ptr变量,是不存在的:

这才符合子函数调用完其内部变量被销毁的原则。

另外,pp其实就完全等价于原子函数中动态申请内存的指针ptr,因为ptr调用完已经不存在,所以也可以说pp覆盖/代替了原ptr,是指向动态内存首地址的指针。所以,最终,释放内存,只需要free(pp)即可。避免野指针,注意将pp指向空地址。

最后,看&pp值,也就是pp指针的地址,自始至终是没变的,也就是这个指针是没变的,变的只是这个指针指向的值,即上问所述。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值