((size_t*)ptr)[-1] 在 C 中是什么意思?
What ((size_t*)ptr)[-1] mean in C?
我想知道分配给指针的大小。
所以我找到了这个答案:
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])
行完美运行,但我不明白为什么...
谁能帮我理解这条魔法线?谢谢!
似乎您的编译器的 C malloc
实现将分配的大小(以字节为单位)保持在地址 returns.
之前的 4 个字节中
通过将返回的地址 (ptr
) 转换为指向 size_t
的指针(即 ((size_t*)ptr)
),然后取其之前的对齐地址(即 ' [-1]',实际上只是指针运算 - 与编写 *(((size_t*)ptr) - 1)
) 相同 - 您可以访问分配的大小(size_t
类型)。
这是为了解释 ((size_t*)ptr)[-1]
的含义以及它似乎有效的原因,但这绝不是使用它的建议。获取分配给指针的大小是应用程序代码要求的一个数量,如果需要应该由它管理,而不依赖于编译器实现。
如果 ptr
指向由 malloc
、calloc
、realloc
等分配的内存块,则 (((size_t*)ptr)[-1]
调用 未定义的行为。我的猜测是,它依赖于一些随机供应商的标准库实现的行为,恰好将内存块的大小存储在 malloc
等返回的位置之前的位置
请勿使用此类 HACKS!如果程序动态分配内存,它应该能够跟踪它分配的内存大小,而不依赖于未定义的行为。
malloc
等实际分配的内存块的大小可能比申请的大,所以也许你有兴趣知道实际分配的块的大小,包括多余的内存在块的末尾。可移植代码不需要知道这一点,因为访问超出请求大小的位置也是未定义行为,但出于好奇或调试目的,您可能想知道这个大小。
这实际上是一个调用 UB 的非常糟糕的代码。
如果他想保存分配的 space 大小,他应该使用结构,其中第一个字段是大小,第二个零大小数组(或 vla)用于实际数据
首先,让我们从((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
字节 加上 足够的字节用于 size_t
。它在 malloc 缓冲区的开头写入 s
的值作为 size_t
,然后 returns 指向内存的指针 在 之后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
数组后的一个元素,所以指针本身是有效的,随后得到数组下标为-1的对象也是有效的。这是不是其他人建议的未定义行为,因为指针正在转换to/from一个void *
并指向指定类型的有效对象。
在此实现中,只有请求缓冲区的大小存储在返回的指针之前,但是您可以在那里存储更多元数据,前提是您为其分配足够的额外 space。
没有考虑的一件事是 malloc
返回的内存适合任何目的对齐,而 my_malloc
返回的指针可能不符合该要求。因此,放置在返回地址的对象可能存在对齐问题并导致崩溃。为了解决这个问题,需要分配额外的字节来满足该要求,并且 allocated_size
和 my_free
也需要进行调整以解决这个问题。
首先,让我们解释一下 (((size_t*)ptr)[-1])
的作用,假设它是有效的:
(size_t*)ptr
将 ptr
转换为“指向 size_t
的指针”类型。
((size_t *)ptr)[-1]
,根据定义1,等价于*((size_t *) ptr - 1)
.2 即减去1 来自 (size_t *) ptr
并“引用”结果指针。
- 指针运算是根据数组元素定义的,并将单个对象视为一个元素的数组。2 如果
(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
成功,它将为请求大小的任何对象分配space。4所以我们当然可以存储一个size_t
那里 5,除了这段代码应该检查 return 值以防止分配失败。此外,我们可能 return &ret[1]
:
&ret[1]
相当于&*(ret + 1)
,相当于ret + 1
。这指向我们存储在 ret
的 size_t
之外的一个,这是有效的指针算法。
- 指针转换为函数return类型,
void *
,有效5
问题中显示的代码只用 my_malloc
中的值 return 做了两件事:使用 ((size_t*)ptr)[-1]
检索存储的大小并使用 space 释放 space (size_t*)ptr - 1
。这些都是有效的,因为指针转换是适当的,并且它们在指针算法的限制内运行。
但是,还有一个问题是 returned 值的进一步用途。正如其他人指出的那样,虽然 malloc
中的指针 return 适合任何对象对齐,但添加 size_t
会产生一个指针,该指针仅适合其对齐要求的对象并不比 size_t
严格。例如,在许多 C 实现中,这意味着指针不能用于 double
,这通常需要八字节对齐,而 size_t
只是四个字节。
所以我们立即看到 my_malloc
并不是 malloc
的完全替代。尽管如此,也许它只能用于具有令人满意的对齐要求的对象。让我们考虑一下。
我认为许多 C 实现对此没有问题,但是,从技术上讲,这里有一个问题:malloc
被指定为 return space 的一个对象要求的尺寸。该对象可以是数组,因此 space 可用于同一类型的多个对象。但是,没有指定 space 可以用于不同类型的多个对象。因此,如果 size_t
以外的某个对象存储在 my_malloc
编辑的 space return 中,我看不到 C 标准定义了该行为。正如我所指出的,这是一种迂腐的区分;我不希望 C 实现有这方面的问题,尽管多年来越来越积极的优化让我感到惊讶。
在 malloc
编辑的 space return 中存储多个不同对象的一种方法是使用结构。然后我们可以在 size_t
之后的 space 中放置一个 int
或 float
或 char *
。然而,我们不能通过指针运算来实现——使用指针运算来导航结构的成员还没有完全定义。寻址结构成员是通过名称正确完成的,而不是指针操作。因此 returning &ret[1]
from my_malloc
不是一种有效的方式(由 C 标准定义)来提供指向 space 的指针,该指针可用于任何对象(即使满足对齐要求)。
其他注意事项
此代码不正确地使用 %u
来格式化 size_t
类型的值:
printf("%u\n", allocated_size(array));
size_t
的特定整数类型是实现定义的,可能不是 unsigned
。 C 标准可能未定义由此产生的行为。正确的格式说明符是 %zu
.
脚注
1 C 2018 6.5.2.1 2.
2更准确的说是*((((size_t *) ptr)) + (-1))
,但是这些是等价的
3 C 2018 6.5.6 8 和 9.
4 C 2018 7.22.3.4.
5 C 2018 7.22.3.4 的一个非常迂腐的 reader 可能会反对 size_t
不是请求大小的对象,而是一个对象较小的尺寸。我不认为这是预期的意思。
6 C 2018 6.3.2.3 1.
我想知道分配给指针的大小。
所以我找到了这个答案: 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])
行完美运行,但我不明白为什么...
谁能帮我理解这条魔法线?谢谢!
似乎您的编译器的 C malloc
实现将分配的大小(以字节为单位)保持在地址 returns.
通过将返回的地址 (ptr
) 转换为指向 size_t
的指针(即 ((size_t*)ptr)
),然后取其之前的对齐地址(即 ' [-1]',实际上只是指针运算 - 与编写 *(((size_t*)ptr) - 1)
) 相同 - 您可以访问分配的大小(size_t
类型)。
这是为了解释 ((size_t*)ptr)[-1]
的含义以及它似乎有效的原因,但这绝不是使用它的建议。获取分配给指针的大小是应用程序代码要求的一个数量,如果需要应该由它管理,而不依赖于编译器实现。
如果 ptr
指向由 malloc
、calloc
、realloc
等分配的内存块,则 (((size_t*)ptr)[-1]
调用 未定义的行为。我的猜测是,它依赖于一些随机供应商的标准库实现的行为,恰好将内存块的大小存储在 malloc
等返回的位置之前的位置
请勿使用此类 HACKS!如果程序动态分配内存,它应该能够跟踪它分配的内存大小,而不依赖于未定义的行为。
malloc
等实际分配的内存块的大小可能比申请的大,所以也许你有兴趣知道实际分配的块的大小,包括多余的内存在块的末尾。可移植代码不需要知道这一点,因为访问超出请求大小的位置也是未定义行为,但出于好奇或调试目的,您可能想知道这个大小。
这实际上是一个调用 UB 的非常糟糕的代码。
如果他想保存分配的 space 大小,他应该使用结构,其中第一个字段是大小,第二个零大小数组(或 vla)用于实际数据
首先,让我们从((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
字节 加上 足够的字节用于 size_t
。它在 malloc 缓冲区的开头写入 s
的值作为 size_t
,然后 returns 指向内存的指针 在 之后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
数组后的一个元素,所以指针本身是有效的,随后得到数组下标为-1的对象也是有效的。这是不是其他人建议的未定义行为,因为指针正在转换to/from一个void *
并指向指定类型的有效对象。
在此实现中,只有请求缓冲区的大小存储在返回的指针之前,但是您可以在那里存储更多元数据,前提是您为其分配足够的额外 space。
没有考虑的一件事是 malloc
返回的内存适合任何目的对齐,而 my_malloc
返回的指针可能不符合该要求。因此,放置在返回地址的对象可能存在对齐问题并导致崩溃。为了解决这个问题,需要分配额外的字节来满足该要求,并且 allocated_size
和 my_free
也需要进行调整以解决这个问题。
首先,让我们解释一下 (((size_t*)ptr)[-1])
的作用,假设它是有效的:
(size_t*)ptr
将ptr
转换为“指向size_t
的指针”类型。((size_t *)ptr)[-1]
,根据定义1,等价于*((size_t *) ptr - 1)
.2 即减去1 来自(size_t *) ptr
并“引用”结果指针。- 指针运算是根据数组元素定义的,并将单个对象视为一个元素的数组。2 如果
(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
成功,它将为请求大小的任何对象分配space。4所以我们当然可以存储一个size_t
那里 5,除了这段代码应该检查 return 值以防止分配失败。此外,我们可能 return &ret[1]
:
&ret[1]
相当于&*(ret + 1)
,相当于ret + 1
。这指向我们存储在ret
的size_t
之外的一个,这是有效的指针算法。- 指针转换为函数return类型,
void *
,有效5
问题中显示的代码只用 my_malloc
中的值 return 做了两件事:使用 ((size_t*)ptr)[-1]
检索存储的大小并使用 space 释放 space (size_t*)ptr - 1
。这些都是有效的,因为指针转换是适当的,并且它们在指针算法的限制内运行。
但是,还有一个问题是 returned 值的进一步用途。正如其他人指出的那样,虽然 malloc
中的指针 return 适合任何对象对齐,但添加 size_t
会产生一个指针,该指针仅适合其对齐要求的对象并不比 size_t
严格。例如,在许多 C 实现中,这意味着指针不能用于 double
,这通常需要八字节对齐,而 size_t
只是四个字节。
所以我们立即看到 my_malloc
并不是 malloc
的完全替代。尽管如此,也许它只能用于具有令人满意的对齐要求的对象。让我们考虑一下。
我认为许多 C 实现对此没有问题,但是,从技术上讲,这里有一个问题:malloc
被指定为 return space 的一个对象要求的尺寸。该对象可以是数组,因此 space 可用于同一类型的多个对象。但是,没有指定 space 可以用于不同类型的多个对象。因此,如果 size_t
以外的某个对象存储在 my_malloc
编辑的 space return 中,我看不到 C 标准定义了该行为。正如我所指出的,这是一种迂腐的区分;我不希望 C 实现有这方面的问题,尽管多年来越来越积极的优化让我感到惊讶。
在 malloc
编辑的 space return 中存储多个不同对象的一种方法是使用结构。然后我们可以在 size_t
之后的 space 中放置一个 int
或 float
或 char *
。然而,我们不能通过指针运算来实现——使用指针运算来导航结构的成员还没有完全定义。寻址结构成员是通过名称正确完成的,而不是指针操作。因此 returning &ret[1]
from my_malloc
不是一种有效的方式(由 C 标准定义)来提供指向 space 的指针,该指针可用于任何对象(即使满足对齐要求)。
其他注意事项
此代码不正确地使用 %u
来格式化 size_t
类型的值:
printf("%u\n", allocated_size(array));
size_t
的特定整数类型是实现定义的,可能不是 unsigned
。 C 标准可能未定义由此产生的行为。正确的格式说明符是 %zu
.
脚注
1 C 2018 6.5.2.1 2.
2更准确的说是*((((size_t *) ptr)) + (-1))
,但是这些是等价的
3 C 2018 6.5.6 8 和 9.
4 C 2018 7.22.3.4.
5 C 2018 7.22.3.4 的一个非常迂腐的 reader 可能会反对 size_t
不是请求大小的对象,而是一个对象较小的尺寸。我不认为这是预期的意思。
6 C 2018 6.3.2.3 1.