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

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

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

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

ct2axkht

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

等等,无穷无尽。

w6mmgewl

w6mmgewl17#

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

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

nnvyjq4y

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

wlwcrazw

wlwcrazw19#

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

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

代码最初位于以下位置:

C program to find the number of elements in an array

luaexgnf

luaexgnf20#

我建议永远不要使用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(),但它需要一条消息)。

kb5ga3dv

kb5ga3dv21#

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

查看this link了解相关说明

gj3fmq9x

gj3fmq9x22#

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);

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

4si2a6ki

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));
vcudknz3

vcudknz324#

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

相关问题