49

我知道将动态分配的数组的长度传递给操作它们的函数是一种常见的约定:

void initializeAndFree(int* anArray, size_t length);

int main(){
    size_t arrayLength = 0;
    scanf("%d", &arrayLength);
    int* myArray = (int*)malloc(sizeof(int)*arrayLength);

    initializeAndFree(myArray, arrayLength);
}

void initializeAndFree(int* anArray, size_t length){
    int i = 0;
    for (i = 0; i < length; i++) {
        anArray[i] = 0;
    }
    free(anArray);
}

但是,如果我无法从指针中获取分配内存的长度,那么free()当我给它的所有指针都是相同的指针时,“自动”如何知道要释放什么?作为 C 程序员,为什么我不能参与其中?

从哪里free()获得它的免费(har-har)知识?

4

9 回答 9

33

除了Klatchko 的正确观点(标准没有提供)之外,真正的 malloc/free 实现通常会分配比您要求的更多空间。例如,如果您要求 12 个字节,它可能会提供 16 个(请参阅A Memory Allocator,其中指出 16 是常见的大小)。所以它不需要知道你请求了 12 个字节,只是它给了你一个 16 字节的块。

于 2010-04-16T05:58:11.610 回答
19

你不能得到它,因为 C 委员会在标准中没有要求。

如果您愿意编写一些不可移植的代码,您可能会遇到:

*((size_t *)ptr - 1)

或许:

*((size_t *)ptr - 2)

但是,这是否有效将取决于您使用的 malloc 实现存储该数据的确切位置。

于 2010-04-16T05:52:33.190 回答
9

虽然可以获得内存分配器放置在已分配块之前的元数据,但这仅指针确实是指向动态分配块的指针时才有效。这将严重影响函数的实用性,该函数要求所有传递的参数都是指向此类块的指针,而不是简单的自动或静态数组。

关键是没有可移植的方法来检查指针以了解它指向的内存类型。因此,虽然这是一个有趣的想法,但它并不是一个特别安全的提议。

一种安全且可移植的方法是保留分配的第一个字以保存长度。GCC(也许还有其他一些编译器)支持使用具有零长度数组的结构来实现这一点的非便携式方法,与便携式解决方案相比,这在一定程度上简化了代码:

typedef struct
{
    size_t length ;
    char alloc[0] ;   // Compiler specific extension!!!
} tSizedAlloc ;

// Allocating a sized block
tSizedAlloc* blk = malloc( sizeof(tSizedAlloc) + length ) ;
blk->length = length ;

// Accessing the size and data information of the block
size_t blk_length = blk->length ;
char*  data = blk->alloc ;
于 2010-04-16T08:27:54.593 回答
9

在阅读了 Klatchko 的答案后,我自己尝试了一下,并且ptr[-1]确实存储了实际内存(通常比我们要求的内存要多,可能是为了防止分段错误而保存)。

{
  char *a = malloc(1);
  printf("%u\n", ((size_t *)a)[-1]);   //prints 17
  free(a);
  exit(0);
}

尝试使用不同的大小,GCC 分配内存如下:

最初分配的内存为 17 个字节。
分配的内存至少比请求的大小多 5 个字节,如果请求更多,它会多分配 8 个字节。

  • 如果 size 为 [0,12],则分配的内存为 17。
  • 如果 size 为 [13],则分配的内存为 25。
  • 如果 size 为 [20],则分配的内存为 25。
  • 如果 size 为 [21],则分配的内存为 33。
于 2010-04-16T11:14:08.280 回答
4

我知道这个帖子有点老了,但我还是有话要说。有一个函数(或宏,我还没有检查库) malloc_usable_size() - 获取从堆分配的内存块的大小。手册页指出它仅用于调试,因为它输出的不是您询问的数字,而是它分配的数字,这个数字要大一些。请注意,这是一个 GNU 扩展。

另一方面,它甚至可能不需要,因为我相信释放内存块你不必知道它的大小。只需删除负责该块的句柄/描述符/结构。

于 2012-10-24T14:44:49.390 回答
3

一种非标准方式是使用_msize(). 使用此功能将使您的代码不可移植。此外,关于它是否会返回传入的数字malloc()或实际块大小(可能更大),文档也不是很清楚。

于 2010-04-16T06:15:59.377 回答
2

malloc如何存储这些数据取决于实现者。大多数情况下,长度直接存储在分配的内存前面(也就是说,如果要分配 7 个字节,实际上分配了 7+x 个字节,其中 x 个附加字节用于存储元数据)。有时,元数据会存储在分配内存之前和之后,以检查堆损坏。但是实现者也可以选择使用额外的数据结构来存储元数据。

于 2010-04-16T06:01:00.627 回答
1

您可以分配更多内存来存储大小:

void my_malloc(size_t n,size_t size ) 
{
    void *p = malloc( (n * size) + sizeof(size_t) );
    if( p == NULL ) return NULL;
    *( (size_t*)p) = n;
    return (char*)p + sizeof(size_t);
}

void my_free(void *p)
{
    free( (char*)p - sizeof(size_t) );
}

void my_realloc(void *oldp,size_t new_size)
{
    // ...
}

int main(void)
{
    char *p = my_malloc( 20, 1 );
    printf("%lu\n",(long int) ((size_t*)p)[-1] );
    return 0;
}
于 2010-06-01T13:39:27.120 回答
0

要回答关于 delete[] 的问题,C++ 的早期版本实际上要求您调用 delete[n] 并告诉运行时大小,因此它不必存储它。可悲的是,这种行为被删除为“太令人困惑”。

(详见 D&E。)

于 2011-01-01T19:41:28.167 回答