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

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


执行概要:

int a[17];
size_t n = sizeof(a)/sizeof(a[0]);

完整的回答:

要确定数组的字节大小,可以使用sizeof 接线员:

int a[17];
size_t n = sizeof(a);

在我的电脑上,int是4字节长,所以n是68。

要确定数组中元素的数量,我们可以除法 数组的总大小乘以数组元素的大小。 你可以这样处理类型,像这样:

int a[17];
size_t n = sizeof(a) / sizeof(int);

并得到正确的答案(68 / 4 = 17)但如果类型 如果你忘记改变,你会有一个讨厌的bug sizeof(int)也是。

因此首选除数是sizeof(a[0])或等效的sizeof(*a),即数组第一个元素的大小。

int a[17];
size_t n = sizeof(a) / sizeof(a[0]);

另一个优点是您现在可以轻松地参数化 宏中的数组名,get:

#define NELEMS(x)  (sizeof(x) / sizeof((x)[0]))

int a[17];
size_t n = NELEMS(a);

sizeof(array) / sizeof(array[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));

大小的“技巧”是我所知道的最好的方法,在括号的使用上有一个小而重要的变化(对我来说,这是一个主要的烦恼)。

正如维基百科的条目所表明的那样,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);

现在你被保护了。当然,你复制了变量的名称,但如果你改变了它,编译器很可能会检测到这种破坏。


对于多维数组,它稍微复杂一些。通常人们定义显式宏常量,即。

#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])

等等,无限。


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

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/


如果你正在处理没有作为参数接收的数组,sizeof方式是正确的方式。作为参数发送给函数的数组被视为指针,因此sizeof将返回指针的大小,而不是数组的大小。

因此,在函数内部,此方法不起作用。相反,始终传递一个额外的参数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: %d\n", (int) sizeof(array));
    printSizeOf(array);

    printf("Length of array: %d\n", (int)( sizeof(array) / sizeof(array[0]) ));
    printLength(array);
}

void printSizeOf(int intArray[])
{
    printf("sizeof of parameter: %d\n", (int) sizeof(intArray));
}

void printLength(int intArray[])
{
    printf("Length of parameter: %d\n", (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

你可以使用&操作符。以下是源代码:

#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

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

int size = (&arr)[1] - arr;

点击这个链接了解解释


如果你知道数组的数据类型,你可以像这样使用:

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


C中数组的大小:

int a[10];
size_t size_of_array = sizeof(a);      // Size of array a
int n = sizeof (a) / sizeof (a[0]);    // Number of elements in array a
size_t size_of_element = sizeof(a[0]); // Size of each element in array a                                          
                                       // Size of each element = size of type

最好的方法是保存这些信息,例如,在一个结构中:

typedef struct {
     int *array;
     int elements;
} list_s;

实现所有必要的功能,如创建、销毁、检查是否相等以及其他所有需要的功能。它更容易作为参数传递。


#define SIZE_OF_ARRAY(_array) (sizeof(_array) / sizeof(_array[0]))

您可以使用sizeof操作符,但它不适用于函数,因为它将接受指针的引用。 您可以执行以下操作来查找数组的长度:

len = sizeof(arr)/sizeof(arr[0])

代码最初在这里找到:

查找数组中元素个数的C程序


注意:正如M.M在评论中指出的那样,这可能会给你未定义的行为。

int a[10];
int size = (*(&a+1)-a);

要了解更多细节,请参见这里和这里。


sizeof函数返回数组在内存中使用的字节数。如果你想计算数组中的元素数量,你应该用数组的sizeof变量类型除以这个数字。让我们说int array[10];,如果变量类型integer在你的计算机是32位(或4字节),为了得到你的数组的大小,你应该做以下工作:

int array[10];
size_t sizeOfArray = sizeof(array)/sizeof(int);

最简单的答案:

#include <stdio.h>

int main(void) {

    int a[] = {2,3,4,5,4,5,6,78,9,91,435,4,5,76,7,34}; // For example only
    int size;

    size = sizeof(a)/sizeof(a[0]); // Method

    printf("size = %d", size);
    return 0;
}

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


一个更优雅的解决方案是

size_t size = sizeof(a) / sizeof(*a);

除了已经提供的答案,我想指出一个特殊的情况下使用

sizeof(a) / sizeof (a[0])

如果a是char、unsigned char或signed char的数组,则不需要使用sizeof两次,因为带有这些类型的一个操作数的sizeof表达式的结果总是1。

引用自C18,6.5.3.4/4:

当sizeof应用于具有char类型、unsigned char类型或signed char类型(或其限定版本)的操作数时,结果为1。

因此,如果a是char、unsigned char或signed char类型的数组,sizeof(a) / sizeof(a[0])将等效于NUMBER OF ARRAY ELEMENTS / 1。除1是多余的。

在这种情况下,你可以简单地缩写为do:

sizeof(a)

例如:

char a[10];
size_t length = sizeof(a);

如果你想要证明,这里有一个GodBolt的链接。


尽管如此,如果类型发生重大变化(尽管这种情况很少见),该部门仍会保持安全。


对于预定义数组:

 int a[] = {1, 2, 3, 4, 5, 6};

计算数组中的元素数量:

 element _count = sizeof(a) / sizeof(a[0]);

要知道在代码中显式声明并由其变量引用的固定数组的大小,可以使用sizeof,例如:

int a[10];
int len = sizeof(a)/sizeof(int);

但这通常是没有用的,因为你已经知道答案了。

但如果你有一个指针,你不能使用sizeof,这是一个原则问题。

但是…由于数组是作为线性内存提供给用户的,如果您知道最后一个元素地址,并且如果您知道类型的大小,那么您就可以计算它有多少个元素。例如:

#include <stdio.h>

int main(){
    int a[10];
    printf("%d\n", sizeof(a)/sizeof(int));
    int *first = a;
    int *last = &(a[9]);
    printf("%d\n", (last-first) + 1);
}

输出:

10
10

另外,如果你不能利用编译时,你可以:

#include <stdio.h>

int main(){
    int a[10];
    printf("%d\n", sizeof(a)/sizeof(int));
    void *first = a;
    void *last = &(a[9]);
    printf("%d\n", (last-first)/sizeof(int) + 1);
}