🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
### 内存中的数据 **程序归根到底还是数据,当执行程序时,数据加载到内存,程序结束后,数据从内存中释放。数据在内存中的存放共分为以下6种形式** - 程序代码区---存放程序编译后的可执行代码(二进制代码) - 全局区(静态区)---全局变量和静态变量的存储室放在一块的,初始化的全局变量和初始化的静态变量在同一区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,程序结束后由系统释放 - 常量区---常量就是存放在这里的,程序结束后由系统释放 - 堆区---一般由程序员分配和释放,如果程序员不释放,则出现结束时可能由操作系统回收(程序不正常结束则无法回收) - 栈区---由编译器自动分配且释放,该区域一般存放函数的参数、局部变量 - 寄存器---内存阶层的最顶端,一个有限容量的高速存放区,直接建立在中央处理器内部,用来暂存指令,数据和地址,一般用来保存栈顶指针、指令指针和现在正在被运行的指令。由于寄存器区其实是在中央处理器内部建立,而不是内存中,因此严格上讲,不能将其划分到内存中,只是它与内存的功能大致相同,都用来暂存数据,所以将其划分到这里。 ### 堆栈概念 静态内存分配 动态内存分配 * 栈内存 * 系统自动分配 * 系统自动销毁 * 连续的内存区域 * 向低地址扩展 * 大小固定(如果申请的内存空间超过栈的剩余空间,系统会提示栈溢出,因此不要指望栈能存储比较多的数据) * 栈上分配的内存称为静态内存 * 栈中的数据执行速度很快 > **栈顶的地址和栈的最大容量是由系统预先设定好的,因此栈的大小是固定的** * 静态内存分配 * 子函数执行完,子函数中的所有局部变量都会被销毁,内存释放,但内存地址不可能被销毁,只是地址上的值没了 * 堆内存 * 程序员手动分配 * java:new * c:malloc * 空间不连续(各块区域由链表将它们串联起来) * 大小取决于系统的虚拟内存 * C:程序员手动回收free * java:自动回收 * 堆上分配的内存称为动态内存 * 堆的上限是由系统中的有效的虚拟内存来定的,因此获取的空间较大,且获得空间的方式也比较灵活 * 堆需由程序员自己申请,申请时指明空间大小,同时堆的内存不连续,易产生碎片,数据执行速度慢 > **栈和堆各有优点,好多时候可以结合使用,比如存储较大数据时,可以将数据放到堆中,而将指向该数据的指针放到栈中,这样可以有效地提高程序的执行效率;一般来说,只要不是特大的数据,还是建议使用栈,如函数参数,返回值,局部变量,都存放到栈中,这样可以大大提高程序的运行速度** **想要数据既不容易被修改,又不会一直占据内存,同时还不局限在某一块特定区域使用,这时可以使用堆** **堆的使用比较灵活,可随时申请一个堆也可以释放掉一个堆,因此堆中的数据不会一直占据着内存,堆既可以在全局申请,也可以在局部申请,因此堆中数据不会只局限在某一块特定区域,为了保证数据的隐秘性,堆中的每个内存都是匿名的,不用指针,根本无法找到它,因此堆中数据不容易被修改。** **静态内存分配** ``` #include<stdio.h> #include<stdlib.h> /** 栈内存 系统统一分配统一回收 静态内存分配 栈内存大小固定的 内存地址是连续的 main()函数和getData()以及getData2()没有手动地申请内存,那么所占用的都是栈内存, */ int* getData(){ int array[] ={1,2,3,4,5}; printf("getData()中array地址是%#x\n",&array); return &array; } int* getData2(){ int array[] ={5,4,3,2,1}; printf("getData2()中array地址是%#x\n",&array); return &array; } main(){ int* pointer = getData();//getData()函数执行完,内存会被马上回收,执行getData2()时实际上还是用的getData()的内存 getData2();//通过控制台输出结果可知,pointer指的是同一块内存 printf("%d,%d,%d\n",*(pointer+0),*(pointer+1),*(pointer+2));//栈内存,系统统一分配统一回收,只能用一次, printf("%d,%d,%d\n",*(pointer+0),*(pointer+1),*(pointer+2));//再次打印时,内存被回收或者部分被回收,所以打印出来的值就是随机的 printf("pointer值为%#x\n",pointer); system("pause"); } ``` 输出结果 ``` getData()中array地址是0x60febc getData2()中array地址是0x60febc 5,4,3 6,6356652,3 pointer值为0x60febc ``` **动态内存分配** ``` #include<stdio.h> #include<stdlib.h> /** java new对象就会申请一块堆内存 c malloc memory allocation 内存分配 c的堆内存 程序员手动申请手动释放 malloc free 申请一块堆内存 动态内存分配 堆内存 不连续的 堆内存大小不固定 取决机器的状态 */ main(){ //malloc 接收的参数 申请内存大小 返回一个内存地址值 申请到的也是一块连续的内存空间 int* pointer = malloc(sizeof(int)*5); *(pointer+0) = 1; *(pointer+1) = 2; *(pointer+2) = 3; *(pointer+3) = 4; *(pointer+4) = 5; //C for 循环 循环的临时变量i 要先声明再使用 int i; for(i = 0;i<5;i++){ printf("第%d个元素的值= %d\n",i,*(pointer+i)); } free(pointer); printf("第一个元素的值%d\n",*(pointer+0)); system("pause"); } ``` 输出结果 ~~~ 第0个元素的值= 1 第1个元素的值= 2 第2个元素的值= 3 第3个元素的值= 4 第4个元素的值= 5 第一个元素的值0 ~~~ > malloc函数在stdlib.h中声明,该函数的作用是想系统申请指定字节的内存空间,假如给malloc函数传递4,则表示申请4个字节的内存空间,之所以用sizeof(int)代替4,是因为int在不同的编译环境下所占用的字节数是不一样的,malloc函数的返回值类型是void*,表示未确定类型的指针 **申请内存空间失败,对malloc函数返回值进行判断** ``` #include <stdio.h> #include <stdlib.h> //包含malloc函数的声明 #include <LIMITS.h> int main() { int *p=(int*)malloc(2147483648); if (p==NULL) printf("申请内存空间失败"); else { *p=4; printf("%d",*p); } return 0; } ``` > **由于计算机内存有限,可能会出现因内存不足而无法满足malloc函数请求的情况,这种情况下,malloc函数会返回null,该值被赋给指针后,则该指针就是一个空指针,空指针不会指向有效数据,因此在调用malloc函数申请内存空间时,一定要进行返回值的判断,而且调用malloc函数申请的空间是不连续的,因此可看做是堆** **释放指针指向的堆空间** ``` #include <stdio.h> #include <stdlib.h> //包含malloc函数和free函数的声明 int main() { int *p=(int*)malloc(sizeof(int)); *p=400; printf("%d\n",*p); free(p); printf("%d\n",*p); p=0;//当使用free函数释放p所指向的内存空间后,最好将该指针所保存的内存地址清零,以免重复操作该地址,导致出错 p=(int*)malloc(sizeof(int)); *p=6; printf("%d\n",*p); free(p);//再次将内存回收 return 0; } ``` 输出结果 ~~~ 400 0 6 ~~~ > **malloc函数申请的内存空间不会被系统自动释放,因此假如用户不去释放它,则该区域的内存将始终不能为其它数据所使用,为了释放malloc函数申请的内存空间,必需使用free函数** **使用free函数的注意事项** - free函数释放的是指针所指向的内存空间,由于不能将同一块内存连续释放两次,因此不能调用两次free函数来释放同一块内存 - free函数只会释放指针指向的内存空间,并不会释放指针所占用的内存,因此指针还存在,而且它仍然保持着原来的内存空间地址,如果再次使用该指针,会出现意想不到的情况 **内存泄漏** ``` #include <stdio.h> #include <stdlib.h> //包含malloc函数和free函数的声明 void create() { int *p=(int*)malloc(sizeof(int));//create函数中申请一块内存空间,并由p来保存该空间的地址, //指针p是个局部变量,因此当create函数调用完毕,指针p就不存在了,p指向的内存空间这也就再也找不到了, //造成内存丢失,一直到程序结束,p指向的内存空间方才由系统回收。因此假如使用完一块内存空间,则请立即使用free函数将其释放 } int main() { create(); return 0; } ``` > **并不是只有指针丢失才会造成内存泄漏,指针保存的地址也会造成内存泄漏;指针变量只能保存一个地址,如对它重新赋值,则表示以前的地址被覆盖,假如该地址的内存空间没有被释放,则将无法再次通过指针来访问它,因为此时的指针保存的是新的内存空间地址** **迷途指针** ``` #include <stdio.h> #include <stdlib.h> //包含malloc函数和free函数的声明 int main() { int *p=(int*)malloc(sizeof(int)); long *p1; *p=1; printf("将1赋给p指向的空间后,指针p读取到的值:\t\t%d\n",*p); free(p);//删除p指向的空间,这样p就成了一个迷途指针,因为它指向的空间已经不存在了,但是p仍然保存着原来的内存空间的地址, //这时如果再次尝试使用该指针,则将会出现意料不到的情况,为了避免出现这种情况,可以在free(p)之后将指针p赋值为0, //尽管说使空指针是非法的,容易使程序崩溃,但是宁愿程序崩溃也不愿调试起来困难 //p=0;//由于空指针指向的通常不是存在的内存空间,因此导致程序运行过后立即崩溃,从而使我们迅速察觉到是指针出了问题 printf("释放内存后,指针p读取到的值:\t\t\t%d\n",*p);//再次打印p的值发现成了随机产生的一个数而不是1 p1=(long*)malloc(sizeof(long)); printf("申请新内存块后,指针p保存的地址:\t\t%p\n",p); *p1=0; printf("指向新内存块的指针p1保存的地址:\t\t%p\n",p1); *p=2; printf("将2赋给p指向的空间后,指针p读取到的值:\t\t%d\n",*p); printf("将2赋给p指向的空间后,指针p1读取到的值:\t%ld\n",*p1); free(p1); return 0; } ``` #### 指令指针 **指令指针是在寄存器中存放的一种指针,它用来存放下一条待执行指令的地址。每条指令都有一个地址**