动态内存管理(2)

目录​​​​​​​

三、常见的动态内存错误:

3.1 对NULL指针的解引用操作:

3.2 对动态开辟空间的越界访问:

3.3 对非动态开辟内存使用free释放:

3.4 使用free释放一块动态开辟内存的一部分:

3.5 对同一块动态内存多次释放 :

3.6 动态开辟内存忘记释放(内存泄漏):

四、几个经典的笔试题:

4.1、题目一:

4.2、题目二:

4.3、题目三:

4.4、题目四:

 五、C/C++程序的内存开辟 :

 六、柔型数组 :

6.1 柔性数组的特点: 

6.2 柔性数组的使用:

6.2.1 使用柔型数组成员的方法:

6.2.1 不使用柔型数组成员的方法:

6.3 柔性数组的优势:



三、常见的动态内存错误:

3.1 对NULL指针的解引用操作:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	//malloc函数在堆区上如果要申请的空间过大就会失败,返回一个空指针NULL
	int*p = (int*)malloc(100000000000 * sizeof(int));
	//不判断整形指针变量p是否为空指针NULL,直接进行操作
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
		//当i=0时,*(p+i) === *p ,,由于在堆区上申请的空间过大,会失败,返回空指针NULL,所以整型指针变量p是一个空指针,现在的*p就相当于对空指针NULL进行解引用操作
		//空指针NULL不指向任何一块空间,空指针NUL是不可以使用的,会造成非法访问内存。
		//所以,一定要对malloc函数的返回值做判断处理才行、
	}

    free(p);
    p==NULL;
	return 0;
}

3.2 对动态开辟空间的越界访问:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int*p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		//开辟空间失败
		perror("main");
		return 1;
	}
	else
	{
		//开辟空间成功、进行使用、
		int i = 0;
		//越界访问
		for (i = 0; i < 40; i++)
		{
			*(p + i) = i;
			printf("%d ", *(p + i));
		}
		//在堆区上开辟的空间也是有范围的,申请多少就使用多少,不可以越界访问。
		//和数组的越界访问时非常相似的,对于数组的越界访问,编译器是可以不检查的,不报错不代表他就是正确的、
	}
    free(p);
    p==NULL;
	return 0;
}

3.3 对非动态开辟内存使用free释放:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int a = 10;//局部变量,在栈区上开辟空间、
	int* p = &a;
	//.....使用
	free(p);
	p = NULL;
	return 0;
}

3.4 使用free释放一块动态开辟内存的一部分:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int*p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		perror("main");
		return 1;
	}
	else
	{
		int i = 0;
		for (i = 0; i < 5; i++)
		{
			*p++ = i;
			//整型指针变量p所指向的位置已经发生改变、
		}
	}
	free(p);//此时整形指针变量p已经不再指向在堆区上开辟的空间的首元素的地址了,相当于释放了该空间的一部分,这是错误的、
	//不能从动态开辟的连续空间中间某一个位置去释放,必须从首元素的地址,即从头开始释放。
	//当按照此种方法往后走的时候,就再也找不到了在堆区上开辟的连续空间的首元素的地址了,想再去释放该空间是不可以的,这就叫做内存泄漏、
	p = NULL;
	return 0;
}

3.5 对同一块动态内存多次释放 :

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int*p = (int*)malloc(10 * sizeof(int));
	//使用
	//释放
	free(p);
	//.....
	//再一次释放
	free(p);
	p = NULL;
	return 0;
}
//在函数内部释放完之后,出了函数之后又再次释放,就会出现错误、
#include <stdio.h>
#include <stdlib.h>
int main()
{
	int*p = (int*)malloc(10 * sizeof(int));
	//使用
	//释放
	free(p);
	p == NULL;
	//如果担心多次释放造成错误的话,就在第一次释放完之后把指针变量p置成空指针NULL,等后面如果再对指针变量p进行释放的时候,由于指针变量p里面
	//存放的是空指针NULL,对空指针NULL进行释放就相当于没起任何作用、
	//.....
	//再一次释放
	free(p);//p里面存放的是空指针NULL
	p = NULL;
	return 0;
}

3.6 动态开辟内存忘记释放(内存泄漏):


void test()
{
	int*p = (int*)malloc(100 * sizeof(int));
	if (p = NULL)
	{
		perror("main");
		return 1;
	}
	else
	{
		//使用,但是忘记了释放、
	}
}
//当调用函数test结束的时候,由于整型指针变量p是在test函数中定义的,是一个局部变量,当出来test函数的时候,该整型指针变量p就会被销毁,
//但是没有释放整形指针变量p所指向的那一块在堆区上面的连续的内存空间,当整形指针变量p被销毁的时候,就再也找不到了那一块空间了,这就属于内存泄漏
//一旦出去test函数之外,再想去主函数里面释放这一块空间就不行了,找不到了。
//除此之外还有个问题就是:
//由于我们是在调用函数test中通过malloc函数开辟的内存空间,当test执行完毕后被销毁,那么该内存空间会不会被销毁呢?
//答案是不会,原因是:由malloc、calloc、realloc等函数开辟出来的空间只有两种情况会被销毁:
//1、主动销毁的时候,会被销毁、
//2、程序结束,不是函数结束,即,当所有的程序都结束之后,才会释放,也可以理解成当main主函数全部结束之后,这些空间才会被释放。
//忘记释放,又没办法再对他进行释放的情况,就会造成内存泄漏问题。

//所以当使用:malloc、calloc、realloc等函数在堆区上开辟内存空间的时候,一般都会与释放函数free成对使用、
#include <stdio.h>
#include <stdlib.h>
int main()
{
	test();
	//....
	return 0;
}
//忘记释放不再使用的动态开辟的空间会造成内存泄漏、
//动态开辟的空间一定要释放,并且正确释放 。

四、几个经典的笔试题:

4.1、题目一:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//字符指针变量str中放置的是一个空指针NULL,把str传给GetMemory函数的时候,由于是传值调用,所以,GetMemory形参中的字符指针变量p是实参str变量的一份临时拷贝
//在GetMemory函数内部动态申请的一块空间的起始地址存放在字符指针变量p中,不会影响外面的变量str,所以当GetMeory函数返回之后,字符指针变量str中放置的仍是一个
//空指针NULL,所以strcpy会失败,当GetMemory函数返回之后,字符指针变量p就会被销毁,导致在堆上动态开辟的空间造成内存泄漏,无法释放,并在在其他函数中是没有办法来释放
//该内存空间的、 
void GetMemory(char *p) //形参是实参的一份临时拷贝,//GetMemory函数返回类型是:void
{
	p = (char *)malloc(100);//在堆上申请内存空间,大小为100byte,只开辟100个字节,能开辟成功,malloc函数返回来的是在堆上开辟的
	//一块空间的起始地址,并把该地址放在字符指针变量p里面、又因字符指针变量p即是形参变量,又是局部变量,出了他的生命周期,即
	//出来GetMemory函数,该指针变量就会被销毁,
}
//当出来GetMemory函数的时候,字符指针变量p就会销毁,所以malloc函数在堆区上开辟的内存空间就找不到了,并且再其他函数中还没有办法去
//释放该内存空间,因为,要想释放该内存空间,就必须要先找到指向该内存空间的指针变量,即字符指针变量p,然后free(p),才可以把该内存空间释放掉,但是,当字符指针变量p
//出来调用函数之后,就会被销毁,找不到了,所以,该内存空间就找不到了,并且还没办法再被释放,这就造成了内存泄漏、
void Test(void) 
{
	char *str = NULL;
	GetMemory(str);//传值调用,一级指针传参,一级指针接收,
	strcpy(str, "hello world");//当GetMemory函数执行完毕到此位置,此时字符指针变量str中放置的还是空指针NULL,现在要把该字符串
	//拷贝放在一个空指针所指的空间中,是有问题的,因为空指针NULL不指向任何一个空间、
	printf(str);
}
int main()
{
	Test();
	return 0;
}//程序会崩溃掉、

修改方法一:

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

char* GetMemory(char *p) //注意:GetMemory函数的返回类型是char*类型、
{
	p = (char *)malloc(100);
	return p;
	//关于return带会返回值的原因见栈区空间的笔记,在这里,是可以把值带出来的、
}

void Test(void) 
{
	char *str = NULL;
	str = GetMemory(str);//此时,字符指针变量str就指向了malloc函数在堆区上动态开辟的一块内存空间的起始位置的地址、
	strcpy(str, "hello world");//此时字符指针变量str中不是空指针NULL,默认malloc函数可以开辟内存空间成功、
	printf(str);//只能在打印字符串的时候,才能简写成这种形式、
	free(str);
	str = NULL;
	//注意:printf在形参为:int printf ( const char * format, ... );
	//比如:printf("hello world");
	//此时printf函数的参数是一个格式,本质上传参过去的是该字符首元素的地址,还要知道,printf函数默认以 %s 进行打印,所以就可以这届打印出来
	//hello world
	//同时,对于printf("*")和printf(" "),本质上和上面是一样的,,都是一个字符串、
	//这里的字符指针变量str指向的就是malloc函数在堆区上动态开辟的内存空间的的起始位置的地址,就可以打印出来:hello world 、
}
int main()
{
	Test();
	return 0;
}//此时就可以把字符串"hello world""打印出来、

修改方法二:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void GetMemory(char** p)//注意:GetMemory函数的返回类型是void类型、
{
	*p = (char *)malloc(100);//*p就可以找到外面的字符指针变量str,
}

void Test(void)
{
	char *str = NULL;
	GetMemory(&str);//传址调用,
	strcpy(str, "hello world");//此时默认malloc函数能够开辟内存空间成功,所以,字符指针变量str中就不是空指针NULL、所以strcpy可以正常工作。
	printf(str);//打印
	free(str);
	str = NULL;
}
int main()
{
	Test();
	return 0;
}//此时就可以把字符串"hello world""打印出来、

4.2、题目二:

//#include <stdio.h>
//#include <stdlib.h>
//#include <string.h>
//char *GetMemory(void) //GetMemory函数返回类型是:char*
//{
//	char p[] = "hello world";
//	//局部数组,在栈区上开辟空间,出了生命周期之后,该局部数组就会被销毁,即,归还给了操作系统,
//	return p;
//	//把要即将销毁的局部数组的数组首元素的地址返了回来,返回它是没有意义的,返回之后,该局部数组就会被销毁了、如果通过返回的地址去访问内存,就是非法访问内存、
//
//	//如果仅看等号后面的字符串,他是一个常量字符串,他要放在内存中的代码段中,但是,现在把常量字符串放在了一个字符数组里面,相当于是把代码段中的常量字符串临时拷贝了一份放在了
//	//字符数组里面,由于该字符数组是局部数组,出了函数就会被销毁,所以,数组中的数据,即该字符串会随着该字符数组一起被销毁、
//
//	//存储在内存中代码段中的数据的生命周期是整个程序,它出了调用函数生命周期不会结束,所以不会被销毁,只有当整个程序结束的时候,才会被销毁,在这里也不考虑代码段中的数据能够
//	//主动被销毁的情况、
//}
//void Test(void)
//{
//	char *str = NULL;
//	str = GetMemory();//把返回来地址放在了字符指针变量str中,所以,该变量就指向了局部数组p的首元素的地址,但是,当把该地址存放在字符指针变量str
//	//中的时候,局部数组p就已经被还给了操作系统,那么再使用printf进行打印的时候,该空间可能就被别的数据覆盖掉了,又因printf默认以%s进行打印,
//	//就从该位置往后打印字符串,但是别的数据不知道是啥,所以可能会打印出来一串随机值,然后遇到字符\0停止打印,所以在这里不会打印出来字符串
//	//hello world 
//	printf(str);//打印出来是乱码、
//}
//int main()
//{
//	//这属于 返回栈空间地址的问题、
//	Test();
//	//要注意,上述数组p,是局部数组,在栈区上进行开辟空间,出了它的生命周期就会被销毁,但是上一题中的使用malloc函数动态开辟的内存空间是在
//	//堆区上进行开辟的,此时所在函数中的{ }不是它的生命周期,出了这个区域,该动态内存空间不会被销毁
//	//对于在堆区上面动态开辟的内存空间来说,他的销毁方式只有两种,前面已经总结、
//	return 0;
//}
int main()
{
	char arr[] = "hello world";
	//只看后面的字符串,是一个常量字符串,要放在内存中的代码段上,并且放在代码段上的数据的生命周期是整个程序,出调用函数该常量字符串不会被销毁,
	//只有当整个程序结束的时候,该常量字符串才会被销毁,在这里不考虑主动销毁的情况、
	//但是此时把该常量字符串放在了一个字符数组里面,相当于是把代码段中的常量字符串做了一份临时拷贝放在了字符数组中,由于该字符数组是局部数组,出了调用函数
	//就会被销毁,所以该字符数组中的内容也会随着销毁,
	char* p = "hello world";
	//只看后面的字符串,是一个常量字符串,要放在内存中的代码段上,现在字符指针变量p指向该常量字符串首元素的地址,即,该指针直接指向了代码段中的常量字符串、
	//但是由于字符指针变量p是一个局部变量,他的生命周期就在它所在的{ }内部,出了{ },字符指针变量p就会被销毁,但是,如果能够把p的值返回来,就可以再次使用该地址
	//去找到在内存中代码段中的常量字符串了,因为该常量字符串的生命周期是整个程序,出了调用函数后,不会被销毁。
	printf("%p\n", arr);             //00DEF7B8
	//第一种情况是把在代码段中的常量字符串临时拷贝了一份放在了局部字符数组arr中,数组先在栈区上开辟了内存空间,该空间里面存放的是常量字符串的内容,arr是数组名
	//不是特例,代表的是数组首元素的地址,由于数组中的数据是放在了栈区上,所以,数组首元素的地址,指的就是栈区上该字符串的首元素的地址,和后两种情况是不一样的,

	//最后一种情况,字符指针变量p指向的就是在代码段中的常量字符串的首元素的地址,第二种情况直接对一个常量字符串进行打印,由于printf函数的参数部分是使用的
	//char* 类型来接收的,所以,本质上是把该常量字符串首元素的地址传给了该函数,和第三种情况是一样的,由于常量字符串都是放在代码段中的,又因为存储在代码段中的数据
	//是不可以被修改的,是只读常量,所以只需要在代码段中存储一份该常量字符串即可,所以,后两者的地址是一样的,都是指代码段中的常量字符串的首元素的地址、
	printf("%p\n", "hello world");   //00215858
	printf("%p\n", p);               //00215858
	return 0;
}

修改方法一:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char* GetMemory(void)//GetMemory函数返回类型是:char*
{
	static char p[] = "hello world";
	//static修饰的叫做静态变量,是在静态区(数据段)上开辟的内存空间,它的生命周期是整个程序,出了调用函数的{}后,不会被销毁,只有当整个程序都结束的时候,才会被销毁,不考虑主动销毁的情况、
	//static修饰的局部变量出了调用函数后不会被销毁,生命周期是整个程序。
	return p;
}
void Test(void)
{
	char *str = NULL;
	str = GetMemory();
	printf(str);
}
int main()
{
	Test();
	return 0;
}

修改方法二、

#include<stdio.h>
char* GetMemory(void)
{
	char *p = "hello world";
	//只看后面的字符串是一个常量字符串,存储在内存中的代码段中,他的生命周期是整个程序,出了调用函数后不会被销毁,不考虑主动销毁的情况,而字符指针p直接
	//指向了代码段中,该常量字符串首元素的地址,但是,由于字符指针变量p是一个局部变量,出了函数,该变量就会被销毁,现在把p中的值返了回来,即把代码段中的
	//常量字符串的首元素的地址返了回来,由于代码段中的常量字符串的生命周期是整个程序,所以,该地址是一个有效的地址,指向了代码段中该常量字符串首元素的地址、
	return p;
}
void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	//把GetMemory函数的返回值放在了放在了字符指针变量str中,即,字符指针变量str就指向了内存中代码段中常量字符串首元素的地址、
	printf(str);
}
int main()
{
	Test();
	return 0;
}

拓展:

//一、
int* f1(void)
{
	int x = 10;//局部变量,在栈区上开辟空间,出了生命周期,变量x就会被销毁,当把变量x的地址返回出去,如果再通过该地址进行访问的时候, 就会造成
	//非法访问、
	return (&x);
}

//二、
int* f2(void)
{
	int* ptr;//局部变量不进行初始化,里面放的就是一个随机值,全局变量不初始化就默认是0;
	//就会把该随机值当做是一个地址,然后对该地址进行解引用操作,由于该地址是一个随机值,所以解引用之后的空间的位置也是随机的,
	//那么找到的一个随机的空间都可能不是这个程序中的,这就是一个野指针问题,可能会造成非法访问内存,具体定义见笔记、
	//野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
	//所以对于指针来说,要么就指向一个有效的地址,要么就置为空指针NULL,比较安全。
	*ptr = 10;
	return ptr;
}

4.3、题目三:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void GetMemory(char** p, int num)
{
	*p = (char *)malloc(num);
	//把字符指针变量str的地址传了过来,由于字符指针变量str的类型是char*,所以它的地址需要使用char**二级指针来接收、
	//所以二级字符指针变量p就指向了字符指针变量str,*p就拿到了字符指针变量str,并把malloc函数动态开辟的内存空间的起始位置的地址放在了
	//字符指针变量str中,所以,字符指针变量str就指向了malloc函数动态开辟的内存空间的起始位置的地址。
}
void Test(void) 
{
	char *str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
	//但是使用完malloc函数动态开辟的内存空间之后,没有进行释放、所以下面要把该空间释放掉,并且把字符指针变量str置为空指针NULL
	free(str);
	str = NULL;
}
int main()
{
	Test();
	return 0;
}
//如果不对malloc函数动态开辟的内存空间进行释放的话,由于字符指针变量str是一个局部变量,出了函数就会被销毁,此时没有释放该动态内存空间,就会造成内存泄漏、

 4.4、题目四:

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

void Test(void) 
{
	char *str = (char *)malloc(100);
	strcpy(str, "hello");
	free(str);
	//free释放,就会把字符指针变量str指向的动态开辟的内存空间还给操作系统,但是free函数没有能力把字符指针变量str置为空指针,所以,字符指针变量
	//str中存放的还是之前malloc函数动态开辟的内存空间的起始位置的地址,并不是空指针NULL,所以进入下面的if语句,但是,由于该动态内存空间已经被
	//释放了,strcpy函数再次使用该空间的时候就会造成非法访问内存空间,所以是不对的。
	//本题考查的点在于,当free(str)之后,要把字符指针变量str置为空指针NULL,所以下面的if语句就不进入了,就不会造成非法内存访问了。
	//str = NULL;
	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}
int main()
{
	Test();
	return 0;
}//把字符指针变量str置为空指针NULL后,不进入if语句,由于在此之前没进行打印,所以程序能跑起来,但是不会打印出内容。

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

1、对于局部变量、临时变量、函数形参一般都是在内存中栈区上进行开辟空间的,他们的生命周期一般就在函数内部进入函数开始创建,离开函数就要销毁、

2、对于使用malloc、calloc、realloc函数一般都是在内存中堆区上进行动态开辟内存空间,所开辟的动态内存空间的销毁一般只有两种情况:

      一、自己主动释放动态开辟的内存空间、

      二、对于这三个函数所开辟的动态内存空间来说,他们的生命周期是整个程序,即,当整个程序,也可以理解成整个main函数结束之后,他们才会被销毁

3、对于存储在数据段(静态区)中的全局数据和静态数据以及存储在代码段中的可执行代码和只读常量来说,他们的生命周期都是整个程序,即整个main函数,只有当整

     个程序全部结束的时候,他们的生命周期才结束,在这里不考虑主动销毁的情况、

4、static修饰的变量叫做静态变量,是在内存中数据段(静态区)中进行开辟内存空间的,而且,static既可以修饰局部变量,也可以修饰全局变量、

C/C++程序内存分配的几个区域:

1. 栈区(stack) :在执行函数时,函数内 局部变量 的存储单元都可以在 栈上 创建,函数执行 结束时 这些存储单元 自动被释放 ,在 栈区上 开辟空间的数据都是 自动创建
动释放 的,栈内存 分配运算 内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的 局部变量、函数形参、返回数据、返回地址
等。
2. 堆区(heap) :一般由程序员分配释放, 若程序员不释放, 程序结束时 可能由 OS 回收 ,分配方式类似于 链表
3. 数据段(静态区) static )存放 全局变量、静态数据 。程序结束后由 系统释放
4. 代码段 :存放 函数体(类成员函数和全局函数) 二进制代码
实际上 普通的局部变量 是在 栈区 分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。 但是被static 修饰的变量存放在 数据段(静态区) ,数据段的特点是在上
面创建的变量,直到 程序结束才销毁 ,所以 生命周期变长。
静态区又称为全局数据区和数据段,常量区又称为代码段和公共代码区、

 六、柔型数组 :

也许你从来没有听说过  柔性数组(flexible array) 这个概念,但是它确实是 存在 的、 C99 中, 结构体成员变量中 最后一个元素 允许 未知大小的 数组
这就叫做 『柔性数组』 成员,所谓 柔性 即指其 大小 可以改变 的, VS编译器是支持C99的、 柔性数组只能出现在结构体中、
例如:
//写法一:
struct S
{
	int n;
	int arr[];//柔性数组成员,在结构体成员变量中,一般不进行初始化,所以当写正常的数组的时候,就必须加上数组元素个数,但是由于这是柔性数组
			  //所以,该数组的元素个数可以是未知的、
};
//写法二:
struct S
{
	int n;
	int arr[0];//柔性数组成员,在结构体成员变量中,一般不进行初始化,所以当写正常的数组的时候,就必须加上数组元素个数,但是由于这是柔性数组
	//所以,该数组的元素个数可以是未知的,也可以把柔性数组的元素个数写成0,也代表该柔性数组的大小即元素个数是未知的、
};//有些编译器会报错无法编译可以改成方法一即可、

6.1 柔性数组的特点: 

1、 结构体成员变量 中的柔性数组成员 前面必须至少一个其他成员。
2、 sizeof 计算 返回的这种结构体的 大小 不包括柔性数组的内存。
3、 包含 柔性数组成员 的结构体用 malloc () 函数进行 内存的动态分配 ,并且分配的内存应该 大于结构体的大小 ,以适应柔性数组的 预期大小
例如:
struct S
{
	int n;
	int arr[0];//柔性数组成员,在结构体成员变量中,一般不进行初始化,所以当写正常的数组的时候,就必须加上数组元素个数,但是由于这是柔性数组
	//所以,该数组的元素个数可以是未知的,也可以把柔性数组的元素个数写成0,也代表该柔性数组的大小即元素个数是未知的、
};
int main()
{
	struct S s = { 0 };
	printf("%d\n", sizeof(s));         //4
	printf("%d\n", sizeof(struct S));  //4
	return 0;
}

6.2 柔性数组的使用:

6.2.1 使用柔型数组成员的方法:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct S
{
	int n;
	int arr[0];//柔性数组成员
};//包含柔性数组成员的结构体,要使用malloc等函数进行动态开辟内存空间,一次性开辟、
int main()
{
	//期望柔性数组arr的大小是10个int整型 
	struct S* ps= (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));
	//通过结构体指针变量ps来访问malloc函数在堆区上动态开辟的内存空间的时候,分为两部分,第一部分的内存分配给了整形int变量n,剩余的一部分内存
	//分配给了柔性数组成员arr来使用,当发现柔性数组成员arr不够使用的时候,由于该内存空间是由malloc函数来动态开辟的,所以就可以使用realloc函数
	//来扩容了,由于后面的柔性数组成员所占内存空间的大小是可以改变的,这就体现出来了柔性的作用、
	if (ps == NULL)
	{
		//开辟空间失败
		perror("main");
		return 1;
	}
	else
	{
		//开辟空间成功
		ps->n = 10;
		int i = 0;
		for (i = 0; i < 10; i++)
		{
			ps->arr[i] = i;
		}
		for (i = 0; i < 10; i++)
		{
			if (i == 0)
			{
				printf("%d ", ps->n);
			}
			printf("%d ", ps->arr[i]);
		}
		//如果发现柔性数组成员arr不够使用的时候,就可以使用realloc函数来扩容了、
		struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 20 * sizeof(int));
		if (ptr == NULL)
		{
			//增容失败
			perror("main");
			return 1;
		}
		else
		{
			//增容成功
			ps = ptr;
		}
		//使用
		i = 0;
		for (i = 10; i < 20; i++)
		{
			ps->arr[i] = i;
			printf("%d ", ps->arr[i]);
		}
	}
	//释放
	free(ps);
	ps = NULL;
	return 0;
}

6.2.1 不使用柔型数组成员的方法:


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct S
{
	//两个变量 — 整型变量n和整形指针变量 arr;
	int n;
	int* arr;//再动态开辟该指针指向的空间即可、
};
int main()
{
	//保证所有的空间都在 堆区 上进行开辟、
	struct S* ps = (struct S*)malloc(sizeof(struct S));
	if (ps == NULL)
	{
		//开辟动态内存空间失败
		perror("main");
		return 1;
	}
	else
	{
		//开辟动态内存空间成功
		ps->n = 10;
		ps->arr = (int*)malloc(10 * sizeof(int));
		if (ps->arr == NULL)
		{
			//开辟动态内存空间失败
			perror("main");
			return 1;
		}
		else
		{
			//开辟动态内存空间成功
			int i = 0;
			for (i = 0; i < 10; i++)
			{
				ps->arr[i] = i;
				if (i == 0)
				{
					printf("%d ", ps->n);
				}
				printf("%d ", ps->arr[i]);
			}
			//增容
			int* ptr = (int*)realloc(ps->arr, 20 * sizeof(int));
			if (ptr == NULL)
			{
				//增容失败
				perror("main");
				return 1;
			}
			else
			{
				//增容成功
				ps->arr = ptr;
				int i = 0;
				for (i = 10; i < 20; i++)
				{
					ps->arr[i] = i;
					printf("%d ", ps->arr[i]);
				}
			}
		}
	}
	//释放
	//由于使用了malloc函数动态开辟了两份空间,应该先回收为int* arr开辟的动态内存空间,因为此动态内存空间的起始位置的地址为:ps->arr,要先找到该地址,才能释放该内存空间,,,如果先把为结构体动态开辟的
	//内存空间回收的话,再使用ps->arr就相当于非法访问了,所以要先回收为int* arr动态开辟的内存空间,然后再回收为结构体开辟的内存空间、
	//因为使用了两次malloc函数开辟内存空间,又因该函数要与free函数成对使用,所以后面要释放两次内存空间。
	free(ps->arr);
	ps->arr = NULL;
	free(ps);
	ps = NULL;
	//该方法的确能够实现出柔性数组的功能,但是比起来柔性数组的话,具有一定的缺点,比如:
	//1、该方法使用了两次malloc函数,就对应了两次free函数,比较容易出错、
	//2、当使用这些函数在堆区上开辟内存空间的时候,多次开辟的内存空间块之间会存在空隙,这些空隙就叫做内存碎片,这些内存碎片再次被利用的可能性
	     //比较低,使用这些函数在堆区上开辟空间越多,内存碎片就越多,那么内存的利用率就会降低,除此之外,多次开辟内存空间,效率也会降低、

	//局部性原理:
	//空间局部性:当在某一处使用内存的时候,接下来%80的内存使用都是在上一次内存使用的附近进行开辟空间,除了一些数据必须要放在比较远的内存空间上
	  
	return 0;
}

6.3 柔性数组的优势:

一、方便内存释放
       如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free 可以释放结构体,但是用户并
不知道这个结构体内的成员也需要 free ,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好 了,并返
回给用户一个结构体指针,用户做一次free 就可以把所有的内存也给释放掉, 一次开辟,一次释放、
二、有利于访问速度.
       连续的内存有益于提高访问速度,也有益于减少内存碎片。
关于动态开辟内存空间的知识就到此结束了,谢谢大家!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

脱缰的野驴、

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

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

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

打赏作者

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

抵扣说明:

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

余额充值