【问题标题】:How can I get the size of an array from a pointer in C?如何从 C 中的指针获取数组的大小?
【发布时间】:2010-09-18 23:05:32
【问题描述】:

我已经分配了一个mystruct 大小为n 的“数组”,如下所示:

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

后来,我只能访问p,而不再拥有n。有没有一种方法可以确定仅给定指针p 的数组的长度?

我认为它必须是可能的,因为free(p) 就是这样做的。我知道malloc() 会跟踪它分配了多少内存,这就是它知道长度的原因;也许有办法查询这些信息?比如……

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

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

【问题讨论】:

  • 虽然答案都是“正确地做”,但这是一个非常好的问题。所以有一个upvote ;)

标签: c memory-management pointers malloc


【解决方案1】:

不,如果不强烈依赖malloc 的实现细节,就无法获取此信息。特别是,malloc 可能会分配比您请求更多的字节(例如,为了提高特定内存架构的效率)。重新设计你的代码会更好,这样你就可以明确地跟踪n。另一种选择是至少同样多的重新设计和更危险的方法(鉴于它是非标准的,滥用指针的语义,并且对于那些追随你的人来说将是维护的噩梦): malloc 的地址处的 lengthn,后跟数组。那么分配将是:

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;

并传递arrInfos 而不是原始指针。

现在我们正在做饭。但只要你在重新设计,为什么要停在这里?您真正想要的是抽象数据类型 (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++。上帝帮助我们所有人。

【讨论】:

  • @Joel - 想想 delete [] *p 如何设法调用 p 指向的数组中的所有析构函数 - 那是因为 new 做了与 bary 建议相同的事情。 new 将数组中的项目数存储在数组的开头,并为您提供超过第一个位置的指针。
  • @computinglife - 不一定,分配器可以轻松地将元数据保存在与它正在分发的位不同的内存部分中,以防止缓冲区溢出破坏内部数据结构,或者将数字放在几个字节中早一点。
  • 事实上,glibc 的默认分配器将大小直接放在返回的指针之前,但使用低位元数据——因此数字必须被屏蔽才能准确。
  • 你不能像这样对void *p 进行算术运算。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    只是为了确认之前的答案:没有办法知道,仅仅通过研究一个指针,返回这个指针的 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 语言中,您始终可以编写自己的分配器,但同样,当您只需要记住分配了多少内存时,这可能太麻烦了。

    【讨论】:

    • get_size 必须传递一个指向已分配块开头的指针这一事实并不妨碍拥有它。只是不要传入无效值。 free() 具有相同的约束,并且存在...
    • 当然可以,但是 free 通常在使用分配内存的 malloc 时考虑到这一点。 get_size 将在任何地方使用,包括用户不应该知道内存是如何完全分配的(在堆栈上、通过池等)。
    • +1 以获得出色的解释。我唯一的缺点是:如果它有效并且你可以用它做什么有限制怎么办?正如 dmkee 在 cmets 的其他地方指出的那样,在 OSX(我的平台)上,它被称为 malloc_size(),它完全按照需要工作。有“你不能那样做”和“如果你要那样做,你应该非常小心”——两件截然不同的事情! :)
    【解决方案4】:

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

    【讨论】:

    • 在 OSX 上称为 malloc_size。
    【解决方案5】:

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

    按如下方式分配所有数组:

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

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

    一定要free那个指针,而不是数组指针本身!

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

    【讨论】:

    • 对可移植代码没有好处,因为如果 mystruct 包含对齐要求大于 sizeof(int) 的任何成员,它就不起作用。显然,在 sizeof(int) 是任何类型的最大对齐要求的倍数的平台上,这不是问题,但会与 SPARC 上的 -mfaster-structs 冲突。
    【解决方案6】:

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

    【讨论】:

      【解决方案7】:

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

      
      int size;
      struct mystruct *cur;
      
      for (cur = myarray; cur->name != NULL; cur++)
          ;
      
      size = cur - myarray;
      

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

      【讨论】:

        【解决方案8】:

        其他人讨论了纯 c 指针的限制和malloc()stdlib.h 实现。一些实现提供了返回分配的块大小的扩展,它可能大于请求的大小。

        如果您必须有这种行为,您可以使用或编写专门的内存分配器。最简单的做法是在 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 */
        ...
        

        【讨论】:

          【解决方案9】:

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

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

          【讨论】:

            【解决方案10】:

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

            为什么不能存储分配的内存大小?

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

            【讨论】:

              【解决方案11】:

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

              【讨论】:

              • 实际上这就是为什么您应该能够询问 malloc 库一个块有多大的完美理由。对我来说,C 语言的设计没有这样的查询功能从来没有意义。
              • 我曾经在一个系统上工作,其中标准分配函数返回块及其实际大小(>= 当然是请求的大小)。适用于缓冲区和缓存之类的东西,您可以在其中有利地利用任何多余的空间。
              • c-the-language 是一种方便的汇编表达方式。标准库是最小的,因为它适合最初运行的系统的严格限制(并且仍然在嵌入式领域)。如果您想要一个提供许多花里胡哨的分配器,请使用一个。
              【解决方案12】:

              这是对我的排序例程的测试。它设置了 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),问题是只有一个指向动态分配数组的指针。
              【解决方案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))
              

              【讨论】:

                【解决方案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
                

                【讨论】:

                  猜你喜欢
                  • 2021-07-23
                  • 1970-01-01
                  • 1970-01-01
                  • 2020-09-08
                  • 2018-02-15
                  相关资源
                  最近更新 更多