如何在C中确定数组的大小?
也就是说,数组可以容纳的元素数?
ct2axkht16#
对于多维数组,它稍微复杂一些。通常人们定义显式的宏常量,即
# 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])
等等,无穷无尽。
w6mmgewl17#
每个人都在使用的宏ARRAYELEMENTCOUNT(x)计算不正确。实际上,这只是一个敏感的问题,因为您不能有导致“数组”类型的表达式。
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将以指针类型结束,并使整个宏无效(就像您试图在带有指针参数的函数中使用宏一样)。
p + 1
归根结底,在这个特定的示例中,错误并不重要(所以我只是在浪费每个人的时间;哈哈!),因为您没有类型为“数组”的表达式。但我认为关于预处理器评估的真正要点是很重要的。
nnvyjq4y18#
如果您知道数组的数据类型,则可以使用如下内容:
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(数组名称)都是一个指针。
arr
wlwcrazw19#
您可以使用sizeof运算符,但它不适用于函数,因为它将引用指针。您可以执行以下操作来确定数组的长度:
len = sizeof(arr)/sizeof(arr[0])
代码最初位于以下位置:
C program to find the number of elements in an array
luaexgnf20#
我建议永远不要使用sizeof(即使可以使用它)来获取两种不同大小的数组中的任何一种,无论是以元素数量还是以字节为单位,这是我在这里展示的最后两种情况。对于这两种大小中的每一种,都可以使用下面显示的宏来使其更安全。原因是为了让维护人员明白代码的意图,并在第一眼看到sizeof(ptr)和sizeof(arr)的区别(这种编写方式并不明显),这样对于每个阅读代码的人来说,Bug都是显而易见的。
sizeof
sizeof(ptr)
sizeof(arr)
TL;DR:
# 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 is buggy(截至2020年4月/日):
must_be_array(arr)
-Wsizeof-pointer-div
# 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提供的解决方案,即决不对函数的参数使用数组表示法。
我喜欢将数组表示法作为将指针用作数组的文档。但这意味着需要应用一种简单易懂的解决方案,这样就不可能编写有错误的代码。
从一个数组中,我们可能想知道三个大小:
第一个非常简单,我们处理的是数组还是指针并不重要,因为它是以相同的方式完成的。
用法示例:
void foo(size_t nmemb, int arr[nmemb]) { qsort(arr, nmemb, sizeof(arr[0]), cmp); }
qsort()需要将此值作为其第三个参数。
qsort()
对于其他两个大小,这是问题的主题,我们希望确保我们处理的是数组,如果不是,则中断编译,因为如果我们处理的是指针,我们将获得错误的值。当编译中断时,我们将能够很容易地看到,我们处理的不是数组,而是指针,我们只需用一个变量或宏来编写代码,该变量或宏将数组的大小存储在指针后面。
这是最常见的一个,许多答案都提供了典型的宏ARRAY_SIZE:
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()需要将此值作为其第三个参数。
memset()
与前面一样,如果将数组作为参数(指针)接收,它将不会进行编译,我们将不得不用值替换宏调用:
void foo(size_t nmemb, int arr[nmemb]) { memset(arr, 0, sizeof(arr[0]) * nmemb); }
今天我发现GCC中的新警告只有在宏定义在不是系统头的头中时才有效。如果在安装在系统中的头文件(通常是/usr/local/include/或/usr/include/)(#include <foo.h>)中定义宏,编译器不会发出警告(我尝试了GCC 9.3.0)。
/usr/local/include/
/usr/include/
#include <foo.h>
因此,我们有#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])),并希望使其安全。我们将需要C2X static_assert()和一些GCC扩展:表达式__builtin_types_compatible_p中的语句和声明:
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
static_assert()
# 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()是完全安全的,因此它的所有衍生品都将是安全的。
ARRAY_SIZE()
__arraycount()
Libbsd在<sys/cdefs.h>中提供了宏__arraycount(),这是不安全的,因为它缺少一对括号,但我们可以自己添加这些括号,因此我们甚至不需要在标题中编写除法(为什么要重复已经存在的代码?)。该宏是在系统头中定义的,因此如果我们使用它,我们将被迫使用上面的宏。
<sys/cdefs.h>
# 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(),有些系统同时提供这两种格式。您应该检查您的系统,并使用您已有的系统,并且可能使用一些预处理器条件来实现可移植性并同时支持两者。
<sys/param.h>
nitems()
遗憾的是,({}) GCC扩展名不能在文件范围内使用。为了能够在文件范围内使用宏,静态Assert必须位于sizeof(struct {})内。然后,将其乘以0以不影响结果。对(int)进行强制转换可以很好地模拟返回(int)0的函数(在本例中,这不是必需的,但它可以用于其他用途)。
({})
sizeof(struct {})
0
(int)
(int)0
此外,ARRAY_BYTES()的定义可以简化一点。
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功能。然而,使用较旧的标准可以使用一些卑鄙的技巧来克服它的缺失(例如,请参见:What is “:-!!” in C code?)(在C11中,您也有static_assert(),但它需要一条消息)。
kb5ga3dv21#
int size = (&arr)[1] - arr;
查看this link了解相关说明
gj3fmq9x22#
sizeof“诀窍”是我所知道的最好的方法,在括号的使用上有一个小的但(对我来说,这是一个重大的)令人恼火的变化。
正如维基百科条目所表明的那样,C的sizeof不是一个函数;它是一个运算符。因此,除非参数是类型名称,否则它不需要将参数括起来。这很容易记住,因为它使参数看起来像也使用括号的强制转换表达式。
所以:如果你有以下条件:
int myArray[10];
您可以使用如下代码来查找元素的数量:
size_t n = sizeof myArray / sizeof *myArray;
对我来说,这比带括号的替代方案容易多了。我还赞成在除法的右侧使用星号,因为它比索引更简洁。
当然,这也是编译时,所以不必担心划分会影响程序的性能。因此,请尽可能地使用此表单。
最好是在实际对象上使用sizeof,而不是在类型上使用sizeof,因为这样就不需要担心犯错误和声明错误的类型。
例如,假设您有一个函数可以将某些数据输出为字节流,例如通过网络。让我们调用函数send(),并使其以指向要发送的对象的指针和对象中的字节数作为参数。因此,原型变成:
send()
void send(const void *object, size_t size);
然后您需要发送一个整数,所以您可以这样编码:
int foo = 4711; send(&foo, sizeof (int));
现在,通过在两个地方指定foo的类型,您已经引入了一种微妙的方法来攻击自己的脚。如果其中一个更改了,但另一个没有更改,代码就会崩溃。因此,总是这样做:
foo
send(&foo, sizeof foo);
现在你受到保护了。当然,您复制了变量的名称,但如果您更改了它,则很有可能会以编译器可以检测到的方式中断。
4si2a6ki23#
值得注意的是,sizeof在处理衰减为指针的数组值时没有帮助:即使它指向数组的开始,但对于编译器来说,它与指向该数组的单个元素的指针相同。指针不会“记住”用于初始化它的数组的任何其他信息。
int a[10]; int* p = a; assert(sizeof(a) / sizeof(a[0]) == 10); assert(sizeof(p) == sizeof(int*)); assert(sizeof(*p) == sizeof(int));
vcudknz324#
sizeof方式是处理未作为参数接收的数组的正确方式iff。作为参数发送到函数的数组被视为指针,因此sizeof将返回指针的大小,而不是数组的大小。
因此,在函数内部,此方法不起作用。相反,始终传递一个指示数组中元素数量的附加参数size_t size。
size_t size
测试:
# include <stdio.h> # include <stdlib.h> void printSizeOf(int intArray[]); void printLength(int intArray[]); int main(int argc, char* argv[]) { int array[] = { 0, 1, 2, 3, 4, 5, 6 }; printf("sizeof of array: %dn", (int) sizeof(array)); printSizeOf(array); printf("Length of array: %dn", (int)( sizeof(array) / sizeof(array[0]) )); printLength(array); } void printSizeOf(int intArray[]) { printf("sizeof of parameter: %dn", (int) sizeof(intArray)); } void printLength(int intArray[]) { printf("Length of parameter: %dn", (int)( sizeof(intArray) / sizeof(intArray[0]) )); }
输出(在64位Linux操作系统中):
sizeof of array: 28 sizeof of parameter: 8 Length of array: 7 Length of parameter: 2
输出(在32位Windows操作系统中):
sizeof of array: 28 sizeof of parameter: 4 Length of array: 7 Length of parameter: 1
24条答案
按热度按时间ct2axkht16#
对于多维数组,它稍微复杂一些。通常人们定义显式的宏常量,即
但是这些常量也可以在编译时使用sizeof进行求值:
请注意,此代码可以在C和C++中运行。对于两维以上的数组,请使用
等等,无穷无尽。
w6mmgewl17#
每个人都在使用的宏
ARRAYELEMENTCOUNT(x)
计算不正确。实际上,这只是一个敏感的问题,因为您不能有导致“数组”类型的表达式。鉴于
它正确地评估为:
这与显式数组的大小没有太大关系。我只是注意到了很多错误,因为我没有真正观察到C预处理器是如何工作的。始终对宏参数进行换行,而不是中可能涉及的表达式。
这是正确的;我的例子是一个糟糕的例子。但这实际上正是应该发生的事情。正如我前面提到的,
p + 1
将以指针类型结束,并使整个宏无效(就像您试图在带有指针参数的函数中使用宏一样)。归根结底,在这个特定的示例中,错误并不重要(所以我只是在浪费每个人的时间;哈哈!),因为您没有类型为“数组”的表达式。但我认为关于预处理器评估的真正要点是很重要的。
nnvyjq4y18#
如果您知道数组的数据类型,则可以使用如下内容:
或者,如果您不知道数组的数据类型,您可以使用如下内容:
注意:只有在运行时未定义数组(如Malloc)并且未在函数中传递数组时,此操作才有效。在这两种情况下,
arr
(数组名称)都是一个指针。wlwcrazw19#
您可以使用sizeof运算符,但它不适用于函数,因为它将引用指针。您可以执行以下操作来确定数组的长度:
代码最初位于以下位置:
C program to find the number of elements in an array
luaexgnf20#
我建议永远不要使用
sizeof
(即使可以使用它)来获取两种不同大小的数组中的任何一种,无论是以元素数量还是以字节为单位,这是我在这里展示的最后两种情况。对于这两种大小中的每一种,都可以使用下面显示的宏来使其更安全。原因是为了让维护人员明白代码的意图,并在第一眼看到sizeof(ptr)
和sizeof(arr)
的区别(这种编写方式并不明显),这样对于每个阅读代码的人来说,Bug都是显而易见的。TL;DR:
must_be_array(arr)
(定义如下)需要作为-Wsizeof-pointer-div
is buggy(截至2020年4月/日):关于这个主题有一些重要的错误:https://lkml.org/lkml/2015/9/3/428
我不同意Linus提供的解决方案,即决不对函数的参数使用数组表示法。
我喜欢将数组表示法作为将指针用作数组的文档。但这意味着需要应用一种简单易懂的解决方案,这样就不可能编写有错误的代码。
从一个数组中,我们可能想知道三个大小:
数组元素的大小
第一个非常简单,我们处理的是数组还是指针并不重要,因为它是以相同的方式完成的。
用法示例:
qsort()
需要将此值作为其第三个参数。对于其他两个大小,这是问题的主题,我们希望确保我们处理的是数组,如果不是,则中断编译,因为如果我们处理的是指针,我们将获得错误的值。当编译中断时,我们将能够很容易地看到,我们处理的不是数组,而是指针,我们只需用一个变量或宏来编写代码,该变量或宏将数组的大小存储在指针后面。
数组中的元素个数
这是最常见的一个,许多答案都提供了典型的宏
ARRAY_SIZE
:当您将此宏应用于指针时,最新版本的编译器(如GCC 8)会发出警告,因此它是安全的(有其他方法可以使其在较旧的编译器中保持安全)。
它的工作原理是将整个数组的字节大小除以每个元素的大小。
用法示例:
如果这些函数不使用数组,而是将它们作为参数获取,则前面的代码将无法编译,因此不可能有错误(假设使用了最新的编译器版本,或者使用了其他一些技巧),并且我们需要用值替换宏调用:
数组在内存中使用的字节大小
ARRAY_SIZE
通常用作前一种情况的解决方案,但这种情况很少被安全地编写,可能是因为它不太常见。获取此值的常见方法是使用
sizeof(arr)
。问题:与前一个相同;如果您使用指针而不是数组,您的程序将会变得疯狂。问题的解决方案涉及使用与前面相同的宏,我们知道这是安全的(如果将其应用于指针,则会中断编译):
它的工作原理非常简单:它取消了
ARRAY_SIZE
所做的除法,所以在数学删除之后,您最终只得到一个sizeof(arr)
,但增加了ARRAY_SIZE
结构的安全性。用法示例:
memset()
需要将此值作为其第三个参数。与前面一样,如果将数组作为参数(指针)接收,它将不会进行编译,我们将不得不用值替换宏调用:
更新时间(2020年4月23日):
-Wsizeof-pointer-div
is buggy:今天我发现GCC中的新警告只有在宏定义在不是系统头的头中时才有效。如果在安装在系统中的头文件(通常是
/usr/local/include/
或/usr/include/
)(#include <foo.h>
)中定义宏,编译器不会发出警告(我尝试了GCC 9.3.0)。因此,我们有
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
,并希望使其安全。我们将需要C2Xstatic_assert()
和一些GCC扩展:表达式__builtin_types_compatible_p中的语句和声明:现在
ARRAY_SIZE()
是完全安全的,因此它的所有衍生品都将是安全的。更新:libbsd提供
__arraycount()
:Libbsd在
<sys/cdefs.h>
中提供了宏__arraycount()
,这是不安全的,因为它缺少一对括号,但我们可以自己添加这些括号,因此我们甚至不需要在标题中编写除法(为什么要重复已经存在的代码?)。该宏是在系统头中定义的,因此如果我们使用它,我们将被迫使用上面的宏。有些系统在
<sys/param.h>
中提供nitems()
,有些系统同时提供这两种格式。您应该检查您的系统,并使用您已有的系统,并且可能使用一些预处理器条件来实现可移植性并同时支持两者。更新:允许在文件范围内使用宏:
遗憾的是,
({})
GCC扩展名不能在文件范围内使用。为了能够在文件范围内使用宏,静态Assert必须位于sizeof(struct {})
内。然后,将其乘以0
以不影响结果。对(int)
进行强制转换可以很好地模拟返回(int)0
的函数(在本例中,这不是必需的,但它可以用于其他用途)。此外,
ARRAY_BYTES()
的定义可以简化一点。注意事项:
这段代码使用了以下扩展,这些扩展是完全必要的,并且它们的存在对于实现安全性是绝对必要的。如果您的编译器没有它们,或者一些类似的,那么您就不能达到这种级别的安全性。
__builtin_types_compatible_p()
typeof()
我还利用了以下C2X功能。然而,使用较旧的标准可以使用一些卑鄙的技巧来克服它的缺失(例如,请参见:What is “:-!!” in C code?)(在C11中,您也有
static_assert()
,但它需要一条消息)。static_assert()
kb5ga3dv21#
查看this link了解相关说明
gj3fmq9x22#
sizeof
“诀窍”是我所知道的最好的方法,在括号的使用上有一个小的但(对我来说,这是一个重大的)令人恼火的变化。正如维基百科条目所表明的那样,C的
sizeof
不是一个函数;它是一个运算符。因此,除非参数是类型名称,否则它不需要将参数括起来。这很容易记住,因为它使参数看起来像也使用括号的强制转换表达式。所以:如果你有以下条件:
您可以使用如下代码来查找元素的数量:
对我来说,这比带括号的替代方案容易多了。我还赞成在除法的右侧使用星号,因为它比索引更简洁。
当然,这也是编译时,所以不必担心划分会影响程序的性能。因此,请尽可能地使用此表单。
最好是在实际对象上使用
sizeof
,而不是在类型上使用sizeof
,因为这样就不需要担心犯错误和声明错误的类型。例如,假设您有一个函数可以将某些数据输出为字节流,例如通过网络。让我们调用函数
send()
,并使其以指向要发送的对象的指针和对象中的字节数作为参数。因此,原型变成:然后您需要发送一个整数,所以您可以这样编码:
现在,通过在两个地方指定
foo
的类型,您已经引入了一种微妙的方法来攻击自己的脚。如果其中一个更改了,但另一个没有更改,代码就会崩溃。因此,总是这样做:现在你受到保护了。当然,您复制了变量的名称,但如果您更改了它,则很有可能会以编译器可以检测到的方式中断。
4si2a6ki23#
值得注意的是,
sizeof
在处理衰减为指针的数组值时没有帮助:即使它指向数组的开始,但对于编译器来说,它与指向该数组的单个元素的指针相同。指针不会“记住”用于初始化它的数组的任何其他信息。vcudknz324#
sizeof
方式是处理未作为参数接收的数组的正确方式iff。作为参数发送到函数的数组被视为指针,因此sizeof
将返回指针的大小,而不是数组的大小。因此,在函数内部,此方法不起作用。相反,始终传递一个指示数组中元素数量的附加参数
size_t size
。测试:
输出(在64位Linux操作系统中):
输出(在32位Windows操作系统中):