zoukankan      html  css  js  c++  java
  • C++内存的分区

    内存一共4个区 
    1.任何在函数内部声明的非static变量,其变量地址本身在栈区。栈是向低地址扩展的数据结构,即栈顶的地址和栈的最大容量是系统预先规定好的。
    2.任何全局变量或者静态局部变量,其变量地址本身在全局区
    3.任何指针变量 如果用malloc,relloc,calloc,或者c++中的new ,指针指向的内存在堆区。堆是向高地址扩展的
    4.代码、字符常量在代码区

    其中.bss段和.data段为全局区,.bss段存储还未初始化的全局变量,.data段则存储已初始化的全局变量,.text段是代码区。

    例1:
    char  sz[] = "string";  这条语句,该语句是按照数组的形式来一个个存放的,编译器将其解释为
    char   sz[] = {'s','t','r','i','n','g','/0'}; 如果在函数内部出现的话,这几个字符将存放在堆栈中,所以不是字符串常量。
    char *psz = "string"; 这条语句,该语句定义了一个指向"string"字符串的指针,并没有空间存放"string"字符串,显然把“string”当做字符串常量并存放在常量区是最合适的选择。并且语句 psz[4] = 'x'; 在编译时不会报错,执行时会产生异常,原因是“string”存放在只读存储区,不可以修改。这和C++中 const char *psz = "string";一句是一样的,所以最好显示的加上const关键字从而让编译器检测出“修改常量“错误。
    函数中行参为数组时应怎么看待?
    如:void  foo(char sz[100], int ival[10]);
    答案是将其理解为指针的形式:
    void  foo(char *sz, int *ival);
    例2:
    int a = 1234;   //全局区
    
    int main()
    
    {
    
        char *b = "Hello!~~";   //指针指向代码区,b变量本身也在栈区
    
        int c = 5678;   //栈区
    
        int *d = (int*)malloc( sizeof(int) ); //指针指向堆区,d变量本身同样在栈区
    
        *d = 9999;
    
        static char e = 0;   //全局区
    
        static int (*f)() = main;  //指向代码区,f本身在全局区    
    
        return 0;
    
    }

    例3:函数递归是通过栈来实现的,栈中是函数返回地址

    procedure A1(…);  procedure A2(…);  procedure A3(…);
       begin         begin         begin
          ┇          ┇        ┇
        r:A2(…);      t:A3(…);     end;
        r1
    :┇         t1: ┇
       end;         end;



     
     
     



    top

     
     

    r1


    top
     

     
    t1
    r1



    top

     
     
    r1


     
     
     

    top
     

     
    调用A2前
       
    调用A2后
       
    调用A3后
       
    返回A2后

    top
     

     
    返回A1后

    具体地,当调用(call)一个函数时,主调函数将声明中的参数表以逆序压栈,然后将当前的代码执行指针(eip)压栈,跳转到被调函数的入口点。

    VarType Func (Arg1, Arg2, Arg3, ... ArgN) 
    { 
        VarType Var1, Var2, Var3, ...VarN;
        //... 
        return VarN; 
    }

    假设sizeof(VarType) = 4(DWORD), 则一次函数调用汇编代码示例为:

    调用方代码:

    push ArgN ; 依次逆序压入调用参数
    push ... 
    push Arg1 
    call Func_Address ; 压入当前EIP后跳转(EIP:指令寄存器,extended instruction pointer,指向下一条等待执行的指令地址)

    跳转至被调方代码:

    push ebp ; 备份调用方EBP指针(EBP:基址指针,base pointer,指向系统栈最上面一个栈帧的底部)
    mov ebp, esp ; 建立被调方栈底(ESP:堆栈指针,stack pointer,指向系统栈最上面一个栈帧的栈顶)
    sub esp, N * 4; 为局部变量分配空间(栈是从高地址向低地址拓展,即栈顶在低地址处)

                          内存低地址 | ESP - - - - - - - - - - - - - - - - EBP - - - - - - - - - - - - - - - - - - - - - >| 内存高地址

    mov dword ptr[esp - 4 * 1 ], 0 ; 初始化各个局部变量 = 0 这里假定VarType不是类 
    mov dword ptr[esp - 4 * ... ], 0
    mov dword ptr[esp - 4 * N ], 0
    . . . . . . ; 这里执行一些函数功能语句(比如将第N个参数[ebp + N * 4]存入局部变量), 功能完成后将函数返回值存至eax
    add esp, N * 4 ; 销毁局部变量
    mov esp, ebp ; 恢复主调方栈顶
    pop ebp ; 恢复主调方栈底
    ret ; 弹出EIP 返回主调方代码

    接上面调用方代码: 
    add esp, N * 4 ; 释放参数空间, 恢复调用前的栈 
    mov dword ptr[ebp - 4], eax ; 将返回值保存进调用方的某个VarType型局部变量

    (eax:4个字节;AX:eax的低2个字节;AH:AX的高字节;AL:AX的低字节)

  • 相关阅读:
    JavaScript实现接口的三种经典方式
    javascript实现继承3种方式: 原型继承、借用构造函数继承、组合继承,模拟extends方法继承
    JavaScript简单重写构造器的原型
    cocos2d-x中的宏定义CC_PROPERTY
    CCCallFunc CCCallFuncN CCCallFuncND的区别和使用
    action(二)
    action(一)
    CShopDialog类
    cocos2d-x与ISO内存管理(转)
    CGameConfig类
  • 原文地址:https://www.cnblogs.com/qionglouyuyu/p/4175983.html
Copyright © 2011-2022 走看看