如何确定C语言中数组的大小?

也就是说,数组可以容纳多少元素?


当前回答

每个人都在使用的宏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 !),因为你没有数组类型的表达式。但是我认为关于预处理器评估的微妙之处是很重要的。

其他回答

我建议永远不要使用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 ()

值得注意的是,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(array) / sizeof(array[0])

“你这是搬起石头砸自己的脚。”

C 'native'数组不存储它们的大小。因此,建议将数组的长度保存在单独的变量/const中,并在传递数组时传递它,即:

#define MY_ARRAY_LENGTH   15
int myArray[MY_ARRAY_LENGTH];

如果你正在编写c++,无论如何你都应该避免本机数组(除非你不能,在这种情况下,小心脚下)。如果你正在编写c++,请使用STL的“vector”容器。“与数组相比,它们提供了几乎相同的性能”,而且它们更有用!

// vector is a template, the <int> means it is a vector of ints
vector<int> numbers;  

// push_back() puts a new value at the end (or back) of the vector
for (int i = 0; i < 10; i++)
    numbers.push_back(i);

// Determine the size of the array
cout << numbers.size();

看到的: http://www.cplusplus.com/reference/stl/vector/

如果你真的想这样做来传递你的数组,我建议实现一个结构来存储你想要数组类型的指针和一个表示数组大小的整数。然后你可以把它传递给你的函数。只需将数组变量值(指向第一个元素的指针)赋给该指针即可。然后你可以选择数组。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;
}