許多開發者好奇:free(ptr) 只接收指針,不接收大小,為何能正確釋放對應大小的記憶體?答案在於分配器在分配時,會在用戶指針前方預留元數據區塊,記錄塊大小等信息。
在 malloc.h 中定義 _ALLOCA_S_MARKER_SIZE:x86 為 8 字節,x64 為 16 字節。分配時:
void *_MarkAllocaS(void *_Ptr, unsigned int _Marker) {
if (_Ptr) {
*((unsigned int*)_Ptr) = _Marker; // 寫入大小標記
_Ptr = (char*)_Ptr + _ALLOCA_S_MARKER_SIZE; // 偏移返回用戶指針
}
return _Ptr;
}
釋放時:_freea 先向前偏移 _ALLOCA_S_MARKER_SIZE 找回原始指針,讀取大小標記,再調用底層 free。
static void* allocate(size_t __n) {
char* __result = (char*)_Alloc::allocate(__n + 8); // 多分配 8 字節
*(size_t*)__result = __n; // 前 4/8 字節存大小
return __result + 8; // 返回偏移後指針
}
static void deallocate(void* __p, size_t __n) {
char* __real_p = (char*)__p - 8; // 找回原始指針
assert(*(size_t*)__real_p == __n); // 校驗大小一致
_Alloc::deallocate(__real_p, __n + 8);
}
同樣採用「前置元數據 + 指針偏移」模式。_ALLOCA_S_MARKER_SIZE 根據架構定義為 8 或 16 字節。
int main() {
void *p = NULL;
srand(time(0));
for (int a = 10; a--; ) {
int n = rand() % 10000;
p = malloc(n);
size_t w = *((size_t*)((char*)p - 16)); // x64 下前 16 字節
cout << "w=" << w << " n=" << n << endl;
assert(w == n);
free(p);
}
}
實驗證明:用戶指針前固定偏移處確實存儲了分配大小。
free(ptr+1) 會崩潰:偏移後不再指向元數據區new[]/delete[] 同理,編譯器在塊頭記錄陣列元素個數以正確調用析構函數閱讀分配器源碼是理解記憶體管理的最佳途徑,建議開發者定期研讀開源實現。