如何确定C语言中数组的大小?
也就是说,数组可以容纳多少元素?
如何确定C语言中数组的大小?
也就是说,数组可以容纳多少元素?
当前回答
我建议永远不要使用sizeof(即使可以使用sizeof)来获取数组的两种不同大小,以元素数或字节数为单位,这是我在这里展示的最后两种情况。对于这两种大小中的每一种,都可以使用下面所示的宏来提高安全性。这样做的原因是为了让维护者清楚地看到代码的意图,以及sizeof(ptr)和sizeof(arr)的第一眼差别(这样写并不明显),这样对于阅读代码的每个人来说,bug就很明显了。
TL; diana:
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + must_be_array(arr))
#define ARRAY_BYTES(arr) (sizeof(arr) + must_be_array(arr))
must_be_array(arr)(定义如下)是需要的-Wsizeof-pointer-div是bug(截至2020年4月):
#define is_same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_array(arr) (!is_same_type((arr), &(arr)[0]))
#define must_be(e) \
( \
0 * (int)sizeof( \
struct { \
static_assert(e); \
char ISO_C_forbids_a_struct_with_no_members__; \
} \
) \
)
#define must_be_array(arr) must_be(is_array(arr))
关于这个主题有一些重要的错误:https://lkml.org/lkml/2015/9/3/428
我不同意Linus提供的解决方案,即永远不要使用数组符号表示函数的形参。
我喜欢数组表示法作为指针被用作数组的文档。但这意味着需要应用万无一失的解决方案,这样就不可能编写出有bug的代码。
从数组中,我们有三个大小,我们可能想知道:
数组元素的大小 数组中元素的个数 数组在内存中使用的字节大小
数组元素的大小
第一个很简单,不管我们处理的是数组还是指针,都是一样的。
用法示例:
void foo(size_t nmemb, int arr[nmemb])
{
qsort(arr, nmemb, sizeof(arr[0]), cmp);
}
Qsort()需要这个值作为它的第三个参数。
对于其他两个大小,也就是问题的主题,我们要确保我们处理的是一个数组,如果不是,就会中断编译,因为如果我们处理的是指针,就会得到错误的值。当编译失败时,我们将很容易看到我们处理的不是一个数组,而是一个指针,我们只需要使用一个变量或宏来编写代码,将数组的大小存储在指针后面。
数组中元素的个数
这个是最常见的,很多答案都提供了典型的宏ARRAY_SIZE:
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
最新版本的编译器,如GCC 8,会在将此宏应用于指针时发出警告,因此它是安全的(在较旧的编译器中,还有其他方法可以使其安全)。
它的工作原理是用整个数组的字节大小除以每个元素的大小。
用法示例:
void foo(size_t nmemb)
{
char buf[nmemb];
fgets(buf, ARRAY_SIZE(buf), stdin);
}
void bar(size_t nmemb)
{
int arr[nmemb];
for (size_t i = 0; i < ARRAY_SIZE(arr); i++)
arr[i] = i;
}
如果这些函数没有使用数组,而是将它们作为参数,那么之前的代码将无法编译,因此不可能有错误(考虑到使用了最近的编译器版本,或者使用了其他一些技巧),我们需要用值替换宏调用:
void foo(size_t nmemb, char buf[nmemb])
{
fgets(buf, nmemb, stdin);
}
void bar(size_t nmemb, int arr[nmemb])
{
for (size_t i = nmemb - 1; i < nmemb; i--)
arr[i] = i;
}
数组在内存中使用的字节大小
ARRAY_SIZE通常用作前一种情况的解决方案,但这种情况很少被安全地编写,可能是因为它不太常见。
获取这个值的常用方法是使用sizeof(arr)。问题是:和前一个问题一样;如果你有一个指针而不是数组,你的程序将会发疯。
这个问题的解决方案涉及到使用与之前相同的宏,我们知道这是安全的(如果应用于指针,它会中断编译):
#define ARRAY_BYTES(arr) (sizeof((arr)[0]) * ARRAY_SIZE(arr))
它的工作原理非常简单:它取消了ARRAY_SIZE所做的除法,因此在数学上取消之后,您最终只得到一个sizeof(arr),但增加了ARRAY_SIZE结构的安全性。
用法示例:
void foo(size_t nmemb)
{
int arr[nmemb];
memset(arr, 0, ARRAY_BYTES(arr));
}
Memset()需要这个值作为它的第三个参数。
和以前一样,如果数组作为参数(指针)接收,它将不会编译,并且我们必须用值替换宏调用:
void foo(size_t nmemb, int arr[nmemb])
{
memset(arr, 0, sizeof(arr[0]) * nmemb);
}
更新(23/apr/2020): -Wsizeof-pointer-div有bug:
今天我发现GCC中的新警告只适用于宏定义在非系统头文件中的情况。如果你在安装在系统中的头文件中定义宏(通常是/usr/local/include/或/usr/include/) (#include <foo.h>),编译器将不会发出警告(我尝试了GCC 9.3.0)。
因此我们有#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))并想让它安全。我们将需要C2X static_assert()和一些GCC扩展:
#include <assert.h>
#define is_same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_array(arr) (!is_same_type((arr), &(arr)[0]))
#define Static_assert_array(arr) static_assert(is_array(arr))
#define ARRAY_SIZE(arr) \
({ \
Static_assert_array(arr); \
sizeof(arr) / sizeof((arr)[0]); \
})
现在ARRAY_SIZE()是完全安全的,因此它的所有导数都是安全的。
更新:libbsd提供__arraycount():
Libbsd在<sys/cdefs.h>中提供了宏__arraycount(),这是不安全的,因为它缺少一对括号,但我们可以自己添加这些括号,因此我们甚至不需要在标头中写入除法(为什么我们要复制已经存在的代码?)这个宏是在一个系统头文件中定义的,所以如果我们使用它,我们就被迫使用上面的宏。
#inlcude <assert.h>
#include <stddef.h>
#include <sys/cdefs.h>
#include <sys/types.h>
#define is_same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_array(arr) (!is_same_type((arr), &(arr)[0]))
#define Static_assert_array(arr) static_assert(is_array(arr))
#define ARRAY_SIZE(arr) \
({ \
Static_assert_array(arr); \
__arraycount((arr)); \
})
#define ARRAY_BYTES(arr) (sizeof((arr)[0]) * ARRAY_SIZE(arr))
有些系统在<sys/param.h>中提供了nitems(),有些系统同时提供了这两种方法。您应该检查您的系统,并使用您现有的系统,也许还可以使用一些预处理器条件来实现可移植性和对两者的支持。
更新:允许宏在文件范围内使用:
不幸的是,({})gcc扩展名不能在文件范围内使用。 为了能够在文件范围内使用宏,静态断言必须是 在sizeof(struct{})中。然后,将其乘以0以不受影响 结果。强制转换为(int)可能很适合模拟函数 返回(int)0(在这种情况下,它是不必要的,但它 对于其他事情是可重用的)。
另外,ARRAY_BYTES()的定义可以稍微简化一点。
#include <assert.h>
#include <stddef.h>
#include <sys/cdefs.h>
#include <sys/types.h>
#define is_same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_array(arr) (!is_same_type((arr), &(arr)[0]))
#define must_be(e) \
( \
0 * (int)sizeof( \
struct { \
static_assert(e); \
char ISO_C_forbids_a_struct_with_no_members__; \
} \
) \
)
#define must_be_array(arr) must_be(is_array(arr))
#define ARRAY_SIZE(arr) (__arraycount((arr)) + must_be_array(arr))
#define ARRAY_BYTES(arr) (sizeof(arr) + must_be_array(arr))
注:
这段代码使用了以下扩展,这些扩展是完全必要的,它们的存在对于实现安全是绝对必要的。如果你的编译器没有它们,或者一些类似的,那么你就不能达到这个级别的安全。
__builtin_types_compatible_p () typeof ()
我还使用了以下C2X功能。然而,如果使用旧的标准,它的缺失可以通过一些肮脏的技巧来克服(例如:什么是“::!!”)(在C11中你也有static_assert(),但它需要一个消息)。
static_assert ()
其他回答
#define SIZE_OF_ARRAY(_array) (sizeof(_array) / sizeof(_array[0]))
如果你知道数组的数据类型,你可以像这样使用:
int arr[] = {23, 12, 423, 43, 21, 43, 65, 76, 22};
int noofele = sizeof(arr)/sizeof(int);
或者如果你不知道数组的数据类型,你可以使用类似这样的东西:
noofele = sizeof(arr)/sizeof(arr[0]);
注意:只有当数组在运行时没有定义(如malloc)并且数组没有传递到函数中时,这个东西才有效。在这两种情况下,arr(数组名)都是指针。
每个人都在使用的宏ARRAYELEMENTCOUNT(x)的计算结果不正确。实际上,这只是一个敏感的问题,因为你不能有导致“数组”类型的表达式。
/* Compile as: CL /P "macro.c" */
# define ARRAYELEMENTCOUNT(x) (sizeof (x) / sizeof (x[0]))
ARRAYELEMENTCOUNT(p + 1);
实际计算结果为:
(sizeof (p + 1) / sizeof (p + 1[0]));
而
/* Compile as: CL /P "macro.c" */
# define ARRAYELEMENTCOUNT(x) (sizeof (x) / sizeof (x)[0])
ARRAYELEMENTCOUNT(p + 1);
它正确地计算为:
(sizeof (p + 1) / sizeof (p + 1)[0]);
这和数组的大小没有太大关系。我刚刚注意到很多错误,因为没有真正观察C预处理器是如何工作的。您总是包装宏参数,而不是可能涉及的表达式。
这是正确的;我的例子很糟糕。但这正是应该发生的。正如我前面提到的,p + 1将以指针类型结束,并使整个宏无效(就像您试图在带有指针形参的函数中使用宏一样)。
在一天结束的时候,在这个特殊的例子中,错误并不重要(所以我只是在浪费大家的时间;Huzzah !),因为你没有数组类型的表达式。但是我认为关于预处理器评估的微妙之处是很重要的。
对于多维数组,它稍微复杂一些。通常人们定义显式宏常量,即。
#define g_rgDialogRows 2
#define g_rgDialogCols 7
static char const* g_rgDialog[g_rgDialogRows][g_rgDialogCols] =
{
{ " ", " ", " ", " 494", " 210", " Generic Sample Dialog", " " },
{ " 1", " 330", " 174", " 88", " ", " OK", " " },
};
但是这些常量也可以在编译时用sizeof求值:
#define rows_of_array(name) \
(sizeof(name ) / sizeof(name[0][0]) / columns_of_array(name))
#define columns_of_array(name) \
(sizeof(name[0]) / sizeof(name[0][0]))
static char* g_rgDialog[][7] = { /* ... */ };
assert( rows_of_array(g_rgDialog) == 2);
assert(columns_of_array(g_rgDialog) == 7);
注意,这段代码可以在C和c++中运行。对于二维以上的数组,请使用
sizeof(name[0][0][0])
sizeof(name[0][0][0][0])
等等,无限。
sizeof函数返回数组在内存中使用的字节数。如果你想计算数组中的元素数量,你应该用数组的sizeof变量类型除以这个数字。让我们说int array[10];,如果变量类型integer在你的计算机是32位(或4字节),为了得到你的数组的大小,你应该做以下工作:
int array[10];
size_t sizeOfArray = sizeof(array)/sizeof(int);