我正在使用 VC++ 2005
我重载了 new 和 delete 运算符。
一切皆好。

我的问题与 VC++ 添加到内存分配中的一些神奇有关。

当我使用 C++ 调用时:

data = new _T [size];

全局内存分配的返回(例如)是 071f2ea0 但数据设置为 071f2ea4

当调用重载的 delete [] 时,会传入 071f2ea0 地址。

另一个注意事项是使用以下内容时:
data = new _T;

从全局内存分配返回的两个数据是相同的。

我很确定微软在内存分配的头部添加了一些用于簿记的东西。我的问题是,有没有人知道微软正在使用的规则。

我想将“数据”的值传递到一些内存测试例程中,所以我需要从全局分配调用中返回原始内存引用。

我可以假设 4 个字节是一个索引,但我想确定一下。我可以很容易地成为一个标志加上偏移量,或者计数或索引到其他一些表中,或者只是与 CPU 的缓存行对齐。我需要确定一下。我找不到任何引用资料来概述细节。

我还认为在我的其他一次运行中,偏移量为 6 字节而不是 4

最佳答案

这 4 个字节很可能包含分配中的对象总数,因此 delete [] 将能够遍历数组中调用它们的析构函数的所有对象。

要取回原始地址,您可以保留一个以地址/16 为键的查找表,该表存储基地址和长度。这将使您能够找到原始分配。但是,您需要确保您的分配+4 不会跨越 16 字节的边界。

编辑:
我继续编写了一个测试程序,该程序通过 new 创建了 50 个带有析构函数的对象,并调用了 delete []。析构函数只是调用 printf,所以它不会被优化掉。

#include <stdio.h>

class MySimpleClass
{
    public:
    ~MySimpleClass() {printf("Hi\n");}
};

int main()
{
    MySimpleClass* arr = new MySimpleClass[50];
    delete [] arr;


    return 0;
}

部分拆解如下,清理后更清晰。如您所见,VC++ 将数组计数存储在最初的 4 个字节中。
; Allocation
mov ecx, 36h ; Size of allocation
call    scratch!operator new
test    rax,rax ; Don't write 4 bytes if NULL.
je      scratch!main+0x25
mov     dword ptr [rax],32h ; Store 50 in first 4 bytes
add     rax,4 ; Increment pointer by 4

; Free
lea     rdi,[rax-4] ; Grab previous 4 bytes of allocation
mov     ebx,dword ptr [rdi] ; Store in loop counter
jmp     StartLoop ; Jump to beginning of loop
Loop:
lea     rcx,[scratch!`string' (00000000`ffe11170)] ; 1st param to printf
call    qword ptr [scratch!_imp_printf; Destructor
StartLoop:
sub     ebx,1 ; Decrement loop counter
jns     Loop ; Loop while not negative

这种簿记不同于 malloc 或 HeapAlloc 所做的簿记。这些分配器不关心对象和数组。他们只能看到总大小的内存块。 VC++ 无法向堆管理器查询分配的总大小,因为这意味着堆管理器将被绑定(bind)分配一个与您请求的大小完全相同的块。堆管理器不应该有这个限制——如果你要求为 20 个 12 字节的对象分配 240 个字节,它应该可以自由地返回一个它立即可用的 256 字节块。

关于visual-c++ - VC++中的内存分配,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/866688/

10-11 23:08
查看更多