如何在C语言中通过指针获取数组的大小?

71
我已分配了一个大小为 nmystruct "数组",代码如下:
if (NULL == (p = calloc(sizeof(struct mystruct) * n,1))) {
 /* handle error */
}

后来,我只能访问p,而不再有n。有没有办法仅凭指针p确定数组的长度?

我想这一定是可能的,因为free(p)就是这么做的。我知道malloc()跟踪其分配了多少内存,这就是它知道长度的原因;也许有一种方法可以查询这些信息?类似于...

int length = askMallocLibraryHowMuchMemoryWasAlloced(p) / sizeof(mystruct)

我知道我应该重构代码,以便我知道n是什么,但如果可能的话,我宁愿不这样做。有任何想法吗?


8
虽然所有答案都是“做好它”,但这真是一个很好的问题。所以我给你点赞 ;) - David Arno
16个回答

56

不,没有办法在不强烈依赖于malloc的实现细节的情况下获取此信息。特别是,malloc可能会分配比您请求的更多字节(例如,在特定内存架构中为了效率)。更好的方法是重新设计代码,以便显式跟踪n。另一种选择至少需要进行同样多的重新设计,并且是一种更加危险的方法(因为它是非标准的,滥用指针的语义,并且对于接替你的人来说将是一个维护噩梦):在malloc的地址处存储长度n,然后是数组。分配将是:

void *p = calloc(sizeof(struct mystruct) * n + sizeof(unsigned long int),1));
*((unsigned long int*)p) = n;

n现在存储在*((unsigned long int*)p),您的数组开始位置现在为

void *arr = p+sizeof(unsigned long int);

编辑:只是为了反驳一下……我知道这些“解决方案”都需要重新设计,但让我们来看看吧。 当然,上面提出的解决方案只是一个(精心打包的)结构体的hacky实现。你也可以定义:

typedef struct { 
  unsigned int n;
  void *arr;
} arrInfo;

而不是传递原始指针,可以传递arrInfo

现在我们马上就可以开始。但只要你重新设计,为什么要止步于此呢?你真正想要的是一个抽象数据类型(ADT)。任何算法和数据结构课程的介绍性文本都可以做到这一点。ADT定义了数据类型的公共接口,但隐藏了该数据类型的实现。因此,对于数组而言,公开的ADT可能如下所示:

typedef void* arrayInfo;
(arrayInfo)newArrayInfo(unsignd int n, unsigned int itemSize);
(void)deleteArrayInfo(arrayInfo);
(unsigned int)arrayLength(arrayInfo);
(void*)arrayPtr(arrayInfo);
...

换句话说,ADT 是一种数据和行为封装形式...... 换句话说,这已经是用纯 C 最接近面向对象编程的方法了。除非你被困在没有 C++ 编译器的平台上,否则最好使用 STL 的 std::vector

如此一来,我们把一个简单的关于 C 的问题升级到了 C++ 。愿神帮助我们所有人。


2
@Joel - 你有没有想过delete [] *p是如何调用指向p的数组中所有析构函数的 - 这是因为new执行了Bary建议的相同操作。new在数组开头存储了数组中项目的数量,并给出了指向第一个位置之后的指针。 - computinglife
1
@computinglife - 不一定,分配器可以轻松地将元数据保存在与它分配的位不同的内存部分中,以防止缓冲区溢出破坏内部数据结构,或者将数字放置几个字节之前。 - ephemient
1
事实上,glibc的默认分配器将大小放在返回指针之前,但使用低位用于元数据 - 因此必须屏蔽该数字以确保准确性。 - ephemient
1
你不能这样在 void *p 上进行算术运算。 - unwind

16

您需要自己跟踪数组大小;free函数使用malloc链来释放已分配的,该块的大小不一定与您请求的数组大小相同。


9

为了确认之前的答案:仅通过研究指针无法知道malloc分配了多少内存。

如果这是可行的呢?

这是不可能的一个例子。假设有一个名为get_size(void *)的函数,它返回为指针分配的内存:

typedef struct MyStructTag
{ /* etc. */ } MyStruct ;

void doSomething(MyStruct * p)
{
   /* well... extract the memory allocated? */
   size_t i = get_size(p) ;
   initializeMyStructArray(p, i) ;
}

void doSomethingElse()
{
   MyStruct * s = malloc(sizeof(MyStruct) * 10) ; /* Allocate 10 items */
   doSomething(s) ;
}

为什么即使它工作了,也不会起作用?

但这种方法的问题在于,在C语言中,您可以玩弄指针算术。让我们重写doSomethingElse():

void doSomethingElse()
{
   MyStruct * s = malloc(sizeof(MyStruct) * 10) ; /* Allocate 10 items */
   MyStruct * s2 = s + 5 ; /* s2 points to the 5th item */
   doSomething(s2) ; /* Oops */
}

如何使用get_size函数,当你传递给该函数一个有效的指针,但不是由malloc返回的指针时,该函数应该如何工作。即使get_size费尽周折找到了大小(以低效的方式),在这种情况下它返回的值也会在您的上下文中错误。
结论
总有办法避免这个问题,在C语言中,您始终可以编写自己的分配器,但是当您只需要记住分配了多少内存时,这可能太麻烦了。

7
get_size必须传递指向已分配块起始位置的指针这一事实并不妨碍使用它。只需要不传入无效的值即可。free()具有相同的限制,并且也存在类似的要求... - Steve Jessop
当然,但free通常与分配内存的malloc一起使用。get_size将在任何地方使用,包括用户不应该知道内存是如何分配的(在堆栈、通过池等)。 - paercebal
优秀的解释,点赞!我的唯一疑问是:如果它能够工作并且有限制,那该怎么办?正如dmkee在评论中指出的,在我的平台OSX上,它被称为malloc_size(),并且它的工作方式完全符合要求。有"你不能这样做"和"如果你要这样做,你应该非常小心"这两种情况,它们是两个非常不同的事情! :) - Olie

8
一些编译器提供了msize()或类似的函数(_msize()等),可以让您做到这一点。

5
在OSX中它被称为malloc_size。 - dmckee --- ex-moderator kitten

4

我可以推荐一种糟糕的方法吗?

按照以下方式分配所有数组:

void *blockOfMem = malloc(sizeof(mystruct)*n + sizeof(int));

((int *)blockofMem)[0] = n;
mystruct *structs = (mystruct *)(((int *)blockOfMem) + 1);

那么你可以将数组转换为int *并访问倒数第一个元素。

一定要释放指针,而不是数组指针本身!

此外,这可能会导致可怕的错误,让你抓狂。也许你可以在API调用中包装分配函数之类的东西。


3
如果mystruct包含的任何成员的对齐要求大于sizeof(int),那么它对于可移植代码没有好处。在某些平台上,如sizeof(int)是任何类型的最大对齐需求的倍数时,这不是问题,但在SPARC上使用-mfaster-structs则会出现问题。 - Steve Jessop

2

对于指针数组,您可以使用以NULL结尾的数组。然后可以像字符串一样确定长度。在您的示例中,您可以使用结构属性来标记结束。当然,这取决于是否存在不能为NULL的成员。因此,假设您有一个名为name的属性,需要为数组中的每个结构设置该属性,则可以通过以下方式查询大小:


int size;
struct mystruct *cur;

for (cur = myarray; cur->name != NULL; cur++)
    ;

size = cur - myarray;

顺便提一下,在你的示例中应该使用calloc(n, sizeof(struct mystruct))。


2

其他人已经讨论了普通c指针和stdlib.hmalloc()的限制。一些实现提供了返回分配的块大小的扩展,这可能比请求的大小更大。

如果您必须具有此行为,则可以使用或编写专用内存分配器。最简单的方法是实现一个stdlib.h函数的包装器。类似于以下内容:

void* my_malloc(size_t s);     /* Calls malloc(s), and if successful stores 
                                  (p,s) in a list of handled blocks */
void my_free(void* p);         /* Removes list entry and calls free(p) */
size_t my_block_size(void* p); /* Looks up p, and returns the stored size */
...

2

你的问题实际上是:“我能否找出malloc(或calloc)数据块的大小。”正如其他人所说:没有标准方法可以做到这一点。

但是,有一些自定义的malloc实现可以做到这一点——例如http://dmalloc.com/


2

malloc会返回至少与您请求的大小相同的内存块,但可能更大。因此,即使您可以查询块大小,这也不能可靠地给出数组大小。所以你必须修改你的代码来自己跟踪它。


1

这是我的排序程序的测试。它设置了7个变量来保存浮点值, 然后将它们分配到一个数组中, 用于查找最大值。

魔法在于对myMax的调用:

float mmax = myMax((float *)&arr,(int) sizeof(arr)/sizeof(arr[0]));

那真是神奇,不是吗?

myMax期望一个浮点数数组指针(float *),因此我使用&arr获取数组的地址,并将其强制转换为浮点数指针。

myMax还期望数组中元素的数量作为int类型。我通过使用sizeof()函数获取数组和第一个元素的字节数,然后将总字节数除以每个元素的字节数来获得该值。(我们不应该猜测或硬编码int的大小,因为在某些系统上它是2个字节,在一些系统(例如我的OS X Mac)上它是4个字节,在其他系统上可能是其他值)。

注意:当数据样本数量可能变化时,所有这些都很重要。

以下是测试代码:

#include <stdio.h>

float a, b, c, d, e, f, g;

float myMax(float *apa,int soa){
 int i;
 float max = apa[0];
 for(i=0; i< soa; i++){
  if (apa[i]>max){max=apa[i];}
  printf("on i=%d val is %0.2f max is %0.2f, soa=%d\n",i,apa[i],max,soa);
 }
 return max;
}

int main(void)
{
 a = 2.0;
 b = 1.0;
 c = 4.0;
 d = 3.0;
 e = 7.0;
 f = 9.0;
 g = 5.0;
 float arr[] = {a,b,c,d,e,f,g};

 float mmax = myMax((float *)&arr,(int) sizeof(arr)/sizeof(arr[0]));
 printf("mmax = %0.2f\n",mmax);

 return 0;
}

我认为你需要再次阅读问题。在你的回答中,你使用了一个静态分配数组(arr)的名称,而问题只涉及到拥有一个指向动态分配数组的指针。 - CB Bailey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接