栏目分类:
子分类:
返回
文库吧用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
文库吧 > IT > 软件开发 > 后端开发 > C/C++/C#

C语言笔记——内存管理

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C语言笔记——内存管理

1、内存的分区

2、GetMemory的笔试题(说明代码中的问题及修正) TEST1
#include 
void GetMemory1(char* p)
{
	p = (char*)malloc(100);
}
void Test1(void)
{
	char* str = NULL;
	GetMemory1(str);
	strcpy(str, "helloworld");
	printf(str);
}
int main()
 {
	Test1();
	return 0;
}

1、代码中最为严重的两个问题:

首先,就是由于strcpy()的源码中会访问传入函数中的指针所代表的值,因此这就会出现访问空指针(Nullstr)的情况,在C语言中这是不被允许的;

其次,使用malloc()在堆区申请了空间后没有使用free()释放空间;

最后,还存在的小问题就是,我们需要在malloc后判断下是否成功申请,可以判断下返回值是否为NULL或者使用assert();

2、我们可以从内存的角度分析下代码运行的流程:

首先,在主函数中,我们进入了Test1()函数,进入Test1函数后,我们定义了一个局部变量char型指针str,并且将其赋值为NULL,此时str存储在栈区中,并将其传入到GetMemory()中

接着,我们进入到了GetMemory()函数中,此时我们的函数形参p为str的值NULL,首先我们在堆区申请了一块空间,并且我们将其地址传入到了局部变量也是函数形参的char型指针p中,此时函数结束了,由于p是局部变量存储在栈区中,故p中所存储的值已经被释放掉了,也就是说此时我们已经没有任何变量来存储我们刚刚在栈区所申请的空间的地址;

最后,我们回到了Test1()中执行strcpy()函数进行数据拷贝,但此时我们的str依旧为NULL,我们来观察下strcpy的源码

char * strcpy(char * dst, const char * src)
{
        char * cp = dst;
        while((*cp++ = *src++) != '');
        return( dst );
}

我们传入的第一个形参并未被const修饰,也就是说在这个函数看来它本来是可以进行访问操作的的,但是我们此时传入的参数却是NULL,故会出现异常

3、如果想要修改这段代码我们可以采用以下两种方法:

方法一:

无论如何,如何我们想要修改这段代码的核心都在于建立起str和p之间的联系,最简单的方法就是直接让p在接受到我们在堆区申请的空间地址后,将其返回到str中,修改后的代码为:

#include 
#include 
#include 
char* GetMemory1(char* p){
	p = (char*)malloc(100);
    assert(p!= NULL);
	return p;
}
void Test1(void)
{
	char* str = NULL;
	str = GetMemory1(str);
	strcpy(str, "hello world");
	printf(str);
	free(str);
}
int main() {
	Test1();
	return 0;
}

此时,我们将malloc()申请到的空间的地址传入到了str中,这样的话我们就可以利用strcpy()把常量区的字符串常量“hello world”拷贝到地址为str的堆区空间中,最终我们将其释放即可;

方法二:

在错误的代码中,我们是将str这一栈区变量中的值传入到了变量p中,因此我们才没有建立其str和p之间的联系,因此,但如果我们可以将str这一变量本身的地址(&str)传入到函数中,并把malloc()申请的堆区空间的地址,通过查找str的地址的方式传入进去就可以了,但由于str本身就是一个指针变量,因此我们如果想要GetMemory()可以接受指针变量的地址需要使用二级指针作为函数形参,代码如下:

#include 
#include 
void GetMemory1(char** p){
	*p = (char*)malloc(100);
}
void Test1(void)
{
	char* str = NULL;
	GetMemory1(&str);
	strcpy(str, "hello world");
	printf(str);
	free(str);
}
int main() {
	Test1();
	return 0;
}

由于函数的形参p是一个二级指针,其用于接收指针变量的地址,可以理解为存储变量地址的地址,故我们如果给p传入了&str即:p=&str,因此,如果我们对p取*运算则得到的是str这一指针变量,则我们成功的让str接收到了malloc()出来的堆区地址。

TEST2
#include 
#include 
char* GetMemory2(void){
	char p[] = "hello world";
	return p;
}
void Test2(void)
{
	char* str = NULL;
	str = GetMemory2(str);
	printf(str);
}
int main() {
	Test2();
	return 0;
}

对于这段代码,我们运行后的结果如下:

我们会发现,字符串在读取时出现了问题,我们依旧还是从内存分区的角度来读一遍这段代码:

首先,在TEST2()中定义了一个char型指针变量str储存在栈区中并赋值为NULL,将其传入到了GetMemory2()中,在这里我们定义了一个字符串数组p并令其为"hello world"。

此时我们要清楚的是:等号右侧的“hello world”存储在常量区,而p所代表的字符串数组储存在栈区里,p所代表的是这一栈区空间的地址,但是当我们return后这一空间中的值就被释放掉了,尽管我们返回了这一空间的地址,但是该地址所对应的空间中并没有值,故我们想要printf(str)必然会出现读取错误的情况,因此我们如果想要修改这段代码也非常简单:

由于常量区是不会被释放的,而原本p是指向栈区地址的指针变量,因此我们将p改为指向常量区中的字符串常量的指针就可以了,代码如下:

#include 
#include 
char* GetMemory2(void){
	char *p = "hello world";
	return p;
}
void Test2(void)
{
	
	char* str = NULL;
	str = GetMemory2(str);
	printf(str);
}
int main() {
	Test2();
	return 0;
}
TEST3
#include 
char* GetMemory(void) 
{
	char* p = "helloworld";
	return p;
}
void Test3(void) 
{
	char* str = NULL;
	str = GetMemory3();
	printf(str);
}
int main()
{
	Test3();
	return 0;
}

TEST3的写法和我们刚刚修改过的TEST2是一样的,因此这段代码是没有问题的

TEST4
#include 
char* GetMemory4(void)
{
	static char p[] = "hello world";
	return p;
}
void Test4(void) 
{
	char* str = NULL;
	str = GetMemory4();
	printf(str);
}
int main()
{
	Test4();
	return 0;
}

首先,我们来回顾下静态变量的描述,静态变量被定义后存在于静态区,静态区和全局区的变量生命周期都是整个程序的开始一直到结束,而静态局部变量的作用域是从定义一直到函数结束,也就是到“}”,而静态全局变量和全局变量的作用域都是从程序开始到结束。

在这段代码中,我们可以看到,最开始我们在Test4()中定义了char型指针变量str,并赋值为NULL,接着我们再将GetMemory()的返还值返还给str,在GetMemory()中,我们定义了一个静态局部变量,一个字符数组,数组名为p,那么这个数组名p代表的是数组空间首地址,而这个空间位于静态区,故其生命周期会一直到程序结束,则我们将p返还回去的时候,字符数组所在空间不会被释放,则最终我们可以正常输出,故这段代码也是没有问题的。

TEST5
#include 
#include 
#include 
#include 
void Test5(void)
{
	char* str = (char*)malloc(100);
	assert(str != NULL);
	strcpy(str, "hello");
	free(str);
	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}
int main()
{
	Test5();
	return 0;
}

对于这段代码,我们一眼看过去似乎并没有什么问题,malloc()申请了堆区空间后也判断了是否申请成功,也free()释放了这一空间,当我们运行这段代码后,结果如下:

其实,对于这段代码的输出结果对于不同的编译器或者运行环境可能会有不同的结果,有可能您的输出结果会是正常的输出“world”,那么我们到底犯了什么错误呢,在C语言中有一种非常严重的错误称作“踩内存”,我们再次以内存的角度来读一下这段程序;

首先,在Test5()中,我们在堆区申请了一块100字节的空间(char型占用1字节)并用栈区的局部char型指针变量存储这块堆区空间的地址,接下来我们利用assert()来判断下是否申请成功了;

接着,我们用strcpy()字符串拷贝函数,将一个字符串常量"hello"拷贝到了我们所申请的堆区空间中,接着我们又将这一堆区空间释放了,也就是说此时,我们不能确定之后计算机是否会使用这里的堆区空间进行操作,然而,事实是我们又一次的使用strcpy()这一函数,将一字符串常量“world”拷贝到了这一堆区空间,如果此时我们的计算机并没有使用这里的堆区空间,那么没有问题,我们会输出粗存在这里的字符串“world”,但如果计算机正在使用这里的空间,那么我们就犯了踩内存的错误了,因此在读取这里的内容时会出现错误 。

因此,我们在释放了申请的堆区空间以后,就不可以再次使用这里了,需要重新申请。

TEST6
#include 
#include 
#include 
#include 
void Test5(void)
{
	char* str = (char*)malloc(100);
	assert(str != NULL);
	strcpy(str, "hello");
	str += 6;
	free(str);
	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}
int main()
{
	Test5();
	return 0;
}

这段代码和TEST5基本一致,唯一不同的地方在于我们在完成第一次字符串拷贝操作后,我们将储存堆区空间地址的指针变量str向后移动了6位,接着我们想要进行释放,但是free()释放操作时要求,传入的地址必须是malloc()函数的返还值,否则会出现异常。

以上便是全部GetMemory()笔试题的详解。

转载请注明:文章转载自 www.wk8.com.cn
本文地址:https://www.wk8.com.cn/it/1038844.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 wk8.com.cn

ICP备案号:晋ICP备2021003244-6号