C语言中的((size_t *)ptr)[-1]是什么意思?

时间:2018-12-14 12:13:21

标签: c pointers size

我想知道分配给指针的大小。

所以我找到了这个答案: 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])行工作正常,但是我不明白为什么...

有人可以帮助我理解这条魔术吗?谢谢!

5 个答案:

答案 0 :(得分:7)

如果ptr指向malloccallocrealloc等分配的内存块,则(((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_mallocmalloc的包装器,它为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_mallocallocated_size来解决这个问题。

答案 3 :(得分:1)

首先,假设它有效,让我们解释一下(((size_t*)ptr)[-1])的作用:

  • (size_t*)ptrptr转换为“指向size_t的指针”的类型。
  • 根据定义,
  • ((size_t *)ptr)[-1] 1 等同于*((size_t *) ptr - 1) 2 也就是说,它从(size_t *) ptr中减去1,然后“引用”结果指针。
  • 指针算术是根据数组元素定义的,并且将单个对象视为一个元素的数组。 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成功,它将为请求大小的任何对象分配空间。 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之后的空格中放置mallocintfloat。但是,我们不能通过指针算法来做到这一点-使用指针算法来导航结构的成员尚未完全定义。通过名称正确地寻址结构成员,而不是指针操作。因此,从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]的含义以及它似乎起作用的原因,但这绝不是建议使用它。获取分配给指针的大小是应用程序代码所要求的数量,并且在不依赖编译器实现的情况下,可以在需要时对其进行管理。

相关问题