我想知道分配给指针的大小。
所以我找到了这个答案: how can i know the allocated memory size of pointer variable in c
它具有下面的代码。
#include <stdlib.h>
#include <stdio.h>
void * my_malloc(size_t s)
{
size_t * ret = malloc(sizeof(size_t) + s);
*ret = s;
return &ret[1];
}
void my_free(void * ptr)
{
free( (size_t*)ptr - 1);
}
size_t allocated_size(void * ptr)
{
return ((size_t*)ptr)[-1];
}
int main(int argc, const char ** argv)
{
int * array = my_malloc(sizeof(int) * 3);
printf("%u\n", allocated_size(array));
my_free(array);
return 0;
}
(((size_t*)ptr)[-1])
行工作正常,但是我不明白为什么...
有人可以帮助我理解这条魔术吗?谢谢!
答案 0 :(得分:7)
如果ptr
指向malloc
,calloc
,realloc
等分配的内存块,则(((size_t*)ptr)[-1]
调用 undefined行为。我的猜测是,它依赖于标准库的某些随机供应商实现的行为,该行为恰好在malloc
等返回的位置之前将存储块的大小存储在该位置。
请勿使用此类文物!如果程序正在动态分配内存,则它应该能够跟踪其分配的内存大小,而不必依赖未定义的行为。
由malloc
等实际分配的内存块的大小可能大于所请求的大小,因此也许您有兴趣知道分配的块的实际大小,包括内存中的多余内存。块的结尾。可移植代码不需要知道这一点,因为访问超出请求大小的位置也是未定义的行为,但是出于好奇或调试目的,您可能想知道此大小。
答案 1 :(得分:2)
这实际上是调用UB的非常糟糕的代码。
如果他想保存分配的空间大小,则应使用第一个字段为大小,第二个零大小数组(或vla)作为实际数据的结构
答案 2 :(得分:2)
首先,让我们从((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 | | <--- 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
进行相互转换,并指向指定类型的有效对象。
在此实现中,仅在返回的指针之前存储请求的缓冲区的大小,但是如果您为其分配了足够的额外空间,则可以在其中存储更多的元数据。
这没有考虑的一件事是,void *
返回的内存可以适当地用于任何目的,并且malloc
返回的指针可能不符合该要求。因此,放置在返回地址处的对象可能会出现对齐问题并导致崩溃。为了解决这个问题,需要分配额外的字节来满足该要求,并且还需要调整my_malloc
和allocated_size
来解决这个问题。
答案 3 :(得分:1)
首先,假设它有效,让我们解释一下(((size_t*)ptr)[-1])
的作用:
(size_t*)ptr
将ptr
转换为“指向size_t
的指针”的类型。((size_t *)ptr)[-1]
1 等同于*((size_t *) ptr - 1)
。 2 也就是说,它从(size_t *) ptr
中减去1,然后“引用”结果指针。(size_t *) ptr
指向size_t
的正上方对象,然后*((size_t *) ptr - 1)
指向size_t
对象。(((size_t*)ptr)[-1])
是size_t
之前的ptr
对象。现在,让我们讨论一下此表达式是否有效。 ptr
是通过以下代码获得的:
void * my_malloc(size_t s)
{
size_t * ret = malloc(sizeof(size_t) + s);
*ret = s;
return &ret[1];
}
如果malloc
成功,它将为请求大小的任何对象分配空间。 4 因此,我们当然可以在其中存储size_t
5 ,除了该代码应检查返回值以防止分配失败。此外,我们可能会返回&ret[1]
:
&ret[1]
等效于&*(ret + 1)
,等效于ret + 1
。这指向了我们存储在size_t
的{{1}}之外的一个有效指针算法。ret
。 5 问题中显示的代码仅用两件事来处理从void *
返回的值:使用my_malloc
检索存储的大小并使用((size_t*)ptr)[-1]
释放空间。这些都是有效的,因为指针转换是适当的,并且它们在指针算术的范围内运行。
但是,对于返回值可以进一步使用还有一个问题。正如其他人指出的那样,虽然从(size_t*)ptr - 1
返回的指针已针对任何对象进行了适当对齐,但添加malloc
所生成的指针仅针对其对齐要求不比{严格的对象{1}}。例如,在许多C实现中,这将意味着指针不能用于size_t
,这通常需要八字节对齐,而size_t
仅是四个字节。
因此,我们立即看到double
不能完全替代size_t
。尽管如此,也许它只能用于具有满意对准要求的物体。让我们考虑一下。
我认为许多C实现都对此没有问题,但是从技术上讲,这里存在一个问题:my_malloc
被指定为返回请求大小的一个对象的空间。该对象可以是一个数组,因此该空间可用于相同类型的多个对象。但是,未指定该空间可用于不同类型的多个对象。因此,如果将malloc
以外的某个对象存储在malloc
返回的空间中,则我看不到C标准定义了行为。正如我指出的那样,这是一种学究的区别;我期望C实现不会对此产生问题,尽管这些年来越来越积极的优化使我感到惊讶。
在size_t
返回的空间中存储多个不同对象的一种方法是使用结构。然后,我们可以在my_malloc
之后的空格中放置malloc
或int
或float
。但是,我们不能通过指针算法来做到这一点-使用指针算法来导航结构的成员尚未完全定义。通过名称正确地寻址结构成员,而不是指针操作。因此,从char *
返回size_t
并不是提供C指针的有效方法(由C标准定义),该指针可用于任何对象(即使满足对齐要求)。 >
此代码不正确地使用&ret[1]
来格式化my_malloc
类型的值:
%u
size_t
的特定整数类型是实现定义的,可能不是printf("%u\n", allocated_size(array));
。最终的行为可能不会由C标准定义。正确的格式说明符为size_t
。
1 C 2018 6.5.2.1 2。
2 更确切地说,它是unsigned
,但它们是等效的。
3 C 2018 6.5.6 8和9。
4 C 2018 7.22.3.4。
5 一个非常花哨的C 2018 7.22.3.4读者可能会反对%zu
不是所请求大小的对象,而是一个较小大小的对象。我不认为这是预期的意思。
6 C 2018 6.3.2.3 1。
答案 4 :(得分:0)
似乎编译器的C malloc
实现将分配的大小(以字节为单位)保留在返回地址之前的4个字节中。
通过将返回的地址(ptr
)转换为指向size_t
的指针(即((size_t*)ptr)
),然后在它之前获取对齐的地址(即'[- 1]',实际上只是指针算术-与编写*(((size_t*)ptr) - 1)
相同-您可以访问分配的大小(类型为size_t
)。
这是为了解释((size_t*)ptr)[-1]
的含义以及它似乎起作用的原因,但这绝不是建议使用它。获取分配给指针的大小是应用程序代码所要求的数量,并且在不依赖编译器实现的情况下,可以在需要时对其进行管理。