zoukankan      html  css  js  c++  java
  • 变量在内存的位置

    1、首先,讲下 “堆 heap” 和 “栈 stack” 的区别:

        一个由 c/c++编译过的程序占用的内存分为一下几个部分

        (1)、栈区 stack :由编译器自动分配释放,存放函数的参数值,局部变量的值等。这个栈的操作方式类似于数据结构中的栈。

        (2)、堆区 heap :一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收,注意它与数据结构中的堆是两回事,分配方式类似于链表。

        (3)、*全局区(静态区)static*** : 全局变量和静态变量的存储是放在一块的。初始化的全局变量和静态变量在一块区域,未初始化的全局变量和静态变量又放在相邻的另一块区域中。程序结束后由系统释放。

        (4)、*文字常量区* : 常量字符串放在这里。程序结束后由系统释放。

        (5)、程序代码区 : 存放函数体的2进制代码。

    根据变量的位置可以分为全局变量和局部变量

    根据变量的静态属性可以分为静态变量和非静态变量。

    根据变量的const属性可以分为const变量和非const变量。

    针对上面的几种变量分类,变量的初始化分为以下几种:

    全局变量和局部变量的不同主要体现在变量的作用范围上,全局变量的初始化分为两种,非类对象的变量的初始话发生在函数的编译阶段,如果我们没有显示的初始化,编译器会默认初始化,类类型的全局变量的初始化发生在程序运行阶段的Main函数之前。对于局部变量,不会执行默认初始化,因此在使用局部变量之前必须先进行变量的初始化。

    静态变量和非静态变量的初始化:

    静态变量的分类:静态变量根据其位置可以分为三种:全局静态变量、定义在函数中的静态变量以及定义在类中的静态变量。

    静态变量的初始化:编译器要求不同,有的要求必须主动完成初始化,有的编译器会完成默认初始化,但是值不确定。所以,在使用静态变量的时候,我们一般要求必须在定义的同时完成初始化。对于g++编译器,如果静态变量是全局或者函数的局部变量,都会完成默认初始化。但是如果类包含静态数据成员,C++要求这个静态数据成员仅可以在类内部完成声明,然后在类外,且任何函数之外,完成静态成员的初始化,初始化可以赋初始值,也可以不赋值,不赋值会默认初始化。

    初始化的形式如下:
    int A::i = 1;

    如果试图在类内初始化,编译器会报错:ISO C++ forbids in-class initialization of non-const static member ‘A::k’ 。

    如果在函数内(比如main函数)试图初始化, 编译器同样会报错:qualified-id in declaration before ‘=’ token

    如果不初始化直接使用,由于未分配内存,还是会报错:undefined reference to `A::k’

    报错愿意分析: 由于类的静态数据成员所有类共有,所以,类在分配内存的时候并不会主动分配这个静态数据成员的内存,因此要求我们必须主动要求分配内存。必须在类外完成初始化的原因是因为,这个静态数据成员保存在数据段(全局区),如果在函数内初始化,意味着要求编译器在栈区委这个变量分配内存,这也是错误的。

    但是这中情况有一个例外,那就是如果类的静态数据成员是const, 那么 这个变量必须在类内完成初始化。

    对于全局变量,如果程序仅由一个源文件构成,那么全局静态变量类同于全局变量,在编辑阶段进行初始化。如果程序由多个源文件构成,那么全局静态变量仅对所在的源文件有效,而全局变量在整个程序中有效。

    定义在函数中的静态变量:定义在函数中的静态变量的作用是变量值的保持,对于初始化而言,函数中的静态变量仅在定义时进行初始化,且在整个程序的运行中,函数中的静态变量只初始化一次。

    类中的静态成员变量:类的静态成员存在于任何对象之外,对象中不包含任何与静态数据成员有关的数据,因此,静态数据成员不是由构造函数进行初始化的,类的静态数据成员的初始化过程必须在类的定义之外,相对的,类的非静态数据的初始化发生在类的构造函数中。

    const对象和非const对象的初始化:

    const对象必须在定义的时候进行初始化,引用的本质是一个const指针,因此引用也必须在定义的时候进行初始化。

    各种变量的存储位置:

    常规的变量(非全局,非静态)保存在栈上。

    动态数据等显示分配的内存在堆上。

    全局变量和静态变量保存在数据段(全局区)

    const全局变量存放于只读数据段,在第一次使用时为其分配内存。


    2、例子程序 :  这是一个前辈写的,非常详细

       int a = 0; 全局初始化区 
        char *p1; 全局未初始化区 
     
        main() 
        { 
            int b;// 栈 
            char s[] = "abc"; //栈 
            char *p2; //栈 
            char *p3 = "123456"; 123456";//在常量区,p3在栈上。 
            static int c =0; //全局(静态)初始化区 
            p1 = (char *)malloc(10); 
            p2 = (char *)malloc(20); 
            //分配得来得10和20字节的区域就在堆区。 
            strcpy(p1, "123456"); //123456放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
        } 
    
    

    BSS引入

    内存分配区域
    32位操作系统下为:1G内核态,3G用户态

    BSS段 :通常是指用来存放程序中 未初始化的全局变量、静态变量(全局变量未初始化时默认为0)的一块内存区域

    数据段 :通常是指用来存放程序中 初始化后的全局变量和静态变量

    代码段 :通常是指用来存放程序中 代码和常量

    堆 :通常是指用来存放程序中 进程运行时被动态分配的内存段 ( 动态分配:malloc / new,者动态释放:free / delete)

    栈 :通常是指用来存放程序中 用户临时创建的局部变量、函数形参、数组(局部变量未初始化则默认为垃圾值)也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除 以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进后出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。它是由操作系统分配的,内存的申请与回收都由OS管理。

    堆(heap)和栈(stack)的区别
    1、申请方式

    栈: 由系统自动分配。例如,声明在函数中一个局部变量int b;系统自动在栈中为b开辟空间

    堆: 需要程序员自己申请,并指明大小( 动态分配:malloc / new,者动态释放:free / delete)

    2、申请后系统的响应

    栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。

    堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,
    会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

    3、申请大小的限制

    栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。

    堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连
    续的,
    而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
    (4)申请效率的比较:

    栈:由系统自动分配,速度较快。但程序员是无法控制的。

    堆:是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
    另外,在WINDOWS下,最好的方式是用Virtual Alloc分配内存,他不是在堆,也不是在栈,而是直接在进程的地址空间中保留一块内存,虽然用起来最不方便。但是速度快,也最灵活。

    (5)堆和栈中的存储内容

    栈:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。
    当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。

    堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。

    (6)存取效率的比较

    BSS段的大小记录在哪里? -》 关于BSS段的大小

    bss段的大小,记录在段表里,记录的是所有未初始化变量总共的大小,bss段只在段表里有个记录,但实际并不存在这个段.,每个未初始化的变量的大小放在了符号表里。

    更详尽的例子:

    
    #include <iostream>
    using namespace std;
     
    int a = 0;//初始化的全局变量:保存在数据段
    char *p1;//未初始化的全局变量:保存在BSS段
     
    int main()
    {
        int b;//未初始化的局部变量:保存在栈上
        char s[] = "abc";//"abc"为字符串常量保存在常量区;数组保存在栈上,
        并将常量区的"abc"复制到该数组中。这个数组可以随意修改而不会有任何隐患,
        而"123"这个字符串依然会保留在静态区中。
     
        char *p2;//p2保存在栈上
        char *p3 = "123456";//p3保存在栈上,"123456"保存在data区的read-only部分
        //注意:如果令p3[1] = 9; 则程序崩溃,指针可以访问但不允许改变常量区的内容
        //声明了一个指针p3并指向"123456"在静态区中的地址,事实上,p3应该声明为
        char const *,以免可以通过p3[i]='
    '这一类的语法去修改这个字符串的内容。如果这样
        做了,在支持“常量区”的系统中可能会导致异常,在“合并相同字符串”的编译方法下会导致其它
        地方的字符串常量古怪地发生变化。
     
        static int c = 0;//初始化的静态局部变量:保存在数据区(数据段)
        p1 = (char *)malloc(sizeof(char) * 10);//分配的10字节区域保存在堆上
        p2 = (char *)malloc(sizeof(char) * 20);//分配的20字节区域保存在堆上
     
        strcpy(p1, "123456");
        //"123456"放在常量区,编译器可能会将它与p3所指向"123456"优化成一个地方
     
        return 0;
    }
    
    

    参考来源:

    C/C++中 变量的存储位置C语言中局部变量和全局变量_等在内存中的存放位置

    关于C++中的各种变量以及存储位置总结

  • 相关阅读:
    Win10 UWP Tile Generator
    Win10 BackgroundTask
    UWP Tiles
    UWP Ad
    Win10 build package error collections
    Win10 八步打通 Nuget 发布打包
    Win10 UI入门 pivot multiable DataTemplate
    Win10 UI入门 导航滑动条 求UWP工作
    UWP Control Toolkit Collections 求UWP工作
    Win10 UI入门 SliderRectangle
  • 原文地址:https://www.cnblogs.com/huckleberry/p/13795974.html
Copyright © 2011-2022 走看看