相关问题:
本文章来自于多年(误)前自己做的一次实验,因为打算什么时候把问题关了,故迁移实验内容自此。
第一个问题的回答中,原答主指出:
__inline void* _MarkAllocaS(_Out_opt_ __crt_typefix(unsigned int*) void* _Ptr, unsigned int _Marker)
{
if (_Ptr)
{
*((unsigned int*)_Ptr) = _Marker;
_Ptr = (char*)_Ptr + _ALLOCA_S_MARKER_SIZE;
}
return _Ptr;
}
malloc.h这个函数中存储了内存的大小,但是评论区有人指出实验结果不符,我个人也尝试了进行实验。此前我在Effective C++中了解了这个new cookie机制,但是未曾亲自动手。
通过对内存的打印,我在我创建的int堆数组附近并未找到类似的魔术数字。我仔细寻找了这个函数的访问点,唯一的访问点就在malloc.h内部。
#ifdef _DEBUG
#ifndef _CRTDBG_MAP_ALLOC
#undef _malloca
#define _malloca(size) \
__pragma(warning(suppress: 6255 6386)) \
(_MallocaComputeSize(size) != 0 \
? _MarkAllocaS(malloc(_MallocaComputeSize(size)), _ALLOCA_S_HEAP_MARKER) \
: NULL)
#endif
#else
#undef _malloca
#define _malloca(size) \
__pragma(warning(suppress: 6255 6386)) \
(_MallocaComputeSize(size) != 0 \
? (((_MallocaComputeSize(size) <= _ALLOCA_S_THRESHOLD) \
? _MarkAllocaS(_alloca(_MallocaComputeSize(size)), _ALLOCA_S_STACK_MARKER) \
: _MarkAllocaS(malloc(_MallocaComputeSize(size)), _ALLOCA_S_HEAP_MARKER))) \
: NULL)
#endif
大意就是Debug模式下,所有_malloca(是微软的malloca私货,不是标准的malloc,所以原答主找错地方了)的内存均在堆上,而Release模式下,小数组的内存会通过_alloca而放到栈上。
#define _ALLOCA_S_THRESHOLD 1024
#define _ALLOCA_S_STACK_MARKER 0xCCCC
#define _ALLOCA_S_HEAP_MARKER 0xDDDD
通过上述代码,我们可以很明显地发现,_MarkAllocaS函数的Marker参数并不是原答主回答的size,而仅仅是用于标记Stack和Heap的标识符,在超过1kb时分配的内存会在堆上。而原答主拥有27赞8评论,其中质疑者包括我在内只有两人。可以看出尽管很多人知道这个知识点,但具体实操却没有经历。
在多次探索char数组的内存而不得后,我通过遍历的方式撞击前后1kb的内存,看是否有魔术数字正好能对应内存大小,尽管内存中因为随机性有这样的数字,但是位置的规律性根本看不出。我查阅了资料得知malloc的大小应该存储在某些被设置为保护的区块头,于是宣告对char数组的内存存储实验以失败告终。
在继续查阅了其他资料之后,我得知了new的机制和malloc存在差异,例如malloc/free本身只需要宣告内存被占用/释放即可,而new/delete却要完成对应的构造/析构操作,如果仅仅存储字节大小,由于内存对齐原因,内存长度 / sizeof(T) >= 对象个数,因此执行数组操作必须存储对象的数目而不仅仅是字节大小。
得到这个结论之后,很容易与
中的POD(即平凡构造/平凡析构)联系在一起。
C语言中不存在new,因此仿C的对象也不会有独特的new cookie机制。因此上述C风格的char数组,struct数组,以及POD(Plain Ol' Data)都不会存储这样的数组大小,自然也就没法找到了。
在使用class封装int并添加上构造/析构函数后,我又一次对INT[32]探测了内存。结果如下
数组索引 地址 值
-2 000001D0B18F51C0 20
-1 000001D0B18F51C4 0
在数组前8-4字节处正确存放了数组的大小0X20,因此编译器能够在new/delete[]时正确析构对象了。