美文网首页
C语言内存

C语言内存

作者: YanZi_33 | 来源:发表于2020-12-18 16:47 被阅读0次

    在阐述C语言内存布局之前,首先我们来介绍一下C语言变量的类型;在C语言中的变量类型有自动变量,全局变量,静态变量以及常量.

    自动变量

    自动变量又称为局部变量,默认用关键字auto修饰,但auto关键字通常会省略;一般在函数内部定义的变量都是局部变量,其作用域仅限于函数体内部,离开函数体就会被系统自动回收销毁.

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            //均为局部变量
            auto int a = 100;
            int b = 200;
            int c = 300;
        }
        return 0;
    }
    

    全局变量

    一般在函数体外部定义的变量称之为全局变量,其作用域是整个应用程序.

    //全局变量
    int m = 1000;
    int n = 2000;
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            //均为局部变量
            auto int a = 100;
            int b = 200;
            int c = 300;
        }
        return 0;
    }
    

    静态变量

    • 静态局部变量: 在函数体内部,用static关键字修饰的变量称之为静态局部变量,其作用域仅限于函数体内部,外界不能访问,但其生命周期与整个应用程序相同.
    • 静态全局变量: 在函数体外部,用static关键字修饰的变量称之为静态全局变量,其作用域是全局的,其生命周期与整个应用程序相同.
    void Test(void);
    void Test()
    {
        //静态局部变量
        static int k = 3;
        //局部变量
        int m = 0;
        k++;
        m++;
        printf(" k = %d -- m = %d\n",k,m);
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Test();
            Test();
            Test();
        }
        return 0;
    }
    
    Snip20201218_28.png

    Test函数执行了三次,但静态局部变量的值在依次递增,其赋值操作好像失效了?

    静态局部变量是在编译时赋初始值,并且只赋一次初始值,在以后每次调用函数时,只是使用上一次函数被调用结束时变量的值.

    //静态全局变量
    static int a = 200;
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            printf(" a = %d\n",a);
            a = 500;
            a++;
            printf(" a = %d\n",a);
        }
        return 0;
    }
    
    Snip20201218_29.png

    静态全局变量,在应用程序运行的过程中可以被赋值多次.

    常量

    • 常量是固定值,在程序执行期间不会改变。这些固定的值,又叫做字面量
    • 常量可以是任何的基本数据类型,比如整数常量、浮点常量、字符常量,或字符串字面值,也有枚举常量。
    • 常量就像是常规的变量,只不过常量的值在定义后不能进行修改。

    整型常量

    12,134,1234;
    

    浮点型常量

    12.34,15.56;
    

    字符型常量

    'a','b','c';
    

    字符串常量

    "abcd","qwerty";
    
    常量的定义
    • 使用 #define 预处理命令;
    • 使用 const 关键字
    #define kScreenW 320
    #define kScreenH 616
    
    const int a = 500;
    const float b = 12.23;
    

    内存分区与内存布局

    在C语言中内存分为五个不同的区域,从高地址到低地址(内存地址编号从高到低)依次为栈区堆区静态全局区常量区代码区

    栈区
    • 主要存放自动变量,局部变量;
    • 栈区内存的分配与回收由系统自动管理,无需开发人员手动管理.
    堆区
    • 通过C语言函数malloc,申请分配堆区内存,malloc函数返回一个指针;
    • malloc函数申请的堆内存,回收释放调用free函数,堆内存是由开发人员手动管理的.
    静态全局区
    • 静态全局区由系统控制管理;
    • 静态全局区的内存, 一旦分配, 就一直占用, 直到程序结束.
    常量区
    • 常量区由系统控制管理;
    • 常量区的内容只能读不能修改;
    代码区
    • 代码区由系统控制管理,
    • 代码区存放数据为: 程序中的函数编译后的指令.
    //全局变量
    int a = 100;
    //静态全局变量
    static int b = 200;
    
    void MyFunction(int);
    void MyFunction(int a)
    {
        printf(" a = %d",a);
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            //栈区
            int c = 100;
            NSLog(@" 栈区 -- 局部变量 = %p",&c);
            int m = 666;
            NSLog(@" 栈区 -- 局部变量 = %p",&m);
            int k = 777;
            NSLog(@" 栈区 -- 局部变量 = %p",&k);
    
            //堆区
            int *p = malloc(10);
            NSLog(@" 堆区 = %p",p);
    
            //全局/静态变量区
            //静态局部变量
            static int d = 500;
            NSLog(@" 全局变量 = %p",&a);
            NSLog(@" 静态全局变量 = %p",&b);
            NSLog(@" 静态局部变量 = %p",&d);
    
            //常量区
            char *p1 = "iOS";
            NSLog(@" 常量区 = %p",p1);
    
            //代码区
            NSLog(@" 代码区 = %p",MyFunction);
        }
        return 0;
    }
    
    Snip20201218_30.png

    内存布局中栈区与堆区的增长方向

    • 栈区内存的增长方向从高地址 --> 低地址;
    • 堆区内存的增长方向从低地址 --> 高地址.
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            //栈区
            int c = 100;
            NSLog(@" 栈区 -- 局部变量 = %p",&c);
            int m = 666;
            NSLog(@" 栈区 -- 局部变量 = %p",&m);
            int k = 777;
            NSLog(@" 栈区 -- 局部变量 = %p",&k);
            
           //堆区
            int *p1 = malloc(10);
            NSLog(@" 堆区 -- %p",p1);
            int *p2 = malloc(10);
            NSLog(@" 堆区 -- %p",p2);
            int *p3 = malloc(10);
            NSLog(@" 堆区 -- %p",p3);
        }
        return 0;
    }
    
    Snip20201218_31.png

    下图对最上面的知识点进行一个总结:


    Snip20201218_33.png

    更深层次的理解堆栈内存

    • 为什么栈向下增长?

    这样设计可以使得堆和栈能够充分利用空闲的地址空间。如果栈向上涨的话,我们就必须得指定栈和堆的一个严格分界线,但这个分界线怎么确定呢?平均分?但是有的程序使用的堆空间比较多,而有的程序使用的栈空间比较多。所以就可能出现这种情况:一个程序因为栈溢出而崩溃的时候,其实它还有大量闲置的堆空间,但是我们却无法使用这些闲置的堆空间。所以最好的办法就是让堆和栈一个向上涨,一个向下涨,这样它们就可以最大程度地共用这块剩余的地址空间,达到利用率的最大化!!

    • 为什么要把堆和栈分开?
    • 从软件设计的角度来看,栈代表了处理逻辑,而堆代表了数据。
      这样分开,使得处理逻辑更为清晰。分而治之的思想。这种隔离、模块化的思想在软件设计的方方面面都有体现。
    • 堆与栈的分离,使得堆中的内容可以被多个栈共享(也可以理解为多个线程访问同一个对象)。这种共享的收益是很多的, 一方面这种共享提供了一种有效的数据交互方式(如:共享内存),另一方面,堆中的共享常量和缓存可以被所有栈访问,节省了空间。
    • 栈因为运行时的需要,比如保存系统运行的上下文,需要进行地址段的划分。由于栈只能向上增长,因此就会限制住栈存储内容的能力。而堆不同,堆中的对象是可以根据需要动态增长的,因此栈和堆的拆分,使得动态增长成为可能,相应栈中只需记录堆中的一个地址即可。
    • 面向对象就是堆和栈的完美结合。其实,面向对象方式的程序与以前结构化的程序在执行上没有任何区别。但是,面向对象的引入,使得对待问题的思考方式发生了改变,而更接近于自然方式的思考。当我们把对象拆开,你会发现,对象的属性其实就是数据,存放在堆中;而对象的行为(方法),就是运行逻辑,放在栈中。我们在编写对象的时候,其实即编写了数据结构,也编写的处理数据的逻辑。不得不承认,面向对象的设计,确实很美.

    相关文章

      网友评论

          本文标题:C语言内存

          本文链接:https://www.haomeiwen.com/subject/gcdxnktx.html