当声明一个访问内存中多个连续值的函数时,我通常使用类似数组的参数
f(int a[4]);
对于我来说,它工作正常。但是,我最近读了the opinion of Linus Torvalds。
所以我想知道今天是否认为数组参数已过时?更具体地说,
无论如何,指向数组的指针呢?
void f(int (*a)[4]);
请注意,此表格不容易出现“ sizeof”错误。但是在这种情况下效率如何?我知道GCC会生成相同的代码(link)。总是这样吗?在这种情况下,进一步优化的机会又如何呢?
答案 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个元素的数组中的第一个。
gets
函数,并在C2011中将其从标准库中删除的原因-无法得知目标缓冲区的大小,因此它将高兴地在输入结束时写入输入数组和破坏者之后的一切。这就是为什么它是如此受欢迎的恶意软件利用。