如何确定C语言中数组的大小?
也就是说,数组可以容纳多少元素?
如何确定C语言中数组的大小?
也就是说,数组可以容纳多少元素?
当前回答
一个更优雅的解决方案是
size_t size = sizeof(a) / sizeof(*a);
其他回答
你可以使用&操作符。以下是源代码:
#include<stdio.h>
#include<stdlib.h>
int main(){
int a[10];
int *p;
printf("%p\n", (void *)a);
printf("%p\n", (void *)(&a+1));
printf("---- diff----\n");
printf("%zu\n", sizeof(a[0]));
printf("The size of array a is %zu\n", ((char *)(&a+1)-(char *)a)/(sizeof(a[0])));
return 0;
};
下面是示例输出
1549216672
1549216712
---- diff----
4
The size of array a is 10
大小的“技巧”是我所知道的最好的方法,在括号的使用上有一个小而重要的变化(对我来说,这是一个主要的烦恼)。
正如维基百科的条目所表明的那样,C语言的sizeof不是一个函数;它是一个算子。因此,除非实参是类型名,否则不需要在实参周围加上圆括号。这很容易记住,因为它使实参看起来像一个强制转换表达式,它也使用括号。
所以:如果你有以下情况:
int myArray[10];
你可以用这样的代码找到元素的数量:
size_t n = sizeof myArray / sizeof *myArray;
对我来说,这比用括号代替要容易得多。我还赞成在除法的右边部分使用星号,因为它比索引更简洁。
当然,这也都是编译时的,所以没有必要担心除法会影响程序的性能。所以尽可能使用这个形式。
当你有一个实际对象时,使用sizeof总是最好的,而不是在一个类型上,因为这样你就不需要担心犯错误并声明错误的类型。
例如,假设您有一个函数,它将一些数据输出为字节流,例如跨网络输出。让我们调用send()函数,并将一个指向要发送的对象的指针和该对象中的字节数作为参数。所以,原型变成:
void send(const void *object, size_t size);
然后你需要发送一个整数,所以你像这样编码:
int foo = 4711;
send(&foo, sizeof (int));
现在,通过在两个地方指定foo类型,您引入了一种搬起石头砸自己的脚的微妙方法。如果一个改变了,而另一个没有改变,代码就会崩溃。因此,总是这样做:
send(&foo, sizeof foo);
现在你被保护了。当然,你复制了变量的名称,但如果你改变了它,编译器很可能会检测到这种破坏。
如果你真的想这样做来传递你的数组,我建议实现一个结构来存储你想要数组类型的指针和一个表示数组大小的整数。然后你可以把它传递给你的函数。只需将数组变量值(指向第一个元素的指针)赋给该指针即可。然后你可以选择数组。arr[i]获取第i个元素并使用Array。Size来获取数组中元素的数量。
我为您提供了一些代码。它不是很有用,但是你可以用更多的特性来扩展它。老实说,如果这些是你想要的东西,你应该停止使用C语言,使用另一种内置这些功能的语言。
/* Absolutely no one should use this...
By the time you're done implementing it you'll wish you just passed around
an array and size to your functions */
/* This is a static implementation. You can get a dynamic implementation and
cut out the array in main by using the stdlib memory allocation methods,
but it will work much slower since it will store your array on the heap */
#include <stdio.h>
#include <string.h>
/*
#include "MyTypeArray.h"
*/
/* MyTypeArray.h
#ifndef MYTYPE_ARRAY
#define MYTYPE_ARRAY
*/
typedef struct MyType
{
int age;
char name[20];
} MyType;
typedef struct MyTypeArray
{
int size;
MyType *arr;
} MyTypeArray;
MyType new_MyType(int age, char *name);
MyTypeArray newMyTypeArray(int size, MyType *first);
/*
#endif
End MyTypeArray.h */
/* MyTypeArray.c */
MyType new_MyType(int age, char *name)
{
MyType d;
d.age = age;
strcpy(d.name, name);
return d;
}
MyTypeArray new_MyTypeArray(int size, MyType *first)
{
MyTypeArray d;
d.size = size;
d.arr = first;
return d;
}
/* End MyTypeArray.c */
void print_MyType_names(MyTypeArray d)
{
int i;
for (i = 0; i < d.size; i++)
{
printf("Name: %s, Age: %d\n", d.arr[i].name, d.arr[i].age);
}
}
int main()
{
/* First create an array on the stack to store our elements in.
Note we could create an empty array with a size instead and
set the elements later. */
MyType arr[] = {new_MyType(10, "Sam"), new_MyType(3, "Baxter")};
/* Now create a "MyTypeArray" which will use the array we just
created internally. Really it will just store the value of the pointer
"arr". Here we are manually setting the size. You can use the sizeof
trick here instead if you're sure it will work with your compiler. */
MyTypeArray array = new_MyTypeArray(2, arr);
/* MyTypeArray array = new_MyTypeArray(sizeof(arr)/sizeof(arr[0]), arr); */
print_MyType_names(array);
return 0;
}
值得注意的是,sizeof在处理已经衰减为指针的数组值时没有帮助:即使它指向数组的开始,对编译器来说,它与指向该数组中单个元素的指针相同。指针不会“记住”用于初始化它的数组的任何其他内容。
int a[10];
int* p = a;
assert(sizeof(a) / sizeof(a[0]) == 10);
assert(sizeof(p) == sizeof(int*));
assert(sizeof(*p) == sizeof(int));
我建议永远不要使用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 ()