动态内存管理(2)

TIPS

1. scanf读取与空格:

我们都知道,scanf()在从输入缓冲区里面读取数据的时候,如果中间碰到了空格,那么就会直接停下来,而如果在最前面有个空格,直接无视空格。 

 

2. scanf()读取与\n,如果是读取字符串或者数字,那么最开头的\n根本起不到什么影响但是如果scanf%c去读取字符的话,如果输入缓冲区里的\n没有被清理掉,就会直接去读\n这个字符 

3.  

有关动态内存分配题目1

附:

1. 绝大部分情况下,指针(变量)也是临时变量,是放在内存栈区的,内存栈区里面的东西一旦出了其作用域,就会直接销毁。
2. 实参是指针变量,形参是指针变量,这两个指针变量在交接的时候都指向同一地址,是传值调用,形参是实参的一份临时拷贝。交接完之后,形参在怎么折腾,与实参无关。但同时,由于形参也是指针,它解引用在内存里面改来改去的话当函数调用结束后会遗留下影响的。但是形参有自己的独立空间
3. NULL空指针其实就是0。0地址这块空间是不允许普通程序去访问的。写入的时候会发生访问冲突。但是对空指针取地址是可以的,解引用访问是不行的
4. 里面也存在内存泄露。这个malloc向堆区申请来的空间函数里面没有释放,出了函数也不给留个"标记"指针,这块空间没人记得了也没还给操作系统。 
6. 实参为指针变量还是传值调用,实参为货真价实的地址就是传址调用

解决办法:

void GetMemory(char** p)
{
	char* pa = (char*)malloc(100);
	if (pa == NULL)
	{
		perror("");
	}
	*p = pa;
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str);
	strcpy(str, "hello world");
	printf(str);
	free(str);
	str = NULL;
}
int main()
{
	Test();
	return 0;
}

有关动态内存分配题目2

 

附:

1. 对于内存栈区与堆区一定要区分开。不能混淆在一起,哪个东西在哪边要清清楚楚。
2. 局部变量(包括数组,指针变量等等),函数的参数与函数栈帧,反正一切临时的变量都是在内存栈区里面的,对于内存栈区里面的东西,进入其作用域就会创建,出了之后就会销毁,原先的那个内存空间已经还给操作系统了。而对于内存堆区而言,我是专门用来进行动态内存的开辟与释放的,只要你一旦创建好,没有手动用free(指向那个堆区空间的指针)释放的话,那个空间就像鬼屋一样一直在那。
3. 当函数内部有一个指针指向一个栈区空间,然后你返回指针,这时候函数调用结束后里面的所有东西全部销毁,原先指针指向的空间也已经还给操作系统了。这时候返回一个指针,如果还厚着脸皮去解引用,就会造成非法访问。这个叫做返回栈空间地址的问题。地址我虽然存起来了,但是我空间已经销毁了啊。但是会有偶然现象发生,有时候这个空间还给操作系统了,但是这个数据倒还是停留在里面没有被覆盖掉。
(常量字符串不像局部变量一样,局部变量是在栈区里面的,进入范围就创建,出了范围就会销毁。而常量字符串并不是局部范围的变量,不是在栈区的,而在可读数据区,所以并不会随着函数的返回而销毁)
(static修饰的话就是说即使出了作用域,变量也不会销毁,空间还是属于我们的,没有还给操作系统,static 可以修饰数组与变量..)

4. 至于说内存泄露,那是对堆区而言,malloc等申请了一个堆区空间,结果我不用了,又不返回,别人用不了,回头这个空间还可能丢了。再次强调一下:栈区与堆区千万不能一团浆糊搞在一起。

有关动态内存分配题目3

附:

1. 唯一的问题就在于内存泄漏

2. 

 1. 用malloc,calloc,realloc开辟的堆区空间不会自己去释放,一定需要你手动用free去释放
 2. 你如果不释放的话,就会形成内存泄露:
你不用了
你不释放
别人还用不了
回头你这块空间还可能找不到了 

有关动态内存分配题目4  

附: 

非法访问,属于操作系统的内存空间是你能随便这么用野指针解引用去访问的吗?

1. free(p)的话也就是把p指向的堆区空间还给了操作系统。但是对于那个指针p的话,它还是这么孤零零的指向着那边,虽然他指向的那个堆区里面原先的空间早已去世了,这时候就会很危险,万一对它解引用操作的话就会非法访问,这也是为什么叫回收置空,要置空,置空,置空,置空,别以为free完了之后就可以拍拍屁股走了
2. free()的意思就是把指针指向的堆区空间还给操作系统,但是我这块堆区空间还在那儿放着,里面的数据可能没有被别人修改。但是已经属于操作系统了,使用权限不在我这儿了。
3. 指向属于操作系统的堆区/栈区空间的指针就是一条野狗,野指针。
4. 你去使用修改与访问属于操作系统的栈区/堆区空间,已经形成了非法访问了,代码出问题了。
修改:将str置为空

C/C++程序的内存开辟 

那我们数据在内存里面开辟空间的时候,到底是怎么在内存里面开辟的呢?
C/C++程序到底是如何去内存里面开辟空间的呢?
1. 我们之前知道内存分为三个区域:栈区,堆区,静态区。
2. 但是让我们现在分析的更加细一点。
3. C/C++程序当中内存区域的划分其实是比较多的。

内核空间

1. 比如说有内核空间,用户的代码是不能去读写这个区域的。这里面是来放我们操作系统的内核的,我把操作系统跑起来的时候,它有一个内核,而这里面是它内核运行所需要的空间,用户代码不得访问!

2. 这块区域专门留给操作系统,任何人都不能访问。我们自己写的程序压根儿就没有权限去访问的,是留给操作系统内核去使用的。

栈区

1. 栈区:在执行函数时,函数内局部变量(数组啊,指针变量啊,整型啊等等等)的存储单元都可以在栈上搭建。当函数运行结束时,函数栈帧销毁,这些存储单元也自动被释放

2. 栈内存的分配运算内置于处理器的指令集中,效率非常高,但是分配的内存容量有限

3. 栈区主要存放运行函数分配的局部变量,函数参数,返回数据,返回地址等,总而言之都是临时的变量

内存映射段 

暂时不用去了解它 

堆区

1. 堆区:堆区里面空间一般由程序员去分配,去释放。如果程序员不释放,程序运行结束的时候(一定是要在结束的时候,如果没结束就回收不了,内存泄露)可能由操作系统回收。分配方式类似于链表(链表现在也不懂不要紧)

2. malloc , calloc ,realloc ,free 专属操作区域

数据段/静态区

1. 数据段:也就是经常叫的静态区,里面是用来存放全局变量,静态数据由static修饰的那些局部变量

2. 由操作系统的程序结束之后回收

代码段

1. 代码段:.......常量字符串就放在只读数据区
2. 在数据段下面还有代码段,在这边放一些可执行的代码,只读的常量啊,字符串常量就是放在这里边的。


1. 全局变量、静态变量、被static修饰的局部变量是放在内存里面的数据段的。
2. 在函数里面创建的数组啊,指针变量啊,整型啊等等,这些普通的局部变量是在栈区上开辟空间的。
3. 而malloc, calloc, realloc,开辟的空间都是在内存的堆里面开辟的。
4. 而常量字符串等常量数据是放在内存里面的只读数据区的

再次回顾static关键字

1. 实际上普通的局部变量是在栈区上分配空间的
2. 栈区的特点是在上面创建的变量出了作用域就会销毁。
3. 但是当被static修饰的时候,这个局部变量就不是放在栈区上了,而是放在数据段(静态区)
4. 数据段的特点就是再上面创建的变量,直到程序结束之后才会被销毁,被操作系统回收。生命周期变长了

柔性数组 

1. 如果在结构体当中,它允许里面最后一个成员未知大小的数组,这个成员(一个成员)就叫做柔性数组成员。
2. 柔性数组必须是结构体当中最后一个成员,如果是一个数组的话,可以不指定元素个数。
3. 在结构当中,如果最后一个成员是数组,现在我们已经知道是柔性数组了,那么在代码里面写的时候,这个[ ]里面可以什么都不写,也可以在里面写一个0,这两种写法代表的意思是一样的:数组大小未知,我没有指定大小。 

 

柔性数组特点

1. 结构体当中柔性数组成员之前必须至少拥有一个其他成员。
2. sizeof(结构体),返回的大小不包含柔性数组的内存。
3. 用malloc函数为包含柔性数组成员的结构体在堆区中开辟内存空间,分配的内存要大于结构体的大小(不能再malloc函数括号里面直接sizeof结构体就拍拍屁股走人了),比方说我希望在最后一个数组成员里面有10个整型的话:就malloc(sizeof(结构体)+10×sizeof (int))。这是为了适应柔性数组的预期大小,在强制类型转化的时候,整个被开辟的堆区空间还是相当于属于结构体的void*就强制类型转化为结构体指针就OK了。接下来这个结构体指针就正常访问成员就OK了。
4. 柔性数组也是一个数组罢了,这个空间不是一次性开辟好的,而是通过malloc的方式在sizeof(结构体)后面+的时候自己指定的,你想要多大你就自己指定。
5. 当你觉得空间不够的时候,就增容用realloc,括号里面还是与malloc一样的。
sizeof(结构体)+自己新指定元素个数。
sizeof(包含柔性数组成员的结构体)计算的时候,最末一个柔性数组成员是不包括进去的 

struct class
{
	char classname[20];
	int score[];
};
int main()
{
	char name[20] = { 0 };
	printf("请输入班级名称:");
	gets(name);
	//
	printf("请输入班级考试人数:");
	int number = 0;
	scanf("%d", &number);
	//
	struct class* p = (struct class*)malloc(sizeof(struct class) + number * sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
	}
	strcpy(p->classname, name);
	//
	int i = 0;
	for (i = 0; i < number; i++)
	{
		printf("请输入第%d个考生的成绩:", i + 1);
		scanf("%d", &(p->score[i]));
	}
	printf("\n");
	//
	printf("是否需要补录(Y/N)");
	int STU_INC = 0;
	getchar();
	char Judge = 0;
	scanf("%c", &Judge);
	if (Judge == 'Y')
	{
		printf("请输入要增加的学生人数:");
		scanf("%d",&STU_INC);
		struct class* p1 = (struct class*)realloc(p, sizeof(struct class) + (number + STU_INC) * sizeof(int));
		if (p1 != NULL)
		{
			p = p1;
		}
		for (i = number; i < number + STU_INC; i++)
		{
			printf("请输入第%d个考生的成绩:", i + 1);
			scanf("%d", &(p->score[i]));
		}
		printf("\n");
	}
	printf("%s班的考试成绩如下:", p->classname);
	for (i = 0; i < number + STU_INC; i++)
	{
		printf("%d ", p->score[i]);
	}
	printf("\n");
	free(p);
	p = NULL;
	return 0;
}

柔性数组的优点:

1. 第一个好处是:方便内存释放
如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给
用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你
不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好
了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。
2. 第二个好处是:这样有利于访问速度.
连续的内存有益于提高访问速度,也有益于减少内存碎片。(其实,我个人觉得也没多高了,反正
你跑不了要用做偏移量的加法来寻址) 

柔性数组成员在用动态开辟内存的时候,参数与sizeof(这时候计算结构体大小不把柔性数组成员列入在内,它等会单独加上去)使用的时候需要稍微处理,但是返回的一个结构体指针指向的结构体还是包含着末尾的柔性数组成员的

  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

絕知此事要躬行

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值