x86_64平台malloc_chunk内存布局结构及实验疑问解析
(底部已更新部分解释内容)
我正在研究malloc的工作原理,在对chunk结构有基础了解后,决定编写一个小程序深入查看其实际运行逻辑。
#include <stdio.h> #include <stdlib.h> /* An allocated chunk looks like this: chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Size of previous chunk, if unallocated (P clear) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Size of chunk, in bytes |A|M|P| mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | User data starts here... . . . . (malloc_usable_size() bytes) . . | nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | (size of chunk, but used for application data) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Size of next chunk, in bytes |A|0|1| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ int main() { void *a = malloc(24); void *b = malloc(16); void *chunk_b = NULL, *chunk_after_b = NULL, *chunk_a = NULL; chunk_a = a - 0x8; //8 byte before a (user data) chunk_b = a + malloc_usable_size(a); chunk_after_b = b + malloc_usable_size(b); *(int *)a = 0xa; *(int *)b = 0xb; printf("a:0x%x malloc_usable_size:%d(0x%x)\n", a, malloc_usable_size(a), malloc_usable_size(a)); printf("b:0x%x malloc_usable_size:%d(0x%x)\n\n", b, malloc_usable_size(b), malloc_usable_size(b)); printf("chunk_a[0]:0x%x value:0x%04x \n", chunk_a, ((int *)chunk_a)[0]); printf("chunk_a[1]:0x%x value:0x%04x \n", &((int *)chunk_a)[1], ((int *)chunk_a)[1]); printf("chunk_a[2]:0x%x value:0x%04x \n\n", &((int *)chunk_a)[2], ((int *)chunk_a)[2]); printf("chunk_b[0]:0x%x value:0x%04x \n", chunk_b, ((int *)chunk_b)[0]); printf("chunk_b[1]:0x%x value:0x%04x \n", &((int *)chunk_b)[1], ((int *)chunk_b)[1]); printf("chunk_b[2]:0x%x value:0x%04x \n\n", &((int *)chunk_b)[2], ((int *)chunk_b)[2]); printf("chunk_after_b[0]:0x%x value:0x%04x \n", chunk_after_b, ((int *)chunk_after_b)[0]); printf("chunk_after_b[1]:0x%x value:0x%04x \n", &((int *)chunk_after_b)[1], ((int *)chunk_after_b)[1]); printf("chunk_after_b[2]:0x%x value:0x%04x \n\n", &((int *)chunk_after_b)[2], ((int *)chunk_after_b)[2]); free(a); free(b); return 0; }
运行结果如下:
a:0x625ca2a0 malloc_usable_size:24(0x18) b:0x625ca2c0 malloc_usable_size:24(0x18) chunk_a[0]:0x625ca298 value:0x0021 chunk_a[1]:0x625ca29c value:0x0000 chunk_a[2]:0x625ca2a0 value:0x000a -->mem (user data) chunk_b[0]:0x625ca2b8 value:0x0021 chunk_b[1]:0x625ca2bc value:0x0000 chunk_b[2]:0x625ca2c0 value:0x000b -->mem (user data) chunk_after_b[0]:0x625ca2d8 value:0x0411 chunk_after_b[1]:0x625ca2dc value:0x0000 chunk_after_b[2]:0x625ca2e0 value:0x6e756863 (gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04) Target: x86_64-linux-gnu)
按照我的理解,malloc()会返回“User data”段的地址,因此将该地址减去8字节(对应“Size of chunk”和“Size of previous chunk”共8字节)就应该是chunk的起始地址(对应示例中的chunk_a);此外,将用户数据地址加上malloc_usable_size()的返回值就应该是下一个chunk的起始地址(对应示例中的chunk_b)。
但运行结果有部分内容不符合我的预期:我认为“Size of chunk”应该位于chunk_a[1]和chunk_b[1]的位置,但这两个位置的值都是0;而我认为是“Size of previous chunk”的chunk_a[0]和chunk_b[0]位置,值为0x21,换算为十进制是33,减去最低位P标志的1后得到32。
这个数值和实际chunk大小匹配:malloc_usable_size(a)返回的用户数据大小是24字节,加上8字节的chunk头部长度,正好是32字节的实际chunk大小。
想请教“Size of chunk”字段实际位于哪个位置?我之前的理解存在什么错误?
感谢评论区的所有建议,我补充做了一些测试后现在逻辑已经通顺了。
核心问题是我之前错误地将“Size of previous chunk”和“Size of chunk”都当成了4字节字段,而在x86_64编译环境下,两个字段都是8字节长度。
基于这个认知我重写了实验程序:
#include <stdio.h> #include <stdlib.h> int main() { int i=0; void *a = malloc(24); void *b = malloc(16); void *chunk_b = NULL, *chunk_a = NULL; chunk_a = (u_int64_t *)a - 2; chunk_b = a + malloc_usable_size(a); ((u_int8_t *)a)[0] = 0xa0; ((u_int8_t *)a)[23] = 0xa1; ((u_int8_t *)b)[0] = 0xb0; printf("a:0x%x malloc_usable_size:%d(0x%x)\n", a, malloc_usable_size(a), malloc_usable_size(a)); printf("b:0x%x malloc_usable_size:%d(0x%x)\n\n", b, malloc_usable_size(b), malloc_usable_size(b)); printf("chunk_a[0]:0x%x value:0x%016x \n", chunk_a, ((u_int64_t *)chunk_a)[0]); printf("chunk_a[1]:0x%x value:0x%016x \n", &((u_int64_t *)chunk_a)[1], ((u_int64_t *)chunk_a)[1]); printf("chunk_a[2]:0x%x value:0x%016x \n", &((u_int64_t *)chunk_a)[2], ((u_int64_t *)chunk_a)[2]); for(i = 0; i<24;i++) { printf("a[%d]:0x%x value:0x%02x \n", i, &((u_int8_t *)a)[i], ((u_int8_t *)a)[i]); } printf("\n"); printf("chunk_b[0]:0x%x value:0x%016x \n", chunk_b, ((u_int64_t *)chunk_b)[0]); printf("chunk_b[1]:0x%x value:0x%016x \n", &((u_int64_t *)chunk_b)[1], ((u_int64_t *)chunk_b)[1]); printf("chunk_b[2]:0x%x value:0x%016x \n\n", &((u_int64_t *)chunk_b)[2], ((u_int64_t *)chunk_b)[2]); free(b); free(a); return 0; }
本次运行结果如下:
a:0xc2a4b2a0 malloc_usable_size:24(0x18) b:0xc2a4b2c0 malloc_usable_size:24(0x18) chunk_a[0]:0xc2a4b290 value:0x0000000000000000 chunk_a[1]:0xc2a4b298 value:0x0000000000000021 chunk_a[2]:0xc2a4b2a0 value:0x00000000000000a0 a[0]:0xc2a4b2a0 value:0xa0 a[1]:0xc2a4b2a1 value:0x00 a[2]:0xc2a4b2a2 value:0x00 a[3]:0xc2a4b2a3 value:0x00 a[4]:0xc2a4b2a4 value:0x00 a[5]:0xc2a4b2a5 value:0x00 a[6]:0xc2a4b2a6 value:0x00 a[7]:0xc2a4b2a7 value:0x00 a[8]:0xc2a4b2a8 value:0x00 a[9]:0xc2a4b2a9 value:0x00 a[10]:0xc2a4b2aa value:0x00 a[11]:0xc2a4b2ab value:0x00 a[12]:0xc2a4b2ac value:0x00 a[13]:0xc2a4b2ad value:0x00 a[14]:0xc2a4b2ae value:0x00 a[15]:0xc2a4b2af value:0x00 a[16]:0xc2a4b2b0 value:0x00 a[17]:0xc2a4b2b1 value:0x00 a[18]:0xc2a4b2b2 value:0x00 a[19]:0xc2a4b2b3 value:0x00 a[20]:0xc2a4b2b4 value:0x00 a[21]:0xc2a4b2b5 value:0x00 a[22]:0xc2a4b2b6 value:0x00 a[23]:0xc2a4b2b7 value:0xa1 chunk_b[0]:0xc2a4b2b8 value:0x0000000000000021 chunk_b[1]:0xc2a4b2c0 value:0x00000000000000b0 chunk_b[2]:0xc2a4b2c8 value:0x0000000000000000
我之前误以为两个字段都是4字节,是因为如果我从用户数据起始地址(本例中为0xa77ef2a0)加上malloc_usable_size()的返回值(本例中为0x18),得到的nextchunk位置和4字节字段的假设不匹配。
我之前的另一个误解是:如果当前chunk的“Size of chunk”字段最低位P标志被置位,代表前一个chunk处于已分配状态,此时当前chunk的“Size of previous chunk”字段会被算作前一个chunk的数据域的一部分。
本次实验中我将a指向的已分配内存的最后一个字节(地址0xc2a4b2b7)设为0xa1,紧跟其后的地址0xc2a4b2b8正好是chunk_b的“Size of chunk”字段,和上述逻辑完全吻合。
目前该逻辑已经完全符合我的认知,如果还有理解错误的地方欢迎指正,再次感谢各位的评论建议。
内容的提问来源于stack exchange,提问作者LLAN

