在C语言中是否有一个标准函数可以返回数组的长度?

29

C语言中是否有一个标准函数,可以返回数组的长度?


我认为,在这个页面上还没有提到:拒绝指针的数组大小宏 - T S
7个回答

70

通常其他答案中描述的技术被封装在宏中,以便让其更易于阅读。类似于:

#define COUNT_OF( arr) (sizeof(arr)/sizeof(0[arr]))
请注意,上面的宏使用了一个小技巧,在索引操作符('[]')中放置了数组名,而不是0 - 这样做是为了防止在C++代码中错误地使用宏并出现重载operator[]()的情况。编译器将会发出警告而不是给出错误结果。
但请注意,如果您传递的是指针而不是数组,该宏将会悄无声息地给出错误结果-这是使用这种技术的主要问题之一。
我最近开始使用一个更复杂的版本,它是从谷歌Chromium代码库中窃取的:
#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
在这个版本中,如果错误地将指针传递为参数,则编译器在某些情况下会发出警告 - 具体来说,如果指针的大小不能被指针所指向的对象的大小整除,那么除零操作将导致编译器报错。事实上,我使用过的至少一个编译器会给出一个警告而不是一个错误 - 我不确定对于具有除以零表达式的表达式它生成什么内容。
这个宏并没有完全杜绝误用的可能性,但它是我在C语言中见过最接近完美的解决方案。
如果你想要更安全的解决方案,当你在C ++ 中工作时,可以看一下Compile time sizeof_array without using a macro,其中描述了微软在winnt.h中使用的一种相当复杂的基于模板的方法。

2
对于Chromium版本和使用0[x]的小技巧,都要+1。 - Danilo Piazzalunga
你能解释一下为什么这个语法 0[arr] 能够作为 arr[0] 来使用吗? - meguli
5
在C语言中,数组下标表示法只是指针加法的简写。所以arr[0]可以变成*(arr + 0),而0[arr]则变成了*(0 + arr),它们当然是等价的。 - Cameron
如果你在C++中工作时想要更安全的解决方案,自C++17起,你可以使用std::size函数 - https://en.cppreference.com/w/cpp/iterator/size - undefined

14

没有。

对于固定大小的数组,您可以使用Andrew提到的常见技巧,sizeof(array) / sizeof(array[0]) - 但这仅适用于声明数组的范围内。
sizeof(array) 给出整个数组的大小,而 sizeof(array[0]) 给出第一个元素的大小。
请参阅Michael的答案以了解如何在宏中包装它。

对于动态分配的数组,您可以使用一个整数类型来跟踪大小,或者如果可能的话将其设置为0结尾(即分配1个额外元素,并将最后一个元素设置为0)。


13
请注意,sizeof(array)/sizeof(array [0])仅适用于声明数组的同一作用域。我们无法将array传递给另一个函数并期望代码能够正常工作。 - Chris Lutz
1
它不仅适用于常量大小的数组,也适用于C99中的可变长度数组。在这种情况下,sizeof会在运行时计算。 - Johannes Schaub - litb

4
sizeof array / sizeof array[0]

12
这仅适用于在编译时已知大小的固定大小数组,请说明这一点。 - John Kugelman
2
你可以使用 *array 来进一步缩短代码 :-) - Frerich Raabe
2
该表达式也适用于变长数组(VLA),其中大小在运行时确定。它不适用于动态分配的数组(通过malloc()等)。它还无法处理函数参数列表中的“数组”——但它们本来就不是数组。 - Jonathan Leffler

2

可以通过以下方式获取数组 x 的元素数量:

sizeof(x)/sizeof(x[0])

需要注意的是,当数组传递给函数时,它们会被转换成指针,而指针携带大小信息。实际上,大小信息从来没有在运行时可用,因为它是在编译时计算的,但是您可以在数组可见的地方(即未被转换的地方)像其存在一样使用该信息。
当我将需要作为数组处理的数组传递给函数时,我始终确保传递了两个参数:
  • 数组的长度;以及
  • 指向数组的指针。
因此,尽管数组可以像数组一样在声明它的地方进行处理,但在其他地方,它被视为大小和指针。
我的代码通常会像这样:
#define countof(x) (sizeof(x)/sizeof(x[0]))
: : :
int numbers[10];
a = fn (countof(numbers),numbers);

如果这样,fn()将可用于大小信息。
过去我使用的另一个技巧(在我看来有些混乱,但为了完整性,我在这里介绍一下)是使用联合数组,并将第一个元素设置为长度,类似于:
typedef union {
    int len;
    float number;
} tNumber;
tNumber number[10];
: : :
number[0].len = 5;
a = fn (number);

那么fn()就可以访问长度和所有元素,而你不必担心数组/指针二分法。

这样做的额外优点是允许长度变化(即使用的元素数量,而不是分配的单位数)。但我不再使用这种方法,因为我认为两个参数的数组版本(大小和数据)更好。


那个 union 技巧很巧妙,但我更愿意使用 struct 的技巧(或者同样思路的非技巧版本)。 - Chris Lutz

2
我创建了一个宏,该宏返回数组的大小,但如果在指针上使用则会产生编译器错误。请注意,该宏依赖于gcc扩展功能,因此不是可移植的解决方案。
#define COUNT(a) (__builtin_choose_expr( \
                  __builtin_types_compatible_p(typeof(a), typeof(&(a)[0])), \
                  (void)0, \
                  (sizeof(a)/sizeof((a)[0]))))

int main(void)
{
    int arr[5];
    int *p;
    int x = COUNT(arr);
//  int y = COUNT(p);
}

如果您删除注释,将会出现以下错误:error: void value not ignored as it ought to be

1
简单的回答当然是否定的。但实际情况是,“我还是需要知道”的。所以,让我们讨论一下绕过此问题的方法。
其中一个可行的方法就是使用sizeof(),这个方法已经被提到了无数次,可以暂时规避问题。
int i[] = {0, 1, 2};
...
size_t i_len = sizeof(i) / sizeof(i[0]);

这个方法是有效的,但当我们尝试将i传递给函数或者获取i的指针时就会出现问题。那么有没有更通用的解决方案呢?

通用的解决方案是在将数组传递给函数时同时传递数组长度。我们在标准库中经常看到这种做法:

void *memcpy(void *s1, void *s2, size_t n);

将会从s1复制n个字节到s2,这允许我们使用n确保我们的缓冲区永远不会溢出。这是一个好策略-它的开销低,并且实际上生成了一些有效的代码(与strcpy()相比较,其必须检查字符串的结尾并没有办法“知道”要进行多少次迭代,以及可怜的混乱的strncpy() ,它必须同时检查两个-都可能变慢,并且如果您已经为某种原因计算了字符串的长度,则可以通过使用memcpy()加速任一个)。

另一种方法是将您的代码封装在一个struct中。常见的做法如下:

typedef struct _arr {
  size_t len;
  int arr[0];
} arr;

如果我们想要一个长度为5的数组,我们可以这样做:
arr *a = malloc(sizeof(*a) + sizeof(int) * 5);
a->len = 5;

然而,这只是一个相对明确的hack(C99允许您使用int arr[]),而且相当费力。一种更好定义的方法是:

typedef struct _arr {
  size_t len;
  int *arr;
} arr;

但是,随之而来的是我们的分配(和释放)变得更加复杂。当然,这两种方法的好处是,现在你创建的数组将携带其长度。虽然它稍微不那么内存高效,但它非常安全。如果选择了其中一种路径,请务必编写辅助函数,以便无需手动分配和释放(以及处理)这些结构。

3
“Struct hack”是一种技巧,但没有理由使它比必要的更加技巧化。在C语言中,大小为0的数组始终明确地是非法的。对于“struct hack”的适当结构声明使用大小为1的数组。传统上,malloc的大小被计算为offsetof(arr, arr) + n * sizeof(int)(因此,数组声明中使用的大小无关紧要)。 - AnT stands with Russia
它们是非法的,但许多旧的 C 编译器支持它们(毫无疑问是因为懒惰),我已经看到过很多次 arr[0](伴随着不适)。不过我从未见过使用 offsetof() 计算大小,虽然这是一种不错的确保方法。而我则持不同意见。黑客应该大声喊出“这是一个黑客!请在你有机会时改进这段代码!” - Chris Lutz
我认为“结构体黑科技”的美妙之处在于它不违反任何约束条件。程序必须编译良好。“结构体黑科技”始终依赖于理论上未定义行为的实际“定义性”。正如我们所知,生活常常让我们依赖于这一点。这就是为什么我们喜爱和珍视那个“结构体黑科技”。但是,“[0]”版本对所有这些都进行了重大的定性改变。它引入了一个约束违规,可能导致编译失败。很难喜欢上甚至无法编译的东西。(是的,我知道有些编译器允许并仍然允许使用“[0]”数组。) - AnT stands with Russia
1
“Hack”在C99中是合法的,但语法看起来更像是hack。虽然我同意合法的hack是好的,但如果它们完全合法,它们就不是真正的hack了。(值得一提的是,使用-Wall -Werror -Wextra编译器选项,在GCC 4.0上编译int arr[0]不会有任何问题。它不可能是那么非法的。) - Chris Lutz
Chris Lutz:也尝试使用-pedantic,arr [0]作为结构体的最后一个成员是已记录的GCC扩展 :) - zwol

0
如果您有一个数组类型的对象a,则可以将数组中的元素数量表示为sizeof a / sizeof *a。如果您允许您的数组对象衰减为指针类型(或者一开始只有一个指针对象),那么在一般情况下无法确定数组中的元素数量。

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