首先,让我们从
((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]);
printf("p[-1] = %d\n", p[-1]);
printf("p[-2] = %d\n", p[-2]);
因此,将此应用于
((size_t*)ptr)[-1]
,这意味着
ptr
指向类型为
size_t
的一个或多个对象的数组的元素(或超出数组末尾的一个元素),下标-1获取
ptr
所指对象的前一个对象。
现在,在示例程序的上下文中,这意味着什么?
函数my_malloc
是malloc
的包装器,它分配了s
字节加上足够的字节来存储一个size_t
。它将s
的值作为size_t
写入malloc缓冲区的开头,然后返回指向size_t
对象之后的内存的指针。
因此,实际分配的内存和返回的指针看起来像这样(假设sizeof(size_t)为8
):
-----
0x80 | s |
0x81 | s |
0x82 | s |
0x83 | s |
0x84 | s |
0x85 | s |
0x86 | s |
0x87 | s |
0x88 | | <--- ptr
0x89 | |
0x8A | |
...
当从my_malloc
返回的指针传递给allocated_size
时,该函数可以使用((size_t*)ptr)[-1]
来读取缓冲区请求的大小:
-----
0x80 | s | <--- ptr[-1]
0x81 | s |
0x82 | s |
0x83 | s |
0x84 | s |
0x85 | s |
0x86 | s |
0x87 | s |
0x88 | | <--- ptr[0]
0x89 | |
0x8A | |
ptr
指向大小为1的size_t
数组之外的一个元素,因此指针本身是有效的,随后使用数组下标-1获取对象也是有效的。正如其他人所建议的那样,这不是未定义的行为,因为指针正在转换为/从void *
并指向指定类型的有效对象。
在这个实现中,只有请求缓冲区的大小存储在返回的指针之前,但是如果分配足够的额外空间,您可以在那里存储更多的元数据。
这件事没有考虑到的是,由malloc
返回的内存适合于任何目的,而my_malloc
返回的指针可能不符合该要求。因此,放置在返回地址处的对象可能具有对齐问题并导致崩溃。为了解决这个问题,需要分配额外的字节以适应该要求,并且还需要调整allocated_size
和my_free
来解决这个问题。
size_t
的大小可能 不 等于所有类型的最大对齐要求。 - Antti Haapala -- Слава Україніreturn &ret[1];
哎呀,这是非常糟糕的代码。继续 @AnttiHaapala 的说法,由于size_t
可能不适合最大对齐限制,因此使用从my_malloc()
返回的指针可能会失败,因为它不再满足 C 标准的要求,即“适当地对齐,以便可以将其分配给具有基本对齐要求的任何类型对象的指针”。 - Andrew Henle