概述
1. 内存管理简介
在计算机系统,特别是嵌入式系统中,内存资源是非常 有限的。尤其对于移动端开发者来说,硬件资源的限制使得其在程序设计中首要考虑的问题就是如何 有效地管理内存资源。
常见内存使用错误:
- 内存申请未成功,就进行使用
- 内存申请成功,但没有初始化
- 内存初始化成功,但越界访问
- 忘记释放内存或者释放一部分
内存管理不当的危害?
- 没有初始化,会造成内存出错
- 越界访问内存可能导致崩溃
- 忘记释放内存造成内存泄露
C语言的内存管理:
C语言为用户提供了相应内存管理的AP接口,如 malloc()
,free()
,new()
等函数,需要开发者手动管理。而java
、C#
则有自动内存回收机制,基本无需再对内存进行操作了。
2. 内存分类 栈区(stack)
由系统自动分配
堆区(heap)
在程序的执行过程中才能分配,由程序员决定
全局区(静态区)
静态区存放程序中所有的全局变量和静态变量
常量区
常量字符串就是放在这里的
代码段:
代码段(code segment/text segment)。通常是指用来存放程序执行代码的一块內存区域。代码区的指令中包括操作码和要操作的对象(或对象地址引用)。如果是立即数(即具体的数值,如5
)直接包含在代码中;如果是局部数据,将在栈区分配空间,然后引用该数据地址。
数据段:
数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。
BSS段:
BSS段(Block Started by Symbol)。指用来存放程序中未初始化的全局变量的一块内存区域。
BSS段本质上也属于数据段,都用来存放C程序中的全局变量。区别在于.data段中存放初始化为非零的全局变量,而把显式初始化为0或者并未显式初始化(C语言规定未显式初始化的全局变量值默认为0)
的全局变量存在BSS段。
3. 栈区(stack)
由编译器 自动分配释放,存放函数的参数值、局部变量的值等,是一种先进后出的内存结构。
哪些是分配在栈空间?
- 局部变量的值存放在栈上
- 在函数体中定义的变量通常是在栈上
函数栈分配:
在函数调用时,第一个进栈的是主函数中函数调用后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。
栈内存什么时候回收?
栈内存的分配和释放也由编译器在函数进入和退出时插入指令自动完成,生命周期和函数、局部变量一样。
栈空间的大小:
在 Windows下,栈是向低地址扩展的数据结构,是块连续的内存的区域。栈空间一般较小,栈大小与编译器有关。默认情况下,visual studio 2010
的栈大小为1M
。但在平时应用程序中,由于函数会使用栈结果,所以只能用略小于1M大小的栈如果申请的空间超过栈的剩余空间时,将提示Stack overflow。
示例代码:
#include<stdio.h> struct A {}; class B {}; void fun(int a , int b) //参数a,b在栈上, 在函数体结束的时候,栈内存释放 { int c;//局部变量,在栈上, 在函数体结束的时候,栈内存释放 } int main() { int a = 10;//局部变量在栈上, 在main函数结束的时候,栈内存释放 char b[] = "hello";//数组变量也在栈上, 在main函数结束的时候,栈内存释放 char *c = NULL;//在栈上, 在main函数结束的时候,栈内存释放 { A d;//结构体变量, 在栈上 B e;//类对象在栈上 } //d,e 在离开这个{}时,栈内存销毁释放 //测试栈的大小 //char buf[1024 * 1024] = { 'A' };//1M时崩溃了 char buf[1000* 1024] = { 'A' };//栈空间略小于1M //经过编译期设置为5M之后,栈空间变大了 char buf[49 * 1024 * 1024 / 10] = { 'A' };//栈空间略小于5M printf("%d" , sizeof(buf) ); return 0; }
4. 堆区(heap)
需程序员自己申请,并可在运行时指定空间大小,并由程序员手动进行释放,容易产生 memory leak
。
哪些是分配在堆空间?
调用 malloc
,realloc
,calloc
函数
//分配得来得10*4字节的区域在堆区 p1 = (char*)malloc(10*sizeof(int));
堆空间需要手动释放:
堆是由 malloc()
等函数分配的内存块,内存释放由程序员调用free()
函数手动释放
堆空间的大小:
堆空间一般较大,与64位/32位,编译器有关,受限于计算机系统中有效的虚拟内存;理论上32位系统堆内存可以达到4G的空间,实际上2G以内,64位128G以内(虚拟内存16TB)
示例代码:
#include<stdio.h> #include<stdlib.h> int main() { //手动分配、这里就是分配了堆内存 int *p = (int*)malloc(10 * sizeof(int )); //手动释放 free(p); int MB = 0; while (malloc(1024 * 1024))//每次分配1M { MB++; } printf("分配了 %d MB n", MB); return 0; }
栈与堆的区别:
类型 | 分配释放 | 大小 | 是否连续 | 申请效率 |
---|---|---|---|---|
栈区 | 由编译器自动分配释放 | 较小 | 一块连续的内存区域 | 由系统自动分配,速度快 |
堆区 | 由程序员分配释放 | 较大 | 堆是向高地址扩展的数据结构,是不连续的内存区域 | 速度慢,容易产生内存碎片 |
5. 全局区(静态区)
全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在块区域。
哪些是分配在全局静态区?
- 全局变量
- static静态变量
全局静态区何时释放?
全局变量、静态变量在整个程序运行的生存期都存在,所以在程序结束时才释放
示例代码:
#include<stdio.h> //储存在全局静态区 int a; //全局变量,未初始化 short b = 10; //全局变量,已赋值 char *c = NULL; //全局变量,已赋值 static int f = 200; //静态变量 int main() { static int d = 100; static int e = 200; printf("%pn", &a); printf("%pn", &b); printf("%pn", &c); printf("%pn", &d); printf("%pn", &e); printf("%pn", &f); }
6. 常量区
字符串常量是放在常量区,当你初始化赋值的时候,这些常量就先在常量区开辟一段空间,保存此常量,以后相同的常量就都使用一个地址。
示例代码:
#include<stdio.h> //“AAA”是字符串常量,存放在常量区 char *p = "AAA"; int main() { //p1是局部变量,在栈上, “AAA”是字符串常量,存放在常量区 char *p1 = "AAA"; //p2是局部变量,在栈上,“AAA”不是字符串常量,她只是一种初始化的写法 char p2[]= "AAA"; //p3是局部变量,在栈上, “AAA”是字符串常量,存放在常量区 char *p3 = "AAA"; //p4是局部变量,在栈上, “AAB”是字符串常量,存放在常量区 char *p4 = "AAB"; printf("%pn", p); printf("%pn", p1); printf("%pn", p2); printf("%pn", p3); printf("%pn", p4); }
7. malloc、calloc、realloc函数
三个函数的作用?
它们都能分配堆內存、成功返回内存的首地址,失败就返回NULL
。
malloc
函数:
void *malloc( size_t size );
该函数将在堆上分配一个 size
byte大小的内存。不对内存进行初始化,所以新内存其值将是随机的。
calloc
函数:
void *calloc( size_t number, size_t size );
该函数功能与 malloc
相同,它将分配count
个size
大小的内存,自动初始化该内存空间为零。
realloc
函数:
void *realloc( void *memblock, size_t size );
该函数将ptr
内存大小增大或减小到newsize
。
realloc
函数返回的两种情况:
- 如果当前连续内存块足够
realloc
的话,只是将p1所指向的空间扩大,并返回p1的指针地址。 - 如果当前连续内存块不够长度,再找一个足够长的地方,分配一块新的内存p2,并将p1指向的内容Copy到p2,并释放p1指向的旧内存,然后返回p2。
示例代码:
#include<stdio.h> #include<stdlib.h> int main() { //malloc ,参数是字节数 , 并且这块内存空间的值是随机的 int *p = (int *)malloc(5 * sizeof(int)); p[0] = 123; for (int i = 0; i < 5; ++i) { printf("%d ", p[i]); //后面4个值随机 } printf("n------------------------------------------------------------n " ); //calloc,参数两个, 自动将内存空间初始化为0 int *p2 = (int *)calloc(5, sizeof(int)); p2[4] = 123; for (int i = 0; i < 5; ++i) { printf("%d ", p2[i]); } printf("n------------------------------------------------------------n "); //realloc ,可以调整内存空间的大小 ,并且拷贝原来的内容(调大,或者 缩小) //int *p3 =(int *) realloc(p, 6* sizeof(int));//调大一点点,两个地址相同 //int *p3 = (int *)realloc(p, 2 * sizeof(int));//缩小,两个地址相同 int *p3 = (int *)realloc(p, 100 * sizeof(int));//调很大,两个地址不同 ,释放原来的内存空间 for (int i = 0; i <2; ++i) { printf("%d ", p3[i]); } printf("np地址: %p , p3的地址: %p ", p, p3); return 0; }
8. strcpy、memcpy、memmove函数
头文件:
#include <string.h>
strcpy
函数
char *strcpy( char *strDestination, const char *strSource );
把src
所指由