在C中使用数组参数是否被认为是不好的做法?

时间:2019-01-14 16:50:21

标签: c arrays pointers pass-by-reference

当声明一个访问内存中多个连续值的函数时,我通常使用类似数组的参数

f(int a[4]);

对于我来说,它工作正常。但是,我最近读了the opinion of Linus Torvalds

所以我想知道今天是否认为数组参数已过时?更具体地说,

  • 在任何情况下,编译器都可以利用此信息(数组大小)来检查越界访问,或者
  • 在任何情况下,这种技术都会带来一些优化机会?

无论如何,指向数组的指针呢?

void f(int (*a)[4]);

请注意,此表格不容易出现“ sizeof”错误。但是在这种情况下效率如何?我知道GCC会生成相同的代码(link)。总是这样吗?在这种情况下,进一步优化的机会又如何呢?

2 个答案:

答案 0 :(得分:7)

如果你写

void f(int a[4]);
与编译器具有完全相同含义的

void f(int *a);

这就是为什么Linus会这样认为。 [4] 看起来像,它定义了数组的预期大小,但没有。当您尝试维护一个大型而复杂的程序时,代码的含义与实际含义之间的不匹配非常糟糕。

(通常,我建议人们假设Linus的观点是正确的。在这种情况下,我同意他的观点,但我不会这么生气地表达出来。)

自C99以来,有一个变体,表示 表示其含义:

void f(int a[static 4]);

也就是说,要求f的所有调用者提供至少四个int数组的指针;如果没有,则程序具有未定义的行为。这至少可以在原则上为优化器提供帮助(例如,可能意味着a[i]内部f上的循环可以被矢量化。)

您的替代结构

void f(int (*a)[4]);

赋予参数a不同的类型(“指向4个int的数组的指针”而不是““指向int的指针”)。这种类型的数组符号等效为

void f(int a[][4]);

这样写,当f的参数是一个内部尺寸为4的二维数组时,应该立即声明该声明是适当的。 / p>

sizeof问题是另一种蠕虫病毒。我的建议是避免几乎不惜一切代价在函数参数上使用sizeof不要扭曲函数的参数列表,使sizeof在函数内部“正确”显示;这使得正确调用 函数变得更加困难,并且您调用该函数的次数可能比实现它的次数多。

答案 1 :(得分:0)

除非它是sizeof或一元&运算符的操作数,否则它是用于在声明中初始化类型为 expression 的声明中的字符数组的字符串文字。 “ T的N元素数组将被转换(“衰变”)为“指向T的指针”类型的表达式,该表达式的值将是其中的第一个元素的地址数组。

将数组表达式作为参数传递给函数时:

int arr[100];
...
foo( arr );

该函数实际接收的是指向数组第一个元素的指针,而不是数组的副本。行为与您编写的行为完全相同

foo( &arr[0] );

有一条规则,将T a[N]T a[]类型的函数参数“调整”为T *a,所以如果您的函数声明为

void foo( int a[100] )

它会像您写的那样被解释

void foo( int *a )

这有两个重大后果:

  • 数组是通过引用隐式传递给函数的,因此对函数中数组内容的更改会反映在调用方中(与字面上的其他所有类型不同);

  • 您无法使用sizeof来确定传递的数组中有多少个元素,因为无法从指针获取该信息。如果您的函数需要知道数组的物理大小才能正确使用它,则必须将该长度作为单独的参数 1 传递。

在我自己的代码中,我不在函数参数列表中使用数组样式的声明-该函数接收的是一个指针,因此我使用了指针样式的声明。我可以看到使用数组样式声明的参数,主要是作为文档说明(此函数期望使用这种大小的数组),但是我认为加强参数的指针性很有价值。

请注意,如果我调用

,则指向数组的指针也会遇到相同的问题
foo( &arr );

然后需要foo的原型

void foo( int (*a)[100] );

但是那也是我所称的原型

void bar[10][100];

foo( bar );  

就像您不知道参数a指向单个int还是指向int序列中的第一个一样,您也不知道bar是否指向指向单个100个元素的数组,或指向100个元素的数组中的第一个。


  1. 这就是为什么在C99之后不推荐使用gets函数,并在C2011中将其从标准库中删除的原因-无法得知目标缓冲区的大小,因此它将高兴地在输入结束时写入输入数组和破坏者之后的一切。这就是为什么它是如此受欢迎的恶意软件利用。