如何找到数组的大小(从指向数组第一个元素的指针)?

393
首先,这里有一些代码:
int main() 
{
    int days[] = {1,2,3,4,5};
    int *ptr = days;
    printf("%u\n", sizeof(days));
    printf("%u\n", sizeof(ptr));

    return 0;
}

有没有办法找出指针ptr所指向的数组的大小(而不仅仅是给出它的大小,32位系统上为四个字节)?

94
我一直在使用sizeof和括号 - 虽然这使它看起来像函数调用,但我认为这更清晰。 - Paul Tomblin
21
为什么不呢?你是否反对使用多余的括号?我个人认为加上括号后读起来更加流畅。 - David Thornley
6
@Paul:假设调用的左侧是一个整型指针,我会把它写成 int *ptr = malloc(4 * sizeof *ptr); 这样对我来说更清晰。少了需要阅读的括号,并且将字面常量放在前面,就像数学中一样。 - unwind
4
不要分配指针数组,如果你想要的是整数数组! - Paul Tomblin
6
这里没有“指向数组的指针”,只有一个指向整数的指针。 - newacct
显示剩余3条评论
17个回答

343
不,你不能这样做。编译器不知道指针指向的是什么。有一些技巧,比如用一个已知的特殊值来结束数组,然后计算数组的大小直到那个值,但这不是使用sizeof()的方法。
另一个技巧是Zan提到的方法,就是将大小存储在某个地方。例如,如果你动态分配数组,可以分配一个比所需大小大一个size_t的块,将大小存储在其中,并将ptr+sizeof(size_t)作为数组的指针返回。当你需要大小时,将指针减小并查看存储的值。只需记住从开头释放整个块,而不仅仅是数组部分。

22
非常抱歉回复评论晚了,但是如果编译器不知道指针指向哪里,那么 free 函数怎么知道要清除多少内存呢?我知道这些信息被存储在内部以供 free 等函数使用。所以我的问题是为什么编译器不能也这样做呢? - viki.omega9
18
@viki.omega9,因为自由发现大小是在运行时确定的。编译器无法知道大小,因为您可以根据运行时因素(命令行参数、文件内容、月相等)使数组大小不同。 - Paul Tomblin
21
为什么没有一个能像free函数一样返回大小的函数?快速跟进一下。 - viki.omega9
5
如果您可以保证该函数仅使用malloc分配的内存,并且该库跟踪了malloced内存的方式与我看到的大多数方式相同(通过在返回指针之前使用int),那么您可以编写一个函数。但是,如果指针指向静态数组或类似内容,则会失败。同样,并不能保证malloced内存的大小对您的程序可访问。 - Paul Tomblin
15
@viki.omega9: 另一个需要记住的事情是,由malloc/free系统记录的大小可能不是您所请求的大小。您申请了9个字节,却得到了16个字节。申请了3K字节,却得到了4K字节。或类似的情况。 - Zan Lynx
显示剩余12条评论

116

答案是,“不”。

C语言程序员的做法是在某个地方存储数组的大小。它可以是结构体的一部分,或者程序员可以有些取巧地使用malloc()比所请求的内存多来存储一个长度值,以便在数组开始之前存储它。


3
帕斯卡字符串的实现方式如下。 - dsm
8
显然,Pascal字符串是Excel运行如此之快的原因! - Adam Naylor
8
@Adam:它很快。我在自己实现的字符串列表中使用它。它非常快速地进行线性搜索,因为它是这样做的:加载大小,预取位置+大小,将大小与搜索大小进行比较,如果相等,则使用strncmp,移动到下一个字符串,重复此操作。在大约500个字符串以下,它比二分查找更快。 - Zan Lynx

57

对于动态数组(使用malloc或C++的new),您需要像其他人提到的那样存储数组的大小,或者可能构建一个处理添加、删除、计数等操作的数组管理器结构。不幸的是,C语言不像C++那样做得很好,因为您基本上必须为每种不同的要存���的数组类型构建它,如果您需要管理多种类型的数组,则会感到繁琐。

对于静态数组,如您示例中的数组,有一个常用的宏用于获取其大小,但不建议使用,因为它不检查参数是否真的是静态数组。实际代码中仍然使用这个宏,例如在Linux内核头文件中,尽管它可能与下面的宏略有不同:

#if !defined(ARRAY_SIZE)
    #define ARRAY_SIZE(x) (sizeof((x)) / sizeof((x)[0]))
#endif

int main()
{
    int days[] = {1,2,3,4,5};
    int *ptr = days;
    printf("%u\n", ARRAY_SIZE(days));
    printf("%u\n", sizeof(ptr));
    return 0;
}

你可以搜索一下有关使用此类宏的潜在问题。要小心谨慎。

如果可能的话,最好使用 C++标准库,例如 vector,它更安全、更易于使用。


13
ARRAY_SIZE是实用程序员常用的一种编程范例。 - Sanjaya R
6
是的,这是一个常见的模式。但你仍需要谨慎使用它,因为很容易忘记并在动态数组上使用它。 - Ryan
2
是的,说得好,但所问的问题是关于指针而不是静态数组的。 - Paul Tomblin
2
如果ARRAY_SIZE宏的参数是一个数组(即数组类型的表达式),那么它总是有效的。对于所谓的“动态数组”,您永远不会得到实际的“数组”(即数组类型的表达式)。 (当然,您不能,因为数组类型在编译时包括其大小。)您只会得到指向第一个元素的指针。您的反对意见“没有检查参数是否真的是静态数组”并不真正有效,因为它们是不同的,一个是数组,另一个则不是。 - newacct
4
有一个模板函数在流传,它能够完成同样的任务但会阻止指针的使用。 - Natalie Adams
显示剩余6条评论

19
有一个干净的解决方案是使用C++模板,而不使用sizeof。下面的getSize()函数返回任何静态数组的大小。
#include <cstddef>

template<typename T, std::size_t SIZE>
constexpr std::size_t getSize(T (&)[SIZE]) {
    return SIZE;
}

这是一个带有foo_t结构的示例:
#include <cstddef>
#include <cstdio>

template<typename T, std::size_t SIZE>
constexpr std::size_t getSize(T (&)[SIZE]) {
    return SIZE;
}

struct foo_t {
    int ball;
};

int main()
{
    foo_t foos3[] = {{1},{2},{3}};
    foo_t foos5[] = {{1},{2},{3},{4},{5}};
    std::printf("%u\n", getSize(foos3));
    std::printf("%u\n", getSize(foos5));
}

输出:

3
5

1
我从未见过符号 T (&)[SIZE]。你能解释一下它的含义吗?此外,您还可以在此上下文中提到constexpr。 - WorldSEnder
7
如果你使用C++并且确实有一个数组类型的变量,那就很好了。但在这个问题中,两者都不是。语言是C,而OP想要从一个简单的指针中获取数组大小。 - Oguk
这段代码会因为为每个不同的大小/类型组合重新创建相同的代码而导致代码膨胀吗?还是编译器会神奇地优化掉它们,使其不存在? - user2796283
@WorldSEnder:这是C++中引用数组类型的语法(没有变量名,只有大小和元素类型)。 - Peter Cordes
@user2796283:这个函数在编译时被完全优化掉了;不需要任何魔法,它不会将任何东西合并为单个定义,只是将其内联到编译时常量中。 (但在调试构建中,是的,您将拥有返回不同常量的多个单独函数。链接器魔法可能会合并使用相同常量的函数。调用者不会将 SIZE 作为参数传递,它是一个模板参数,必须已经被函数定义知道。) - Peter Cordes

11
正如所有正确的答案所述,您无法仅从数组的已损坏指针值中获取此信息。如果衰减的指针是函数接收到的参数,则必须以某种其他方式提供原始数组的大小,以便函数知道该大小。
以下是一个不同于迄今为止提供的建议,可以解决这个问题:传递一个指向数组的指针。该建议类似于C++样式建议,但C不支持模板或引用。
#define ARRAY_SZ 10

void foo (int (*arr)[ARRAY_SZ]) {
    printf("%u\n", (unsigned)sizeof(*arr)/sizeof(**arr));
}

但是,对于你的问题来说,这个建议有些愚蠢,因为该函数被定义为知道传入的数组的确切大小(因此,在数组上几乎没有使用sizeof的必要性)。不过,它确实提供了一些类型安全性。它会防止你传入一个不想要的大小的数组。

int x[20];
int y[10];
foo(&x); /* error */
foo(&y); /* ok */

如果该函数应该能够在任何大小的数组上运行,那么您将不得不提供大小作为额外信息传递给函数。

6
您可以这样做:
int days[] = { /*length:*/5, /*values:*/ 1,2,3,4,5 };
int *ptr = days + 1;
printf("array length: %u\n", ptr[-1]);
return 0;

6

对于这个具体的例子,如果您使用typedefs(请参见下文),是有办法的。当然,如果您以这种方式做,最好使用SIZEOF_DAYS,因为您知道指针指向的内容。

如果您有一个(void *)指针,例如malloc()或类似函数返回的指针,则无法确定指针指向哪个数据结构,因此无法确定其大小。

#include <stdio.h>

#define NUM_DAYS 5
typedef int days_t[ NUM_DAYS ];
#define SIZEOF_DAYS ( sizeof( days_t ) )

int main() {
    days_t  days;
    days_t *ptr = &days; 

    printf( "SIZEOF_DAYS:  %u\n", SIZEOF_DAYS  );
    printf( "sizeof(days): %u\n", sizeof(days) );
    printf( "sizeof(*ptr): %u\n", sizeof(*ptr) );
    printf( "sizeof(ptr):  %u\n", sizeof(ptr)  );

    return 0;
} 

输出:

SIZEOF_DAYS:  20
sizeof(days): 20
sizeof(*ptr): 20
sizeof(ptr):  4

5

没有什么神奇的解决方案。C语言不具备反射性,对象不能自动知道它们是什么。

但你有很多选择:

  1. 显然,添加参数
  2. 使用宏包装调用并自动添加参数
  3. 使用更复杂的对象。定义一个包含动态数组和数组大小的结构体,然后传递结构体的地址。

对象知道它们是什么。但如果你指向一个子对象,就无法获取关于完整对象或更大的子对象的信息。 - M.M

3

我的解决方案是将数组的长度保存到一个叫做Array的结构体中,作为有关该数组的元信息。

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

struct Array
{
    int length;

    double *array;
};

typedef struct Array Array;

Array* NewArray(int length)
{
    /* Allocate the memory for the struct Array */
    Array *newArray = (Array*) malloc(sizeof(Array));

    /* Insert only non-negative length's*/
    newArray->length = (length > 0) ? length : 0;

    newArray->array = (double*) malloc(length*sizeof(double));

    return newArray;
}

void SetArray(Array *structure,int length,double* array)
{
    structure->length = length;
    structure->array = array;
}

void PrintArray(Array *structure)
{       
    if(structure->length > 0)
    {
        int i;
        printf("length: %d\n", structure->length);
        for (i = 0; i < structure->length; i++)
            printf("%g\n", structure->array[i]);
    }
    else
        printf("Empty Array. Length 0\n");
}

int main()
{
    int i;
    Array *negativeTest, *days = NewArray(5);

    double moreDays[] = {1,2,3,4,5,6,7,8,9,10};

    for (i = 0; i < days->length; i++)
        days->array[i] = i+1;

    PrintArray(days);

    SetArray(days,10,moreDays);

    PrintArray(days);

    negativeTest = NewArray(-5);

    PrintArray(negativeTest);

    return 0;
}

但您需要关注设置要存储的数组的正确长度,因为没有办法检查此长度,就像我们的朋友们所解释的那样。


2
这是我在代码中的个人做法。我喜欢保持尽可能简单,同时仍能获得所需的值。
typedef struct intArr {
    int size;
    int* arr; 
} intArr_t;

int main() {
    intArr_t arr;
    arr.size = 6;
    arr.arr = (int*)malloc(sizeof(int) * arr.size);

    for (size_t i = 0; i < arr.size; i++) {
        arr.arr[i] = i * 10;
    }

    return 0;
}

建议使用 size_t 存储大小。 - David Ranieri
这是一个非常好且简单的方法!顺便提一下,在结构体后面可以省略 intArr。另外,更短更易读的写法是 arr.arr = malloc(arr.size * sizeof *arr.arr);,这样更具可重用性,因为你不需要指定“int”。 - Amarok24

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