C++ delete[] 是如何知道数组大小的?
2016-03-22 11:58
645 查看
C++ delete[] 是如何知道数组大小的?
Effective c++ 上面对 使用相同形式的new delete 时 提到在A *p = new A[100];
在 p 地址到 A[100]结束的地方 是存储A[100]数据的,在 p 地址之前有一个 n 记录这个数组的大小,delete 的时候可以知道大小,但是书上说不是所有的编译器都是非这么实现不可。
然后我就是想问这个如果不记录n的话是怎么正确 delete 那块内存大小呢?
如果记录 n 的话是不是存在 p 地址前面的,是多大怎么存的呢?
这方面有什么书可以看下么
还有delete到底具体做了什么呢
添加评论
分享
按投票排序按时间排序
11 个回答
6赞同反对,不会显示你的姓名
知乎用户
知乎用户、陈霖坤、songcy 等人赞同
嗯,是这样的,内存管理库里总会有一个方法来记录你分配的内存是多大,你写的"在 p 地址之前有一个 n 记录这个数组的大小"是一种实现方法,管理库当然也可以自己维护一个列表,记录所有空间分配的位置和大小以及类型,delete的时候查一下这个列表就行了,也就是所谓"不是所有的编译器都是非这么实现不可",目前来说,常见的C++编译器也就是GCC/VCC/ICC/BCC等,具体实现可以查各编译器的文档说明,当然商业编译器不一定肯说.
发布于 2014-09-27 添加评论 感谢
分享
收藏 • 没有帮助 •
举报
• 作者保留权利
16赞同
反对,不会显示你的姓名
夏华林
冯开宇、Bleeding、知乎用户 等人赞同
那就由我来给个详细的补充吧。
==========================
申请内存时,指针所指向区块的大小这一信息,其实就记录在该指针的周围
看下面这段代码:
#include<cstdio> #include<iostream> #include<malloc.h> #include<assert.h> #include<ctime> using namespace std; #define size 16 int main(void) { void * p = NULL; srand(time(0)); int a = 10; while (a--) { int n = rand() % 10000; p = malloc(n); size_t w = *((size_t*)((char*)p - size)); cout << "w=" << w << endl; cout << "n=" << n << endl; assert(w == n); free(p); } return 0; }
(注:如果是X86的CPU,请将 size 改为 8)
你会发现 w 和 n 始终是一致的,,这样其实不是巧合,来看 M$ 编译器 \ vc \include\ 目录下 malloc.h这一头文件 中 184 到 209 行的代码:
//这儿是根据不同的硬件平台的宏定义 #if defined (_M_IX86) #define _ALLOCA_S_MARKER_SIZE 8 #elif defined (_M_X64) #define _ALLOCA_S_MARKER_SIZE 16 #elif defined (_M_ARM) #define _ALLOCA_S_MARKER_SIZE 8 #elif !defined (RC_INVOKED) #error Unsupported target platform. #endif /* !defined (RC_INVOKED) */ _STATIC_ASSERT(sizeof(unsigned int) <= _ALLOCA_S_MARKER_SIZE); #if !defined (__midl) && !defined (RC_INVOKED) #pragma warning(push) #pragma warning(disable:6540) __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; //最后返回给调用者的指针,是原始指针偏移了_ALLOCA_S_MARKER_SIZE的新指针,这也是刚才我将指针向后偏移,就能得到该指针所指向内存区块的大小的原因。 } return _Ptr; }
再来看看在 M$ 编译器中它是如何释放的,同样在 mallloc.h 文件249行到274行:
/* _freea must be in the header so that its allocator matches _malloca */ #if !defined (__midl) && !defined (RC_INVOKED) #if !(defined (_DEBUG) && defined (_CRTDBG_MAP_ALLOC)) #undef _freea __pragma(warning(push)) __pragma(warning(disable: 6014)) _CRTNOALIAS __inline void __CRTDECL _freea(_Pre_maybenull_ _Post_invalid_ void * _Memory) { unsigned int _Marker; if (_Memory) { _Memory = (char*)_Memory - _ALLOCA_S_MARKER_SIZE; //获得原始指针 _Marker = *(unsigned int *)_Memory;//得到指针所指区块的大小 if (_Marker == _ALLOCA_S_HEAP_MARKER) { free(_Memory); } #if defined (_ASSERTE) else if (_Marker != _ALLOCA_S_STACK_MARKER) { #pragma warning(suppress: 4548) /* expression before comma has no effect */ _ASSERTE(("Corrupted pointer passed to _freea", 0)); } #endif /* defined (_ASSERTE) */ } }
再来看看 SGI STL标准库源码 stl_alloc.h 文件209 行到 246行 debug_alloc类模板的设计:
// Allocator adaptor to check size arguments for debugging. // Reports errors using assert. Checking can be disabled with // NDEBUG, but it's far better to just use the underlying allocator // instead when no checking is desired. // There is some evidence that this can confuse Purify. template <class _Alloc> class debug_alloc { private: enum {_S_extra = 8}; // Size of space used to store size. Note // that this must be large enough to preserve // alignment. //这儿就像它所说的那样 public: static void* allocate(size_t __n) { // 这里实际申请的内存大小要多 8 个字节 char* __result = (char*)_Alloc::allocate(__n + (int) _S_extra); *(size_t*)__result = __n;//前 4 个字节用于存储区块大小,可以看到,它预留了4个字节的空白区,具体原由 还望大牛能指出,==。 return __result + (int) _S_extra;//最后返回相对于原始指针偏移8个字节的新指针 } static void deallocate(void* __p, size_t __n) { char* __real_p = (char*)__p - (int) _S_extra;//获得原始指针 assert(*(size_t*)__real_p == __n);//这里增加了一个断言,防止析构了被破坏的指针 _Alloc::deallocate(__real_p, __n + (int) _S_extra); } static void* reallocate(void* __p, size_t __old_sz, size_t __new_sz) { char* __real_p = (char*)__p - (int) _S_extra; assert(*(size_t*)__real_p == __old_sz); char* __result = (char*) _Alloc::reallocate(__real_p, __old_sz + (int) _S_extra, __new_sz + (int) _S_extra); *(size_t*)__result = __new_sz; return __result + (int) _S_extra; } };
再来看看 gcc 下,其实也有类似的设计:
#if(defined(_X86_) && !defined(__x86_64)) #define _ALLOCA_S_MARKER_SIZE 8 #elif defined(__ia64__) || defined(__x86_64) #define _ALLOCA_S_MARKER_SIZE 16 #endif #if !defined(RC_INVOKED) static __inline void *_MarkAllocaS(void *_Ptr,unsigned int _Marker) { if(_Ptr) { *((unsigned int*)_Ptr) = _Marker; _Ptr = (char*)_Ptr + _ALLOCA_S_MARKER_SIZE; } return _Ptr; } #endif
#ifndef RC_INVOKED #undef _freea static __inline void __cdecl _freea(void *_Memory) { unsigned int _Marker; if(_Memory) { _Memory = (char*)_Memory - _ALLOCA_S_MARKER_SIZE; _Marker = *(unsigned int *)_Memory; if(_Marker==_ALLOCA_S_HEAP_MARKER) { free(_Memory); } #ifdef _ASSERTE else if(_Marker!=_ALLOCA_S_STACK_MARKER) { _ASSERTE(("Corrupted pointer passed to _freea",0)); } #endif } } #endif /* RC_INVOKED */
发布于 2014-10-28 4
条评论 感谢
分享
收藏 • 没有帮助 •
举报
• 作者保留权利
0赞同
反对,不会显示你的姓名
李遥,A
Programmer
delete[]可以有无穷种方法来实现
new/malloc是资源管理器,交给用户的既是一个内存地址,同时也是一个资源句柄(handle)。这是双重属性
按照句柄来理解,自然对应的管理器内部可以额外记录(bookkeeping)任意辅助信息。永远不会出现拿着句柄找管理器做不成事情的情况
编辑于 2014-09-28 添加评论 感谢
分享
收藏 • 没有帮助 •
举报
• 作者保留权利
10赞同
反对,不会显示你的姓名
陈硕,Linux
C++程序员,muduo 网络库作者
ff
charlie、王茜蔚、知乎用户 等人赞同
跟free知道该释放多大空间一个道理。
要想弄清楚,看文档(ABI)、源码、下断点单步跟踪、看反汇编,都行。
在non-trivial destructor的情况下,通常的编译器都是把n放在前面,至少占size_t,而且按class/struct的要求对齐,因此不一定是4字节。
(Linux/Windows的ABI要求均如此。我没有见过别的做法。)
编辑于 2014-09-28 6
条评论 感谢
分享
收藏 • 没有帮助 •
举报
• 作者保留权利
1赞同
反对,不会显示你的姓名
知乎用户,double
E 搬砖
Bleeding 赞同
gcc是这样实现的,但他在地址之前存储的并不一定是你分配的大小,而是一个数目的倍数,这样会使得存储数值的最后几位都是bit 0,他会稍微大于或者等于你分配的大小。由于末尾在没有标识的情况下都是0,存储的数值中末尾的几个bit可以作为内存是否被占用的标识(可以预防一个buffer被delete两次,delete只需要重置对应bit就可以了,第二次delete就可以检查这个标识然后crash掉程序)。可以写一写测试代码分析gcc关于这个地方的一些做法。
from: http://www.zhihu.com/question/25556263
相关文章推荐
- c++四种强制类型转换
- C语言中free函数如何确定释放的空间大小
- C++中使用指针、引用进行参数传递
- C++中定义一个数组一定要给出长度吗?
- 【c++】龟兔赛跑问题
- 【c++】龟兔赛跑问题
- c++ 抽象类
- 浅谈 C++ 中的 new/delete 和 new[]/delete[]
- c++: 获取delete[]中的数组大小
- C语言第四篇:指针的辨析
- 链表实例(C语言)
- c++11 学习及测试(constexpr,using,default构造函数,lamada表达式)
- 十六进制字符串转字节序列
- 算法-求二进制数中1的个数
- c++ 浅拷贝 深拷贝
- __cplusplus
- C++ 内存池 -- C++ Memory Pool
- C语言 字符串操作两头堵模型
- MFC UpdateData
- [转]C语言单引号和双引号的区别