zoukankan      html  css  js  c++  java
  • 编译器对内存的分配

    在正式开始今天的博文之前,先看一段代码,思考这段代码有可能出现的情况:

    int main() 
    {
            int  j, b[10];
              for(j = 1; j <= 10; ++j){
                      b[j] = 0;
              }
    }

    看完这段代码后,如果觉得这段代码没有什么坑,那不妨注意一下j的范围是从1-10,而b[10]会导致b数组越界哦。思考到结果了吗?先来看看一些内存知识吧。

    我们在C/C++中学的变量分三种:局部变量、全局变量(静态变量和全局变量)、堆变量

    局部变量  由程序员定义普通变量时编译器在内存中的栈空间为其分配一段内存,如:

    int b[10], j;

    全局变量  由编译器在内存中的静态存储区分配空间,如:

    int x, y; // 全局

    int main(){

      static int m, n; // 静态

    }

    堆变量   由程序员用malloc或new在堆上申请一段内存空间,如:

    int *a = new int[10], *i = new int; // 动态内存分配

    可见编译器通过将内存逻辑划分为不同区段来提供程序员访问内存的权限,而每段空间都有各自的大小,一旦程序员在使用过程中将该大小哦耗尽,就会出现内存不足错误,比如典型的Stack OverFlow 

    了解了变量的存储后,再来思考一个问题,当我在同一个变量区定义几个不同变量时,他们在内存中是如何排列的?

    比如我定义两个局部变量

    int a,b;

    那么到底是编译器先为a分配地址,还是先为b地址分配?(这还用思考吗?肯定是先定义的先分配啦)分配地址后,到底是a的地址高于b的地址,还是b的地址高于a的地址?也就是下面这两张图哪张图是正确的?

    想知道答案只需要将这两个的地址分别打印出来查看一下即可。这里小编就不打印了,建议读者自行打印。

    打印结果是a的地址高于b的地址,也就是说左图正确。

    但是注意这里的a,b均是局部变量,也就是变量存储在栈区,根据结果我们发现在栈区定义的变量是先定义的变量分配在高地址区域,后定义的变量被分配在低地址区域(左图)

    那如果是堆区和静态存储区呢?

    static int a, b;

    int *a = new int, *b = new int;

     自己动手打印一下地址吧。

    结果发现,堆区和静态存储区与栈区恰好相反,堆区/静态存储区的变量,都是先定义的变量分配在低地址区域,后定义的变量分配在高地址区域(右图)。

    总结来说,就是局部变量高存低用,全局/静态/堆变量低存低用。

    (高存(低存)描述的是多个变量同时定义时地址的占用方式是从高(低)地址往低(高)地址占)

    (低用怎么理解:每个变量在分配了内存单元后都是从该变量所占单元的低地址字节到高地址字节存放/读取数据)

    现在返回来再看上面这段代码:

    int j, b[10];

    两个局部变量,还记得高存低用吗?自己动手画一下他们的内存分布图吧。

    清楚了j 和 b数组的内存分配后,再来看这段代码(上图中a数组其实为b数组,画图的时候忘记改过来了)

    int main() 
    {
            int  j, b[10];
              for(j = 1; j <= 10; ++j){
                      b[j] = 0;
              }
    }

    当 j 值为 10 时,b[10]会导致数组越界,而越界导致的结果就是b[10]会再继续往高地址占用一个int(4个字节)的存储空间,很明显,b[10]占用了 j 的地址,此时b[10] = 0 等价于 j = 0,会导致死循环。

    (这里说的是理论结果,实际操作时 b 数组再越界1-2个内存单元才会占到 j 所在地址,实际操作只需要将 j <= 10的条件变为 j <= 11 或 j <= 12即可)

    而如果将 j 和 b[10]的定义顺序交换一下:

    int b[10], j;

    就不会出现死循环哦,自己解释一下原因吧。

    这里我们只讨论了局部变量的高存低用,别忘了还有全局/静态/堆变量的低存低用呢。

    我们将局部变量的 j 和 b[10]重新定义,从堆上申请内存,也就是

    int *j = new int, *b = new int[10];

    再遍历一遍,就不会出现死循环了哦

    int main() 
    {
            int *j = new int, *b = new int[10];
            for(*j = 1; *j <= 10; ++*j){
                    b[*j] = 0;
            }
         }

    此时j, b都在堆上,且 j 相对 b 位于低地址处,无论 b 怎么越界都只会继续占用高址空间,占不到 j 的地址。(定义顺序换一下就死循环了哦) 

    下面附一下我的测试代码

    #include <iostream>
    using namespace std;
    
    int x, y;
    
    int main()
    {
            cout << "Global variable:
    ";
            cout << "&x: " << &x << "  &y: " << &y << endl;
    
            static int m, n;
            cout << "Static variable:
    ";
            cout << "&m: " << &m << "  &n: " << &n << endl;
    
    
            cout << "Heap variable:
    ";
            int* a = new int[10], *i = new int;
            //int i, a[10];
            for(*i = 1; *i <= 11; ++*i){
                    a[*i] = 0;
            }
            for(*i = 0; *i <=11; ++*i){
                    cout << "&a[" << *i << "]:" << &a[*i] << endl;
            }
            cout << "&i:" << i << endl;
    
            delete [] a;
            //delete i; //这里由于a的越界占到了i的空间,delete [] a其实已经将i占用的空间释放,这里再释放一次就会报错invaild pointer
    
            cout << "Stack variable:
    ";
            int b[10], j;
            for(j = 1; j <= 11; ++j){
                    b[j] = 0;
            }
            for(j = 0; j <=11; ++j){
                    cout << "&b[" << j << "]:" << &b[j] << endl;
            }
            cout << "&j:" << &j << endl;
    
    }
  • 相关阅读:
    JSONObject.fromObject--JSON与对象的转换
    oracle 10g学习6
    eclipse中导入svn的三种方式
    网络方面知识收集
    oracle 10g学习之分组函数
    求m阶矩阵的n次幂
    分解质因数
    字符串对比
    十六进制转十进制
    十进制转化十六进制
  • 原文地址:https://www.cnblogs.com/GuoYuying/p/11824495.html
Copyright © 2011-2022 走看看