如何从 C 中的指针获取数组的大小?

How can I get the size of an array from a pointer in C?

提问人:Joel 提问时间:10/24/2008 最后编辑:Ryan SteinJoel 更新时间:9/16/2022 访问量:48058

问:

我分配了一个大小为这样的“数组”:mystructn

if (NULL == (p = calloc(sizeof(struct mystruct) * n,1))) {
 /* handle error */
}

后来,我只能访问 ,而不再拥有 .有没有办法确定仅给定指针的数组长度?pnp

我认为这一定是可能的,因为确实如此。我知道它会跟踪它分配了多少内存,这就是它知道长度的原因;也许有一种方法可以查询这些信息?像...free(p)malloc()

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

我知道我应该重新编写代码以便我知道,但如果可能的话,我宁愿不要。有什么想法吗?n

c 内存管理 指针 malloc

评论

8赞 David Arno 10/24/2008
虽然答案都是“正确地做”,但这是一个非常好的问题。所以有一个赞成;)

答:

16赞 Steven A. Lowe 10/24/2008 #1

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

1赞 Bob Somers 10/24/2008 #2

我不知道有什么办法,但我想它会处理 malloc 内部的混乱,这通常是一个非常非常糟糕的主意。

为什么无法存储分配的内存大小?

编辑:如果你知道你应该重新编写代码,所以你知道n,那么,去做吧。是的,尝试轮询 malloc 可能既快速又容易,但确定知道 n 可以最大限度地减少混淆并加强设计。

2赞 David Arno 10/24/2008 #3

malloc 将返回一个内存块,该内存块至少与您请求的内存块一样大,但可能更大。因此,即使您可以查询块大小,也无法可靠地为您提供数组大小。因此,您只需要修改代码即可自己跟踪它。

1赞 Greg Hewgill 10/24/2008 #4

您不能询问 malloc 库块有多大的原因之一是,分配器通常会将请求的大小四舍五入以满足一些最小粒度要求(例如,16 字节)。因此,如果您要求 5 个字节,您将得到一个大小为 16 的块。如果你取 16 除以 5,当你实际上只分配一个元素时,你会得到三个元素。malloc 库需要额外的空间来跟踪您首先请求的字节数,因此最好自己跟踪它。

评论

5赞 Windows programmer 10/24/2008
实际上,这就是为什么您应该能够询问 malloc 库一个块有多大的完美理由。对我来说,C语言的设计没有这样的查询功能是没有意义的。
3赞 Steve Jessop 10/24/2008
我曾经研究过一个系统,其中标准分配函数同时返回块及其实际大小(当然>= 请求的大小)。适用于缓冲区和缓存等,您可以在其中有利可图地使用任何多余的空间。
0赞 dmckee --- ex-moderator kitten 10/25/2008
c-the-language 是 assembly 的一种方便的表达方式。标准库是最小的,因为它适合它最初运行的系统的严格约束(在嵌入式土地上仍然如此)。如果您想要一个提供大量花里胡哨的分配器,请使用一个。
56赞 Barry Wark 10/24/2008 #5

不可以,如果不严格依赖 的实现细节,就无法获取此信息。特别是,分配的字节数可能比您请求的要多(例如,为了提高特定内存架构的效率)。最好重新设计代码,以便显式跟踪。另一种选择是至少同样多的重新设计和更危险的方法(鉴于它是非标准的,滥用了指针的语义,并且对后来者来说将是一场维护噩梦):将长度存储在 malloc'd 地址,然后是数组。然后分配将是:mallocmallocnn

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;

并传递 s 而不是原始指针。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赞 computinglife 10/25/2008
@Joel - 有没有想过 delete [] *p 如何设法调用 p 指向的数组中的所有析构函数 - 嗯,这是因为 new 做了与 bary 建议相同的事情。new 将数组中的项数存储在数组的开头,并为您提供经过这第一个位置的指针。
1赞 ephemient 10/29/2008
@computinglife - 不一定,分配器可以轻松地将元数据保存在与它分发的位不同的内存部分,以防止缓冲区溢出损坏内部数据结构,或者将数字放在前面几个字节。
1赞 ephemient 10/29/2008
事实上,glibc 的默认分配器将大小放在返回的指针之前,但将较低的位用于元数据 - 因此必须屏蔽数字才能准确。
1赞 unwind 4/15/2016
你不能在这样的地方做算术。void *p
9赞 paercebal 10/24/2008 #6

只是为了确认前面的答案:没有办法知道,仅仅通过研究一个指针,返回这个指针的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赞 Steve Jessop 10/24/2008
必须向get_size传递指向已分配块开头的指针这一事实并不妨碍它。只是不要传入无效值。free() 具有相同的约束,并且存在......
0赞 paercebal 10/25/2008
当然,但 free 通常在考虑这一点的情况下使用,沿着分配内存的 malloc。get_size将在任何地方使用,包括用户不应该知道内存是如何分配的(在堆栈上,通过池等)。
0赞 Olie 5/29/2013
+1 表示出色的解释。我唯一的问题是:如果它有效并且对你可以用它做什么有限制怎么办?正如 dmkee 在评论中指出的那样,在 OSX(我的平台)上,它被称为 ,并且完全按照预期工作。有“你不能那样做”和“如果你要那样做,你应该非常小心”——这是两件截然不同的事情!:)malloc_size()
4赞 Claudiu 10/24/2008 #7

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

按如下方式分配所有阵列:

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

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

然后,您可以随时将数组转换为并访问 -1st 元素。int *

请确保该指针,而不是数组指针本身!free

此外,这可能会导致可怕的错误,让您撕掉头发。也许您可以将 alloc 函数包装在 API 调用或其他东西中。

评论

3赞 Steve Jessop 10/24/2008
对可移植代码没有好处,因为如果 mystruct 包含任何对齐要求大于 sizeof(int) 的成员,它就不起作用。显然,在sizeof(int)是任何类型最大对齐要求的倍数的平台上,这不是问题,但会与SPARC上的-mfaster-structs等问题相悖。
2赞 quinmars 10/24/2008 #8

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


int size;
struct mystruct *cur;

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

size = cur - myarray;

顺便说一句,在您的示例中,它应该是 calloc(n, sizeof(struct mystruct))。

8赞 dmityugov 10/24/2008 #9

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

评论

5赞 dmckee --- ex-moderator kitten 10/25/2008
它在 OSX 上被称为 malloc_size。
2赞 dmckee --- ex-moderator kitten 10/25/2008 #10

其他人则讨论了普通 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 */
...
1赞 Wm J 9/3/2010 #11

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

神奇之处在于对 myMax 的调用:

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

这很神奇,不是吗?

myMax 需要一个浮点数组指针 (float *),所以我使用 &arr 获取数组的地址,并将其转换为浮点指针。

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

注:当您的数据可能具有不同数量的样本时,所有这些都很重要。

测试代码如下:

#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;
}

评论

0赞 CB Bailey 9/4/2010
我认为您需要再次阅读这个问题。在您的答案中,您使用的是静态分配数组的名称 (),问题是关于只有一个指向动态分配数组的指针。arr
2赞 pm100 9/4/2010 #12

实际上,您的问题是 - “我能找出 malloc'd(或 calloc'd)数据块的大小吗”。正如其他人所说:不,不是以标准的方式。

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

0赞 Jonathon Reinhart 9/19/2015 #13

uClibc 中,malloc.h 中有一个宏:MALLOC_SIZE

/* The size of a malloc allocation is stored in a size_t word
   MALLOC_HEADER_SIZE bytes prior to the start address of the allocation:

     +--------+---------+-------------------+
     | SIZE   |(unused) | allocation  ...   |
     +--------+---------+-------------------+
     ^ BASE             ^ ADDR
     ^ ADDR - MALLOC_HEADER_SIZE
*/

/* The amount of extra space used by the malloc header.  */
#define MALLOC_HEADER_SIZE          \
  (MALLOC_ALIGNMENT < sizeof (size_t)       \
   ? sizeof (size_t)                \
   : MALLOC_ALIGNMENT)

/* Set up the malloc header, and return the user address of a malloc block. */
#define MALLOC_SETUP(base, size)  \
  (MALLOC_SET_SIZE (base, size), (void *)((char *)base + MALLOC_HEADER_SIZE))
/* Set the size of a malloc allocation, given the base address.  */
#define MALLOC_SET_SIZE(base, size) (*(size_t *)(base) = (size))

/* Return base-address of a malloc allocation, given the user address.  */
#define MALLOC_BASE(addr)   ((void *)((char *)addr - MALLOC_HEADER_SIZE))
/* Return the size of a malloc allocation, given the user address. */
#define MALLOC_SIZE(addr)   (*(size_t *)MALLOC_BASE(addr))
0赞 Parampreet Rai 7/20/2021 #14

malloc()在实际分配的空间的 8 个字节之前存储有关空间分配的元数据。这可用于确定缓冲区的空间。在我的 x86-64 上,这总是返回 16 的倍数。因此,如果分配的空间是 16 的倍数(在大多数情况下),则可以使用:

法典

#include <stdio.h>
#include <malloc.h>

int size_of_buff(void *buff) {
        return ( *( ( int * ) buff - 2 ) - 17 ); // 32 bit system: ( *( ( int * ) buff - 1 ) - 17 )
}

void main() {
        char *buff = malloc(1024);
        printf("Size of Buffer: %d\n", size_of_buff(buff));
}

输出

Size of Buffer: 1024
0赞 Aboutaleb Roshan 9/16/2022 #15

这是我的方法:

#include <stdio.h>
#include <stdlib.h>

typedef struct _int_array
{
    int *number;
    int size;
} int_array;

int int_array_append(int_array *a, int n)
{
    static char c = 0;
    if(!c)
    {
        a->number = NULL;
        a->size = 0;
        c++;
    }

    int *more_numbers = NULL;

    a->size++;
    more_numbers = (int *)realloc(a->number, a->size * sizeof(int));
    if(more_numbers != NULL)
    {
        a->number = more_numbers;
        a->number[a->size - 1] = n;
    }
    else
    {
        free(a->number);
        printf("Error (re)allocating memory.\n");
        return 1;
    }

    return 0;
}

int main()
{
    int_array a;

    int_array_append(&a, 10);
    int_array_append(&a, 20);
    int_array_append(&a, 30);
    int_array_append(&a, 40);

    int i;
    for(i = 0; i < a.size; i++)
        printf("%d\n", a.number[i]);

    printf("\nLen: %d\nSize: %d\n", a.size, a.size * sizeof(int));

    free(a.number);
    return 0;
}

输出:

10
20
30
40

Len: 4
Size: 16

评论

1赞 Andrew 9/20/2022
欢迎来到 SO...答案旨在帮助人们 - 仅仅列出一些代码而不解释它的作用或原因并不是特别有用。请编辑您的答案以解释您在做什么?
0赞 jxh 9/16/2022 #16

如果编译器支持 VLA(可变长度数组),则可以将数组长度嵌入到指针类型中。

int n = 10;
int (*p)[n] = malloc(n * sizeof(int));
n = 3;
printf("%d\n", sizeof(*p)/sizeof(**p));

输出为 10。

您还可以选择使用包含灵活数组成员的结构自行将信息嵌入到分配的内存中。

struct myarray {
    int n;
    struct mystruct a[];
};

struct myarray *ma =
    malloc(sizeof(*ma) + n * sizeof(struct mystruct));
ma->n = n;
struct mystruct *p = ma->a;

然后,要恢复尺寸,您需要减去挠性杆件的偏移量。

int get_size (struct mystruct *p) {
    struct myarray *ma;
    char *x = (char *)p;
    ma = (void *)(x - offsetof(struct myarray, a));
    return ma->n;
}

尝试查看堆结构的问题在于,布局可能因平台而异或因版本而异,因此可能无法可靠地获取信息。

即使您确切地知道如何查看分配器维护的元信息,存储在那里的信息也可能与数组的大小无关。分配器只是返回可用于满足请求大小的内存,但内存的实际大小可能比请求的大小大(甚至可能大得多)。

了解信息的唯一可靠方法是自己找到跟踪信息的方法。

评论

0赞 John Bollinger 9/16/2022
是的,这可能很有用,但生成的指针不能与最终目标类型的数组互换使用。实际上,这并不能回答提出的问题:它不会给出分配数组的大小,而是给出所述数组的一个元素的大小。
0赞 jxh 9/16/2022
@JohnBollinger 您的意思是必须更改代码才能通过而不是 .是的,这是该方法的局限性。*pp