在C语言中传递数组时,强制声明函数参数中的数组大小

7

背景

在C语言中,我有一个函数接受一个数组作为参数。这个参数被用作该函数的输出。该输出始终具有相同的大小。我的要求是:

  • 让任何阅读代码的人都能清楚地知道所需的大小(尽管它已经在函数注释中),
  • 最好编译时输出一个警告或错误,以便我可以在运行时之前预防问题。

一个可能的解决方案

我在这里找到了一个看起来像解决方案的网址:https://hamberg.no/erlend/posts/2013-02-18-static-array-indices.html,但如果我尝试传递比所需大小更小的数组时,我无法在编译时获得警告或错误。

下面是我的完整程序main.c:

void test_array(int arr[static 5]);

int main(void)
{
    int array[3] = {'\0'};

    test_array(array); // A warning/error should occur here at compilation-time
                       // telling me my array does not meet the required size.

    return 0;
}

void test_array(int arr[static 5])
{
    arr[2] = 0x7; // do anything...
}

与本博客相反,我使用gcc(版本7.4.0)而不是clang,使用以下命令:

gcc -std=c99 -Wall -o main.out main.c

在我的代码中,我们可以看到test_array()函数需要一个5个元素的数组。我传递了一个3个元素的数组。我希望编译器能够给出相应的提示。

问题

在C语言中,如何强制一个函数参数作为一个数组具有给定的大小?如果不是,它应该在编译时可见。


3
你可能已经注意到,C语言有一个特性可以在参数声明的数组大小中使用 static ,但编译器对此的支持基本上不存在。(有些编译器会在看到你向这样的参数传递空指针时发出警告,但他们通常不予理睬。)你可以传递一个数组指针,例如 int (*)[5],这将要求调用者传递一个指向 5 个 int 数组的指针,但它必须恰好是 5 个;他们不能传递一个 6 个 int 的数组。 - Eric Postpischil
@Eugene-sh,我写了至少是因为我在提到的博客中有这样一句话:“当编译器发现调用者使用的不是一个包含10个或更多int的数组时,它可以警告调用者。”。但是我可以删除这部分内容以使问题更清晰。 - Mat.R
@EricPostpischil,根据Eugene Sh的评论,我删除了我的问题中“至少”的部分。因此,您的建议是有效的。 - Mat.R
@CacahueteFrito 没有,什么也没有。 - Mat.R
1
C11: 6.7.6.3.7: "将参数声明为‘‘类型数组’’时,应调整为‘‘限定指向类型的指针’’,其中类型限定符(如果有)是在数组类型派生的 [] 中指定的。如果关键字 static 也出现在数组类型派生的 [] 中,则对于每次函数调用,相应实际参数的值应提供访问至少具有由大小表达式指定的元素数量的数组的第一个元素的访问权限。” - alx - recommends codidact
显示剩余7条评论
2个回答

9
如果传递数组的指针而不是指向其第一个元素的指针,则会收到不兼容指针警告。
void foo(int (*bar)[42])
{}

int main(void)
{
    int a[40];
    foo(&a);  // warning: passing argument 1 of 'foo' from incompatible pointer type [-Werror=incompatible-pointer-types]
    // note: expected 'int (*)[42]' but argument is of type 'int (*)[40]'

    int b[45];
    foo(&b);  // warning: passing argument 1 of 'foo' from incompatible pointer type [-Werror=incompatible-pointer-types]
    // note: expected 'int (*)[42]' but argument is of type 'int (*)[45]'
}

使用-Werror编译可以将警告变成错误。

godbolt


2
遗憾的是,这种方法也会导致const正确性方面的问题,特别是在typedef中,因为使指针const的直觉方式会导致不兼容的指针警告/错误。 - vlad_tepesch
@bruno 是的,我谈到了传递的数组太小的情况,由于它是一个输出数组,函数有可能会写在数组边界之外。这就是为什么我希望在编译时确定传递的数组大小是否符合要求。 - Mat.R
当您输入指向VLA的指针时会发生什么?它的大小在编译时是未知的,所以我猜它不会抱怨。您仍然应该在函数内部断言其大小,也许。 - alx - recommends codidact
@Mat.R 如果我们谈论的是静态类型数组(在编译时已知大小),那么编写不会超出这些数组边界的代码应该不难。然而,我的建议是按照你所要求的做法,在编译时发出警告(或错误)。如果你想要一个允许最小大小数组的方法,那么Eric Postpischil的答案适合你。 - Swordfish
@Swordfish 是的,你的建议完全回答了我的问题。在决定接受什么之前我会尝试一点点不同的东西。我在谈论静态类型数组。你是说我们可以只使用一个常量 #define ARR_SIZE 42 在声明数组和你的 foo 函数中,以避免超出数组边界写入吗?还是你有其他想法? - Mat.R
显示剩余2条评论

4
为了测试传入的数组(不是指针)大小至少为5个元素,可以使用Static_assert,并且可以通过预处理器宏插入必要的_Static_assert
在函数声明后面插入:
#define test_array(arr) \
    do \
    { \
        _Static_assert(sizeof (arr) / sizeof *(arr) >= 5, "Array is too small."); \
       test_array(arr); \
    } while (0)

在定义函数之前,插入以下内容: (do … while (0)是一种经典的宏定义方式,使它在语法上像语句一样,以便可以跟随;并与if语句等一起按预期流程执行。)
#undef test_array

(如果函数有更多的用途,必须插入另一个#define的副本。或者,该函数可以在源文件中早期定义,并跟随一个#define,从而消除任何进一步使用#undef#define指令的需要。)
一般来说,此类代码不太可能有用,因为程序通常传递指向数组第一个元素(或中间元素)的指针,无法测试指针所指向空间的元素数量。因此,此代码仅在需要将数组作为参数给出的代码中有用。这种要求不是由此代码强制执行的。

我可以问一下你的意思是什么:“所以这只在需要将数组作为参数给出的代码中有用。但是这个要求不由这段代码强制执行。”?事实上,将数组传递给函数实际上是传递指针,那么我们如何强制要求传递一个数组呢?或者可能是我没有理解你的句子。 - Mat.R
@Mat.R; 参数是一个数组。在函数调用的评估过程中,参数会被评估。在该评估中,该数组会被转换为指向其第一个元素的指针。但它最初是一个数组,当宏使用它与 sizeof 一起时。sizeof 操作的是数组,而不是指针。 - Eric Postpischil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接