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

pnwntuvh  于 2022-09-26  发布在  其他
关注(0)|答案(24)|浏览(168)

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

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

0wi1tuuw

0wi1tuuw1#

执行摘要:

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

完整答案:

要确定数组的字节大小,可以使用sizeof运算符:

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

在我的计算机上,整数是4字节长,所以n是68。

要确定数组中的元素数,可以将数组的总大小除以数组元素的大小。您可以对类型执行此操作,如下所示:

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

并得到正确的答案(68/4=17),但是如果a的类型改变了,如果你也忘记了改变sizeof(int),你就会有一个严重的错误。

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

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

另一个优点是,您现在可以在宏中轻松地将数组名称参数化,并获得:


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

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

vcudknz32#

sizeof方式是处理未作为参数接收的数组的正确方式iff。作为参数发送到函数的数组被视为指针,因此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: %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
4si2a6ki

4si2a6ki3#

值得注意的是,sizeof在处理衰减为指针的数组值时没有帮助:即使它指向数组的开始,但对于编译器来说,它与指向该数组的单个元素的指针相同。指针不会“记住”用于初始化它的数组的任何其他信息。

int a[10];
int* p = a;

assert(sizeof(a) / sizeof(a[0]) == 10);
assert(sizeof(p) == sizeof(int*));
assert(sizeof(*p) == sizeof(int));
gj3fmq9x

gj3fmq9x4#

sizeof“诀窍”是我所知道的最好的方法,在括号的使用上有一个小的但(对我来说,这是一个重大的)令人恼火的变化。

正如维基百科条目所表明的那样,C的sizeof不是一个函数;它是一个运算符。因此,除非参数是类型名称,否则它不需要将参数括起来。这很容易记住,因为它使参数看起来像也使用括号的强制转换表达式。

所以:如果你有以下条件:

int myArray[10];

您可以使用如下代码来查找元素的数量:

size_t n = sizeof myArray / sizeof *myArray;

对我来说,这比带括号的替代方案容易多了。我还赞成在除法的右侧使用星号,因为它比索引更简洁。

当然,这也是编译时,所以不必担心划分会影响程序的性能。因此,请尽可能地使用此表单。

最好是在实际对象上使用sizeof,而不是在类型上使用sizeof,因为这样就不需要担心犯错误和声明错误的类型。

例如,假设您有一个函数可以将某些数据输出为字节流,例如通过网络。让我们调用函数send(),并使其以指向要发送的对象的指针和对象中的字节数作为参数。因此,原型变成:

void send(const void *object, size_t size);

然后您需要发送一个整数,所以您可以这样编码:

int foo = 4711;
send(&foo, sizeof (int));

现在,通过在两个地方指定foo的类型,您已经引入了一种微妙的方法来攻击自己的脚。如果其中一个更改了,但另一个没有更改,代码就会崩溃。因此,总是这样做:

send(&foo, sizeof foo);

现在你受到保护了。当然,您复制了变量的名称,但如果您更改了它,则很有可能会以编译器可以检测到的方式中断。

kb5ga3dv

kb5ga3dv5#

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

查看this link了解相关说明

luaexgnf

luaexgnf6#

我建议永远不要使用sizeof(即使可以使用它)来获取两种不同大小的数组中的任何一种,无论是以元素数量还是以字节为单位,这是我在这里展示的最后两种情况。对于这两种大小中的每一种,都可以使用下面显示的宏来使其更安全。原因是为了让维护人员明白代码的意图,并在第一眼看到sizeof(ptr)sizeof(arr)的区别(这种编写方式并不明显),这样对于每个阅读代码的人来说,Bug都是显而易见的。

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月/日):


# 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()需要将此值作为其第三个参数。

对于其他两个大小,这是问题的主题,我们希望确保我们处理的是数组,如果不是,则中断编译,因为如果我们处理的是指针,我们将获得错误的值。当编译中断时,我们将能够很容易地看到,我们处理的不是数组,而是指针,我们只需用一个变量或宏来编写代码,该变量或宏将数组的大小存储在指针后面。

数组中的元素个数

这是最常见的一个,许多答案都提供了典型的宏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);
}

更新时间(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])),并希望使其安全。我们将需要C2X static_assert()和一些GCC扩展:表达式__builtin_types_compatible_p中的语句和声明:


# 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扩展名不能在文件范围内使用。为了能够在文件范围内使用宏,静态Assert必须位于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))

注意事项:

这段代码使用了以下扩展,这些扩展是完全必要的,并且它们的存在对于实现安全性是绝对必要的。如果您的编译器没有它们,或者一些类似的,那么您就不能达到这种级别的安全性。

我还利用了以下C2X功能。然而,使用较旧的标准可以使用一些卑鄙的技巧来克服它的缺失(例如,请参见:What is “:-!!” in C code?)(在C11中,您也有static_assert(),但它需要一条消息)。

wlwcrazw

wlwcrazw7#

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

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

代码最初位于以下位置:

C program to find the number of elements in an array

nnvyjq4y

nnvyjq4y8#

如果您知道数组的数据类型,则可以使用如下内容:

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(数组名称)都是一个指针。

w6mmgewl

w6mmgewl9#

每个人都在使用的宏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将以指针类型结束,并使整个宏无效(就像您试图在带有指针参数的函数中使用宏一样)。

归根结底,在这个特定的示例中,错误并不重要(所以我只是在浪费每个人的时间;哈哈!),因为您没有类型为“数组”的表达式。但我认为关于预处理器评估的真正要点是很重要的。

ct2axkht

ct2axkht10#

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


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

等等,无穷无尽。

myss37ts

myss37ts11#

数组的大小,以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
h43kikqp

h43kikqp12#

sizeof(array) / sizeof(array[0])
34gzjxbg

34gzjxbg13#


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

hvvq6cgz14#

如果您真的想这样做以传递数组,我建议实现一个结构来存储指向您想要的类型的指针、数组和表示数组大小的整数。然后,您可以将其传递给您的函数。只需将数组变量值(指向第一个元素的指针)赋给该指针。然后,您可以转到Array.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: %dn", 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;
}
n3h0vuf2

n3h0vuf215#

最好的方法是将此信息保存在结构中,例如:

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

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

相关问题