动态内存分配
1.常用函数
笔记
代码malloc(掌握)
#include<stdio.h>
#include<stdlib.h>
int main()
{//1.利用malloc函数申请一片连续的空间//需求:申请一片空间,要存储100个int类型的整数//返回这片空间的首地址,,所以可以用指针来记录int*p= malloc(100 * sizeof(int));//得考虑通用性//打印一下p所记录的内存地址printf("%p\n", p);//此时用malloc这个函数,申请出来的空间,他里面是没有默认值的,如果说在这个下面强行获取,这些数据是乱七八糟的数据printf("%d\n", *p);//这个是错误的写法//在后面直接*p解引用就可以了,现在是100个int,此刻打印出的就是第一个int的值//所以使用之前要先赋值//2.赋值for (int i = 0; i < 100; i++){//第一种赋值//*(p + i) = (i + 1) * 10;//等号右边相当于现在要赋的100个整数,10 20 30 40 50 60。。。//等号左边:第一次循环时,i是0,这个指针就不做任何偏移,再进行解引用,相当于把10赋值给了上面100个整数当中的第一个//第二次循环时,i=1,相当于就是把这个指针往后偏移了一个单位,那么一个单位是跟指针的 步长有关系的,// 往后一步4个字节,那指的就是第二个int类型的整数了,所以就把20赋值给了第二个整数//循环之后就表示100个整数全部已经复制完毕//第二种赋值//可以仿照数组的写法,上面这个空间也是连续的,内存的结构跟数组几乎一模一样,p[i] = (i + 1) * 10;//其实数组这种方式在底层,在真正运行时,他会把这个p和i进行解析,最终还是解析成p+i,再把这个整体进行解引用//所以在底层其实就是拿着p也就是那个指针往后偏移了几个单位,然后再解引用//i[p]这样也是可以的,就是以这种方括号的形式来进行书写的话,他的底层会进行解析的,// 把外面的和里面的相加,把这个整体再进行解引用}//3.遍历(把100个整数全部打印出来)for (int i = 0; i < 100; i++){//法一//printf("%d ", *(p + i));printf("%d ", p[i]);//法2}return 0;
}
Calloc(了解)
realloc(了解)
#include<stdio.h>
#include<stdlib.h>
int main()
{//1.利用malloc函数申请一片连续的空间//需求:申请一片空间,要存储100个int类型的整数//返回这片空间的首地址,,所以可以用指针来记录int*p= malloc(100 * sizeof(int));//得考虑通用性//int* p = calloc(10, sizeof(int));//打印一下p所记录的内存地址//printf("%p\n", p);//2.赋值for (int i = 0; i < 10; i++){//第一种赋值//*(p + i) = (i + 1) * 10;//等号右边相当于现在要赋的100个整数,10 20 30 40 50 60。。。//第二种赋值p[i] = (i + 1) * 10;}//4.扩容,20个int类型的整数int*pp=realloc(p, 20 * sizeof(int));//第一个参数,就是把p,也就是原来空间的这个指针给他传递过去,后面就是字节的多少//此时,这个函数也会返回一个内存地址,而且原来空间里面的数据他不会丢,他会把原来的数据一起的拷贝到新的空间当中//3.遍历(把100个整数全部打印出来)for (int i = 0; i < 20; i++){//printf("%d ", *(p + i));printf("%d ", p[i]);}//首先一开始是申请了一片连续的空间,存储10个int类型的整数,下面呢可以给他赋值10个int类型的整数,// 复制完之后,我把这个空间进行扩容,新的空间要存储20个int类型的数据,那么在扩容的同时,他会把原本的10个整数赋值过去//所以说下面我们在遍历的时候,会发现前面10个还是原来的 ,后面的没有赋值,所以就是一些乱七八糟的数据return 0;}
free(掌握)
不管是用哪种方式去申请内存的空间,那么这个空间如果说你不释放的话,空间在内存当中永远存在,如果说这样的空间越来越多,总有一天内存会放不下,所以说当这个空间不需要了,一定要随手把他给释放掉
2.malloc函数的细节点
1,malloc创建空间的单位是字节
2,malloc返回的是void类型的指针,没有步长的概念,也无法获取空间中的数据,需要强转3,malloc返回的仅仅是首地址,没有总大小,最好定义一个变量记录总大小 指的是元素的个数
4,malloc申请的空间不会自动消失,如果不能正确释放,会导致内存泄露
5,malloc申请的空间过多,会产生虚拟内存
6,malloc申请的空间没有初始化值,需要先赋值才能使用
7,free释放完空间之后,空间中数据叫做脏数据,可能被清空,可能被修改为其他值
8,calloc就是在malloc的基础上多一个初始化的动作
9,realloc修改之后的空间,地址值有可能发生变化,也有可能不会改变,但是原本的数据不会丢失10,realloc修改之后,无需释放原来的空间,函数底层会进行处理
#include <stdio.h>
#include<stdlib.h>
void method(int* p, int size);int main()
{//5,malloc申请的空间过多,会产生虚拟内存// // 虚拟:假的// 当申请的空间过多,因为每一个内存空间不会在刚申请的时候就立马使用// 所以c语言并不会立马就在内存中去开辟空间,而是什么时候存储数据了,才会真正的分配空间// 目的:为了提高内存的使用效率//表示单次申请空间的字节大小(1G)int number = 1024 * 1024 * 1024;//利用循环不断地申请空间//malloc 申请空间//如果申请空间成功,返回这个空间的首地址//如果申请失败,返回NULLint count=0;while (1){int *p = malloc(number);//每次申请一个G的内存空间,用指针来记录count++;if (p == NULL){printf("申请失败");break;}printf("内存%d申请成功%p\n", count, p);}return 0;}
#include<stdio.h>
#include<stdlib.h>
int main()
{//1.申请一片连续的空间存储10个int类型的整数int* p1 = malloc(10 * sizeof(int));int size = 10;printf("修改之前的内存地址为:%p\n", p1);//给这片空间赋值for (int i = 0; i < size; i++){*(p1 + i) = (i + 1) * 10;}//3.修改大小int* p2 = realloc(p1, 20 * sizeof(int));//p1,相当修改的就是这个空间 malloc(10 * sizeof(int));//这个空间要修改多大,即 20 * sizeof(int)表示修改之后要存储20个int 类型的整数printf("修改之后的内存地址为:%p\n", p2);size =20;//realloc修改之后,无须释放原来的空间,函数底层会进行处理//如果内存地址没变,底层在原来空间的后面接着申请的//如果内存地址变了,申请一个新的大的空间,把原来的数据拷贝到新的空间当中,再把原来的空间给free掉//4.遍历printf("遍历空间中的数据为:\n");for (int i = 0; i < size; i++){printf("%d ", *(p2 + i));//用p2进行遍历}printf("\n");return 0;}
3.c语言的内存结构
代码区:当程序在运行的时候,他会先把代码 加载到代码区,临时存储
栈:函数在调用的时候,会进栈执行,当函数里面所有的代码全部执行完了之后,就会从栈里面出去
而函数 里面定义的变量或者是数组其实也是在栈里面的,
静态区:但是如果说变量前面加上了static,或者把变量的定义写在函数外面了,这个时候就不在栈里面了,变成全局变量,
未初始化静态区:那么此时变量还没有赋值,就会在未初始化静态区
初始化静态区:如果说已经赋值了,就会在初始化静态区
常量区:如果说指针加双引号的形式定义的字符串,那么字符串的底层其实就是字符数组,而这个数组是放在常量区的,一旦放在这,就会有2个细节:一个是里面的内容不能修改,一个是会有一个复用机制
堆:只有用malloc,calloc,realloc这样3个函数申请出来的空间,才会在堆里面
4.变量数组在内存中的运行情况
首先,程序要运行的时候,他会把所有的代码都临时加载到代码区当中,进行存储,在代码区当中,他只是临时存储,不会去运行里面的代码,当程序开始运行的时候,编译器就会从上往下依次去检查代码,,看到main函数就会自动去调用他,这个时候main函数就会加载到栈里面,开始执行里面代码,而在main函数中,我一开始定义了2个变量,a和b,那么就在main函数里面开辟了2个小空间,分别存储数据10那么如果说在main函数当中下面的代码里面,要用到a和b,就会找这2个变量里面对应的值,变量存储的是什么,就使用什么,还有一个数组,所以在左边的main函数中,就定义了一个长度为3的数组,里面分别存储元素1,2,3,那么这3 个元素在内存当中,是连续不断的,现在在整个内存当中,是没有一个变量叫做arr的,arr现在表示的是数组这个整体,是这个大的空间,这也是为什么用sizeof去测量arr时会获取到整个数组占用多少字节的原因,只不过arr在参与计算的时候,他会退化为指向第一个元素的指针,当main函数里面的所有代码全部执行完毕了,整个时候main函数就会从栈里面出去,一旦函数从栈里面出去了,那么函数里面定义的所有的变量还有数组也会随之消失,函数里面所有变量和数组的生命周期都是跟函数有关的
5.全局变量和static变量在内存中运行情况
此时是把变量a定义在函数外面,此时这个变量A就叫做全局变量
在所有函数,都可以使用这个A,
6.字符串在内存中的运行情况
在main函数当中,第一行是用指指针加双引号的形式,去定义的字符串,只有这种形式定义的字符串,他才会把底层的字符数组放在常量区当中,如果表示用这种方式,底层的字符数组是放在栈里面,那么字符串里面的内容就可以随意发生修改,
所以说在一开始使用指针加双引号的时候,在一开始,会去检查常量区里面有没有abc,如果说没有,才会创建一个新的,然后把他的地址值赋值给main函数里面的指针str,所以说在下面我通过str去打印字符串中所有内容时,就会打印到这里的abc
可以创建一个新的字符串,叫做aaa,在创建aaa的时候,编译器也会在常量区中检查有没有aaa,如果说有,就不会创建新的了,会进行复用,但是现在没有,所以才会创建新的数组aaa,并把他的地址值赋值给左边的str,str所记录的地址就会发生变动,此时再次打印str就是新的aaa,
7.malloc函数在内存中的运行情况
如果说要完整的遍历堆里面的这个空间,最好就要用一个单独的变量,去记录他的长度才可以
用我们以前的方式定义的数组是在栈里面的,而用melloc定义的空间,是在堆里面的,而堆里面的空间比栈要大的多,如果说数组中的内容比较多,这个时候放到栈里,不太合适,
如果说你这个数组是在栈里面的,那么他的生命周期是跟函数相关的,函数一旦执行完毕了,里面的数组也会随之消失,如果在其他函数里面还想要用下面这个数组,其实是用不了的,因为函数都消失了,函数里面的东西也就没了,而使用melloc,则是在堆里面,只要不要free,那么他永远存在,一直到程序 的结束才消失