【问题标题】:Forcing an array size in a function parameter in C when passing an array传递数组时在C中的函数参数中强制数组大小
【发布时间】:2019-10-04 04:50:19
【问题描述】:

上下文

在 C 语言中,我有一个以数组为参数的函数。此参数在此函数中用作输出。输出总是相同的大小。我会:

  • 让任何阅读代码的人都清楚所需的大小(不过,它已经在函数 cmets 中了),
  • 理想情况下,编译输出警告或错误,这样我就可以防止在编译时而不是运行时出现问题。

一个潜在的解决方案

我在这里找到: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 中,如何强制作为数组的函数参数具有给定大小?如果不是,它应该在编译时很明显。

【问题讨论】:

  • 如果您删除“至少”部分,您可以获得答案(好吧,您自己也有)。这部分的答案是“没办法”。
  • 如您所见,C 对此有一个特性,static 在参数声明中的数组大小中,但编译器对它的支持大多不存在。 (一些编译器如果看到你为这样的参数传递了一个空指针,他们会发出警告,但否则他们会忽略它。)你可以传递一个指向数组的指针,例如int (*)[5],这将要求调用者传递一个指针到 5 个int 的数组,但它必须正好是 5;他们无法传递 6 个 int 的数组。
  • @Eugene-sh 我之所以写这句话,至少是因为我提到的博客中的这句话:“编译器可以在看到调用者调用函数时发出警告,而不是 10 个或更多整数的数组。 ”。但是我可以删除这部分以使问题更清楚。
  • 不太清楚,是“可能”和“不可能”的区别。你有答案。
  • C11: 6.7.6.3.7: "将参数声明为“类型数组”应调整为“类型限定指针”,其中类型限定符(如果有)是在数组类型派生的[] 中指定的那些。如果关键字static 也出现在数组类型派生的[] 中,那么对于每次调用函数,对应的实际参数的值应提供对数组的第一个元素的访问,该数组的元素至少与大小表达式指定的一样多。"

标签: c arrays parameter-passing


【解决方案1】:

如果你传递一个指向数组的指针而不是指向它的第一个元素的指针,你会得到一个不兼容的指针警告:

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

【讨论】:

  • 可悲的是,这种方法也会导致 const 正确性问题,尤其是 typedef,因为使指针 const 的直观方式会导致不兼容的指针警告/错误
  • @bruno 是的,我谈到了传递的数组太小的情况,因为它是一个输出数组,所以函数可能会写入数组边界之外。这就是为什么我想在编译时确保传递的数组具有所需的大小。
  • 当你输入一个指向 VLA 的指针时会发生什么?它的大小在编译时是未知的,所以我想它不会抱怨。也许你仍然应该在函数中声明它的大小。
  • @Swordfish 除了 Vlad Tepesch 的评论外,使用您建议的方法在代码的可靠性和安全性方面有什么缺点吗?
  • @Mat.R 如果我们谈论的是静态类型数组(大小在编译时已知),编写不超出这些数组边界的代码应该不会太难。但是,我的建议可以满足您的要求,它会在编译时发出警告(或错误)。如果您想要一种允许最小大小数组的方法,那么Eric Postpischils 的答案就是给您。
【解决方案2】:

要测试传递的数组(不是指针)的大小至少为 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,从而无需进一步的 @ 987654330@ 或 #define 指令。)

一般来说,这样的代码不太可能有用,因为程序经常将指针传递给数组的第一个元素(或数组中间的元素),并且无法测试空间中有多少元素一个指针指向。因此,这仅在我们要求将数组作为参数给出的代码中才有用。并且此代码并未强制执行该要求。

【讨论】:

  • 我能问你是什么意思吗:“所以这仅在我们需要将数组作为参数给出的代码中有用。并且此代码不强制执行该要求。”?将数组传递给函数实际上是传递指针,那么我们如何强制传递数组呢?或者我只是没听懂你的句子。
  • @Mat.R;参数是一个数组。在评估函数调用期间,会评估参数。在该评估中,数组被转换为指向其第一个元素的指针。但它最初是一个数组,当宏将它与sizeof 一起使用时。 sizeof 作用于数组,而不是指针。
猜你喜欢
  • 1970-01-01
  • 2011-03-04
  • 2017-05-15
  • 2021-02-13
  • 1970-01-01
  • 1970-01-01
  • 2023-03-24
  • 2017-03-25
  • 2018-11-27
相关资源
最近更新 更多