共计 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 的地址。
而堆内存的调配,是动静的,当调用零碎调用进行调配的时候,零碎调用里的代码会主动寻找一块区域调配给用户程序,每一次调配都是不确定的,所以它无奈在编译期进行确定!