首先,让我们从((size_t*)ptr)[-1]的含义开始。
当您将数组下标运算符用作(例如)A[B]时,这完全等效于*(A + B)。因此,这里真正发生的是指针算术,然后是解除引用。这意味着具有负数组索引是有效的,前提是所讨论的指针不指向数组的第一个元素。
例如:
int a[5] = { 1, 2, 3, 4, 5 };
int *p = a + 2;
printf("p[0] = %d\n", p[0]); // prints 3
printf("p[-1] = %d\n", p[-1]); // prints 2
printf("p[-2] = %d\n", p[-2]); // prints 1
因此将其应用于((size_t*)ptr)[-1],这表示ptr指向由类型为size_t的一个或多个对象组成的数组的元素(或指向末尾的一个元素)。数组),而下标-1恰好在ptr指向的对象之前获取对象。
现在在示例程序的上下文中这意味着什么?
函数my_malloc是malloc的包装器,它为s分配了{strong> plus 个字节。它将size_t的值写为s在已分配缓冲区的开头,然后在size_t对象的之后返回指向内存的指针。
因此实际分配的内存和返回的指针看起来像这样(假设size_t:
sizeof(size_t) is 8)
当从-----
0x80 | s |
0x81 | s |
0x82 | s |
0x83 | s |
0x84 | s |
0x85 | s |
0x86 | s |
0x87 | s |
0x88 | |
0x89 | |
0x8A | |
...返回的指针传递到my_malloc时,该函数可以使用allocated_size读取请求的缓冲区大小:
((size_t*)ptr)[-1]
强制转换的-----
0x80 | s |
0x81 | s |
0x82 | s |
0x83 | s |
0x84 | s |
0x85 | s |
0x86 | s |
0x87 | s |
0x88 | |
0x89 | |
0x8A | |指向大小为ptr的数组之后的一个元素,因此指针本身是有效的,并且随后使用数组下标-1获取对象也是有效的。这是不是未定义的行为,因为其他指针已经建议,因为指针正在与size_t进行相互转换,并指向指定类型的有效对象。
在此实现中,仅在返回的指针之前存储请求的缓冲区的大小,但是如果您为其分配了足够的额外空间,则可以在其中存储更多的元数据。
这没有考虑的一件事是,void *返回的内存可以适当地用于任何目的,并且malloc返回的指针可能不符合该要求。因此,放置在返回地址处的对象可能会出现对齐问题并导致崩溃。为了解决这个问题,需要分配额外的字节来满足该要求,并且还需要调整my_malloc和allocated_size来解决这个问题。