内存管理主要掌握生命周期和内存布局,如果变量没有释放就可以用,释放了就不能用了。
一.作用域和声明周期
-
普通的局部变量
- 在{}内部定义的变量就是局部变量。
- 只有执行到定义变量的这个语句,系统才会给这个变量分配空间。
- 当离开{}时自动释放。
- 作用域在当前{}内。
- 加不加auto关键字都是一样的。
- 不初始化则值为随机数。
-
static局部变量
- 在{}内部定义的变量就是局部变量。
- 在编译阶段分配空间。
- 离开{}不会释放,程序结束才释放。
- 作用域在当前{}内。
- 不初始化则值为零。
- 初始化语句只会执行一次,但可以赋值多次。
- 只能用常量初始化。
-
普通全局变量
-
在函数外面定义的为全局变量。
-
如果使用变量前找不到定义,需要声明。
#include <stdio.h> int main() { extern int a; printf("%d\n",a); } int a = 10;
-
全局变量不初始化则值为零。
-
声明只针对普通全局变量。
-
所有文件中,普通全局变量只能定义一次,可以声明多次。
-
全局变量在编译阶段分配空间,程序结束释放。
-
多文件
//main.c int main() { extern void test(); //也可以把声明写到头文件里,头文件中不能放定义,多个文件包含会重复定义 //#program once表示一个文件包含n次头文件只有一次有效 test(); } //test.c void test() { printf("hello\n"); }
-
-
static全局变量
- 在函数外面定义的为全局变量。
- static全局变量只在当前文件有效,同名static全局变量可以在多个文件定义。
- 全局变量不初始化则值为零。
- 全局变量在编译阶段分配空间,程序结束释放。
二.内存布局
实际上的内存分段远不止这几个,这里拣重点说。
-
在程序没有执行前,有几个内存分区已经确定,但只有程序运行时才会加载内存,linux中可以使用
size *.out
查看。- text(代码区):只读,放函数。
- data(全局初始化数据区/静态数据区):初始化的数据,全局变量,static变量,常量(只读)。
- bss(未初始化数据区):定义但未初始化的数据,全局变量,static变量。
-
当程序运行时,加载内存,首先前面确定的内存分区(text,data,bss)先加载,然后额外加载两个区:
- stack(栈区):普通局部变量,自动管理内存,先进后出。
- heap(堆区):手动申请空间,手动释放,整个程序结束,系统也会自动回收,如果没有手动释放,程序也没有结束,这个堆区空间不会自动释放。
-
存储类型总结
类型 作用域 生命周期 存储位置 auto变量 {}内 当前函数 stack static局部变量 {}内 整个程序运行期 data/bss extern变量 整个程序 整个程序运行期 data/bss static全局变量 当前文件 整个程序运行期 data/bss extern函数 整个程序 整个程序运行期 text static函数 当前文件 整个程序运行期 text register变量 {}内 当前函数 运行时存储在cpu寄存器
| 常量 | 当前文件 | 整个程序运行期 | data |
-
栈越界
Linux中可以用
ulimit -a
查看栈区多大#include <stdio.h> int main() { int a[100000000000000] = {0}; //很显然栈区存不下,越界导致段错误 return 0; }
-
内存操作函数
-
#include <stdio.h> #include <string.h> void *memset(void *s,int c,size_t n); /* 功能:将s的内存区的前n个字节以参数c填入 参数: s:需要操作内存s的首地址 c:填充的字符,类型虽然是int,但必须是unsigned char,范围0-2555 n:指定需要设置的大小 返回值:s的首地址 */ int main() { int a; //主要就是用来清零 memset(&a,0,sizeof(a)); printf("a = %d\n",a); //"a = 0" //中间参数虽然是整形,但是以字符处理 //每个字节都存的97,所以只能char单字节的读 memset(&a,97,sizeof(a)); printf("a = %c\n",a); //"a = a" int b[10]; memset(b,0,sizeof(b)); memset(b,0,10 * sizeof(int)); return 0; }
-
#include <stdio.h> #include <string.h> void *memcpy(void *dest,const void *src,size_t n); int main() { char p[] = "hello\0world"; char buf[100]; printf("sizeof(p) = %d\n",sizeof(p)); strncpy(buf,p,sizeof(p)); printf("buf1 = %s\n",buf); printf("buf2 = %s\n",buf + strlen("hello") + 1); /* sizeof(p) = 12 buf1 = hello buf2 = */ memset(buf,0,sizeof(buf)); memcpy(buf,p,sizeof(p)); printf("buf1 = %s\n",buf); printf("buf2 = %s\n",buf + strlen("hello") + 1); /* buf1 = hello buf2 = world */ return 0; }
-
#include <string.h> int main() { int a[10] = {1,2,3,4,5,6,7,8,9,10}; /* 使用memcpy()最好别出现内存重叠,如果出现最好使用memmove() memcpy(&a[2],a,5*sizeof(int)); */ memmove(&a[2],a,5*sizeof(int)); return 0; }
-
#include <string.h> int main() { int a[5] = {1,2,3,4,5}; int b[5] = {1,2,3,4,6}; int flag = memcmp(a,b,5*sizeof(int)); /* flag < 0: a < b; flag > 0: a > b; flag = 0: a = b; */ return 0; }
-
-
堆区内存分配和释放
指针指向堆区空间
#include <stdlib.h> #include <stdio.h> int main() { int *p = (int*)malloc(sizeof(int)); //malloc分配堆区空间,成功返回void*,需要转int*,失败返回NULL *p = 10; printf("%d\n",*p); if(p != NULL) { free(p); //手动释放p指向的堆区空间,同一块内存只能释放一次,释放完就不能用了 p = NULL; //p的值还是原先的,需要设置为NULL } return 0; }
上例中不手动释放程序结束也会回收内存,但如果是服务器上的程序是不会结束的,如果只分配不释放就会造成
内存泄露:动态分配了内存,不释放
,如果windows,andriod长期不关机,就会很卡,重启就好了也是这个原因。内存污染:非法操作内存,就是野指针。