提问人:Joel 提问时间:10/24/2008 最后编辑:Ryan SteinJoel 更新时间:9/16/2022 访问量:48058
如何从 C 中的指针获取数组的大小?
How can I get the size of an array from a pointer in C?
问:
我分配了一个大小为这样的“数组”: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
答:
自己跟踪数组大小;free 使用 malloc 链来释放已分配的块,该块的大小不一定与您请求的数组相同
我不知道有什么办法,但我想它会处理 malloc 内部的混乱,这通常是一个非常非常糟糕的主意。
为什么无法存储分配的内存大小?
编辑:如果你知道你应该重新编写代码,所以你知道n,那么,去做吧。是的,尝试轮询 malloc 可能既快速又容易,但确定知道 n 可以最大限度地减少混淆并加强设计。
malloc 将返回一个内存块,该内存块至少与您请求的内存块一样大,但可能更大。因此,即使您可以查询块大小,也无法可靠地为您提供数组大小。因此,您只需要修改代码即可自己跟踪它。
您不能询问 malloc 库块有多大的原因之一是,分配器通常会将请求的大小四舍五入以满足一些最小粒度要求(例如,16 字节)。因此,如果您要求 5 个字节,您将得到一个大小为 16 的块。如果你取 16 除以 5,当你实际上只分配一个元素时,你会得到三个元素。malloc 库需要额外的空间来跟踪您首先请求的字节数,因此最好自己跟踪它。
评论
不可以,如果不严格依赖 的实现细节,就无法获取此信息。特别是,分配的字节数可能比您请求的要多(例如,为了提高特定内存架构的效率)。最好重新设计代码,以便显式跟踪。另一种选择是至少同样多的重新设计和更危险的方法(鉴于它是非标准的,滥用了指针的语义,并且对后来者来说将是一场维护噩梦):将长度存储在 malloc'd 地址,然后是数组。然后分配将是:malloc
malloc
n
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;
并传递 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++。上帝帮助我们所有人。
评论
void *p
只是为了确认前面的答案:没有办法知道,仅仅通过研究一个指针,返回这个指针的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 语言中,你总是可以编写自己的分配器,但同样,当你只需要记住分配了多少内存时,这可能太麻烦了。
评论
malloc_size()
我可以推荐一种糟糕的方法吗?
按如下方式分配所有阵列:
void *blockOfMem = malloc(sizeof(mystruct)*n + sizeof(int));
((int *)blockofMem)[0] = n;
mystruct *structs = (mystruct *)(((int *)blockOfMem) + 1);
然后,您可以随时将数组转换为并访问 -1st 元素。int *
请确保该指针,而不是数组指针本身!free
此外,这可能会导致可怕的错误,让您撕掉头发。也许您可以将 alloc 函数包装在 API 调用或其他东西中。
评论
对于指针数组,可以使用以 NULL 结尾的数组。然后可以像使用字符串一样确定长度。在您的示例中,您可以使用结构属性来标记然后结束。当然,这取决于是否存在不能为 NULL 的成员。因此,假设您有一个属性名称,需要为数组中的每个结构设置该名称,然后您可以通过以下方式查询大小:
int size;
struct mystruct *cur;
for (cur = myarray; cur->name != NULL; cur++)
;
size = cur - myarray;
顺便说一句,在您的示例中,它应该是 calloc(n, sizeof(struct mystruct))。
一些编译器提供了 msize() 或类似的函数(_msize() 等),可以让你做到这一点
评论
其他人则讨论了普通 c 指针的局限性和 的实现。某些实现提供扩展,这些扩展返回分配的块大小,该块大小可能大于请求的大小。stdlib.h
malloc()
如果必须具有此行为,则可以使用或编写专用的内存分配器。最简单的事情就是在函数周围实现一个包装器。比如: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 */
...
这是对我的排序例程的测试。它设置 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;
}
评论
arr
实际上,您的问题是 - “我能找出 malloc'd(或 calloc'd)数据块的大小吗”。正如其他人所说:不,不是以标准的方式。
但是,有一些自定义的 malloc 实现可以做到这一点 - 例如 http://dmalloc.com/
在 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))
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
这是我的方法:
#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
评论
如果编译器支持 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;
}
尝试查看堆结构的问题在于,布局可能因平台而异或因版本而异,因此可能无法可靠地获取信息。
即使您确切地知道如何查看分配器维护的元信息,存储在那里的信息也可能与数组的大小无关。分配器只是返回可用于满足请求大小的内存,但内存的实际大小可能比请求的大小大(甚至可能大得多)。
了解信息的唯一可靠方法是自己找到跟踪信息的方法。
评论
*p
p
评论