十八、内存的五大区

在程序编译的时候内存一般分为以下几个部分

  • 栈区(stack):由编译器自动分配,先进后出,存放函数的参数值,局部变量的值等。

  • 堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束可能由OS回收。oc一般通过alloc开辟空间,c/c++通过malloc等方式分配,结束时候使用free释放,如果忘记释放,会导致分配的空间一致占着不放,导致内存泄漏。分配方式类似于链表。

  • 全局区/静态区(static):用来存放全局变量和静态变量。存在程序的整个运行期间,是由编译器分配和释放的。

  • 常量区:存放常量(程序在运行的期间不能够被改变的量,整型常量,字符型常量,浮点常量,字符串常量等)。 空间是由系统管理的,生命周期,在程序运行期间一直存在(与程序的生命周期一样)

  • 代码区:存放程序的代码,即CPU执行的机器指令,并且是只读的。

    内存五大区.png

栈区

  • 栈是系统数据结构,对应线程/进程是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这片地址区域,在代码区中。

你可能感兴趣的:(十八、内存的五大区)