关于c++:C如何用简单的汇编指令实现C复杂抽象的面向对象概念6动态内存newdelete

2次阅读

共计 1846 个字符,预计需要花费 5 分钟才能阅读完成。

动态内存调配是在堆上调配的,且本人调配的,必须要本人开释,否则就会内存透露,具体 C ++ 怎么实现的呢?

老规矩,先上 C ++ 验证代码:

#include<iostream>
// #include <cstdlib>
/**
 * 内存治理:堆区内存调配
*/


int main(){int *arr = new int[11];
    arr[0] = 0;
    arr[9] = 9;
    arr[88] = 88;// 越界
    // std::cout<<arr[88] << std::endl;
    delete[] arr;

    int* arr_malloc = (int*)malloc(11 * sizeof(int));
    arr_malloc[0] = 0;
    arr_malloc[9] = 9;
    arr_malloc[88] = 88;// 越界
    free(arr_malloc);
    return 0;
}

下面 C ++ 代码,别离用 new/delete 调配开释堆内存,又用 malloc/free 调配开释内存,且还进行了越界赋值,看看什么成果。,上面是对应的汇编:

main:
.LFB1522:
    .cfi_startproc
    endbr64
    pushq    %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $16, %rsp

# new/delete
    movl    $44, %edi # 申请调配 44 个字节
    call    _Znam@PLT
    movq    %rax, -16(%rbp) # rax 里是函数返回值,rbp- 8 存储这块堆内存的地址
    
    movq    -16(%rbp), %rax
    movl    $0, (%rax) # 堆内存 + 0 的地址赋值为 0,即第 0 个元素
    
    movq    -16(%rbp), %rax
    addq    $36, %rax
    movl    $9, (%rax) # # 9 * 4 == 36,即第 9 个元素

    movq    -16(%rbp), %rax
    addq    $352, %rax # 88 * 4 = 352
    movl    $88, (%rax) # 越界赋值 88


    cmpq    $0, -16(%rbp) # 空指针查看,如果相等,那么零标记(ZF)将被设置为 1,否则将被设置为 0。je    .L2 # je .L2 指令查看零标记,如果它为 1,则跳转到.L2 标签处
    movq    -16(%rbp), %rax
    movq    %rax, %rdi
    call    _ZdaPv@PLT


# malloc/free
.L2:
    movl    $44, %edi
    call    malloc@PLT # malloc 调配 44 字节
    movq    %rax, -8(%rbp)
    movq    -8(%rbp), %rax
    movl    $0, (%rax)
    movq    -8(%rbp), %rax
    addq    $36, %rax
    movl    $9, (%rax)
    movq    -8(%rbp), %rax
    addq    $352, %rax # 越界赋值
    movl    $88, (%rax) 
    movq    -8(%rbp), %rax
    movq    %rax, %rdi
    call    free@PLT # 不做空指针查看,间接调用 free 去开释
    movl    $0, %eax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc

根据上述汇编可知,汇编上间接调用的是零碎调用的动态链接库,具体 new/delete、malloc/free 的实现代码,须要本人看内核源码,回头钻研钻研。然而基本上咱们能够宏观晓得,C++ 编译器只管调用零碎调用,去给你调配开释内存,而且必须是你本人执行 delete/free 了之后,它才会取调用对应的零碎调用去开释对应的堆内存,如果你 C ++ 代码里不执行 delete/free,那么 C ++ 编译器也不会帮你查看。

所以,利用堆内存,肯定要小心,留神开释,否则就会呈现内存透露问题。

另外,越界问题 C ++ 也不会帮你查看,间接就从数组的起始地位计算第 88 个元素的地址,而后间接帮你越界赋值了,其实这是错的,如果实在的程序中,可能会毁坏无效数据。所以要留神数组下标的管制。

当初明确了,堆内存的调配与开释也是比较简单的。

总结

这几篇文章,别离摸索了栈内存、数据段、只读数据段、代码段、堆内存的调配问题,我当初是比拟直观的了解了这些内存的分配情况是怎么回事。

简而言之,栈内存、数据段、代码段的内存调配、内存的绝对地址,都是编译器提前计算好的,即都是在编译期确定好的。从汇编程序里也能够看出,所以的标号的寻址用的都是绝对地址,绝对于 rbp 寄存器或者 rip 的地址。

而堆内存的调配,是动静的,当调用零碎调用进行调配的时候,零碎调用里的代码会主动寻找一块区域调配给用户程序,每一次调配都是不确定的,所以它无奈在编译期进行确定!

正文完
 0