在程序编译的时候内存一般分为以下几个部分
栈区(stack):由编译器自动分配,先进后出,存放函数的参数值,局部变量的值等。
堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束可能由OS回收。oc一般通过
alloc
开辟空间,c/c++通过malloc
等方式分配,结束时候使用free释放,如果忘记释放,会导致分配的空间一致占着不放,导致内存泄漏。分配方式类似于链表。全局区/静态区(static):用来存放全局变量和静态变量。存在程序的整个运行期间,是由编译器分配和释放的。
常量区:存放常量(程序在运行的期间不能够被改变的量,整型常量,字符型常量,浮点常量,字符串常量等)。 空间是由系统管理的,生命周期,在程序运行期间一直存在(与程序的生命周期一样)
-
代码区:存放程序的代码,即CPU执行的机器指令,并且是只读的。
栈区
- 栈是系统数据结构,对应线程/进程是iOS。主线程栈大小是1MB,其他主线程是512KB唯一的。
- 栈区是先进后出
- 栈的地址空间在iOS中是以0X7开头
- 栈空间分静态分配 和动态分配两种。
静态分配是编译器完成的,比如自动变量(auto)的分配。
动态分配由alloca函数完成。
栈的动态分配无需释放(是自动的),也就没有释放函数。
为可移植的程序起见,栈的动态分配操作是不被鼓励的!
优点是快速高效,缺点时有限制,数据不灵活。[先进后出]
MAC只有8M
int main()
{
int *p1= alloca(4);
*p1 = 123;
printf("%d,%p,%p\n",*p1,p1, &p1);
return 0;
}
·················
123,0x7ffeefbff420,0x7ffeefbff430
·················
所以p1和p1指向的值也是放在栈中的
堆区
- 堆是向高地址扩展的数据结构
- 堆是不连续的内存区域,遵循先进先出(FIFO)原则。
- 堆的地址空间在iOS中是以0x6开头,其空间的分配总是动态的。
int main()
{
int *p1=malloc(4);//申请4字节的空间
*p1=123;// 该空间赋值123
printf("%p:%d\n",p1,*p1);
printf("%p\n",&p1);
free(p1);
return 0;
}
···················
0x10380f1c0:123
0x7ffeefbff430
···················
当需要访问堆中内存时,一般需要先通过对象读取到栈区的指针地址,然后通过指针地址访问堆区
堆区是由系统通过链表管理维护的,所有应用程序共享的一块内存空间。包括内存+虚拟内存(磁盘缓存)
程序运行时堆区的内部操作,以及引发内存泄漏的原因:
创建一个新的对象时,对象p指针存放在栈区,p将指向在堆区开辟的一块存储空间Person
在程序结束之前,p对象必须release,不然系统不知道释放堆区的Person内存。
如果p对象没有release,只是p=nil; 就是p指针指向了堆区地址为0的地方,那么原来的Person永远无法再次访问,而且也无法释放掉。
堆是所有程序共享的内存,当N个这样的内存得不到释放,堆区会被挤爆,程序立马瘫痪。这就是内存泄漏。
全局区/静态区
全局区是编译时分配的内存空间,在iOS中一般以0x1开头
static int a;
int c;
void test(void)
{
static int b=1;
b++;
printf("b:%p: %d\n", &b , b);
}
int main()
{
printf("a: %p: %d\n", &a , a);
printf("c: %p: %d\n", &c , c);
for(uint8_t i=0;i<5;i++)
{
test();
}
return 0;
}
···············
a: 0x100002014: 0
c: 0x100002018: 0
b:0x100002010: 2
b:0x100002010: 3
b:0x100002010: 4
b:0x100002010: 5
b:0x100002010: 6
Program ended with exit code: 0
···············
a是静态全局变量,b是静态局部变量,c是全局变量,他们都存放在静态区;a和c并未初始化,打印出来的都是0,说明比那一起自动把他们初始化为0;b在for循环中初始化了5次,但实际上b仅初始化一次,后面每次调用b都是上次的值,且b的地址是不变的,编译器只会为第一次初始化的b分配内存,后面4次初始化是无效的。
常量区
常量区是编译时分配的内存空间,在iOS中一般以0x1开头,在程序结束后由系统释放
char *p="hello“;
以这种方法初始化的字符串是常量字符串,所以不能修改。
int main(int argc, const char * argv[]) {
char* p ="abcdef";
printf("%p: %s\n", p , p);//打印指针p的地址和p指向的字符串内容
p="qwedma";
printf("%p: %s\n", p , p);//打印指针p的地址和p指向的字符串内容
p[0]='1'; //尝试把p指向的第一个字符q修改为1
printf("%p: %s\n", p , p);//打印指针p的地址和p指向的字符串内容
return 0;
}
···········
0x100000f9a: abcdef
0x100000fab: qwedma
(lldb)
···········
可以看到,p的值改变,地址也发生了改变。在尝试把p指向的第一个字符q修改为1时,系统崩溃了,常量区的内容不可以改变。
继续看定义
char p[]="hello“;在这里把p定义成一个字符数组
int main(int argc, const char * argv[]) {
char p[] ="abcdef";
printf("%p: %s\n", p , p);//打印指针p的地址和p指向的字符串内容
p[0]= '1'; //尝试把p指向的第一个字符q修改为1
printf("%p: %s\n", p , p);//打印指针p的地址和p指向的字符串内容
return 0;
}
···········
0x7ffeefbff489: abcdef
0x7ffeefbff489: 1bcdef
Program ended with exit code: 0
···········
这个p数组是存放在栈区的,然后再把字符串常量”abcd”拷贝到栈区的str数组内,那么此时的str是可以修改的。str是指向栈区的地址:0x0x7ffeefbff489,且指向的内容可以被修改,第一个字符a被修改为e。
代码区
- 代码区的内存是由系统控制的
- 代码区的存放 :程序中的函数编译后cpu指令
- 代码区的地址:函数的地址,程序的入口地址,程序的名字
- 函数的名称也是一个指针,可以通过查询函数名称所处的内存地址,查询函数存放的区域。
int main(int argc, const char * argv[]) {
printf("main:%p\n",main);//打印main函数的存放地址
return 0;
}
..........
main:0x100000eb0
..........
可以看到main函数确实存放在0x0x100000eb0这片地址区域,在代码区中。