我知道如何参数传递给函数不是C标准的一部分,并且取决于硬件体系结构和调用约定。
我还知道优化的编译器可以自动内联函数以节省调用开销,并省略没有“副作用”的代码。
但是,我有一个关于特定案例的问题:
可以说有一个非平凡的函数,不能被内联或删除,必须被调用,该函数声明为不带任何参数:
int veryImportantFunc() {
/* do some important stuff */
return result;
}
但是此函数使用参数调用:
int result = veryImportantFunc(1, 2, 3);
是否允许编译器在不传递这些参数的情况下调用函数?
或者是否存在某些标准或技术限制会阻止这种优化?
此外,如果参数求值有副作用怎么办:
int counter = 1;
int result = veryImportantFunc(1, ++counter, 3);
即使没有通过结果,编译器是否有义务进行评估,还是放弃counter == 1
而放弃评估是合法的?
最后,关于其他参数呢?
char* anotherFunc(int answer) {
/* Do stuff */
return question;
}
如果这样调用此函数:
char* question = anotherFunc(42, 1);
编译器可以根据函数声明删除1吗?
编辑:要澄清一下:我无意编写示例中的那种代码,并且在我正在研究的任何代码中都没有找到它。
这个问题是要了解编译器如何工作以及相关标准怎么说,因此对所有建议我不要使用此类代码的人:谢谢,但我已经知道了。
答案 0 :(得分:3)
首先,“声明不带参数”是错误的。 int veryImportantFunc()
是一个接受任何参数的函数。这是过时的C风格,不应使用。对于不带参数的函数,请使用(void)
。
是否允许编译器在不传递这些参数的情况下调用函数?
如果实际函数定义与参数个数不匹配,则行为未定义。
另外,如果参数评估有副作用怎么办
没关系,因为参数是在调用函数之前进行评估的(未指定顺序)。
即使没有通过结果,编译器是否有义务进行评估,还是放弃计数器== 1放弃评估是合法的?
它将评估参数,然后调用未定义的行为。任何事情都会发生。
最后,关于其他参数呢?
您的示例无效,因为它不是有效的C语言。
答案 1 :(得分:1)
让我们说有一个非平凡的函数,它不能被内联或删除,必须被调用,该函数声明为不带任何参数:
int veryImportantFunc() { /* do some important stuff */ return result; }
但是此函数使用参数调用:
有两种可能性:
该函数使用“完整”原型声明,例如
int veryImportantFunc(void);
在这种情况下,带有额外参数的调用将不会编译,因为参数和参数的数量必须匹配;
该函数被声明为taking an unspecified number of arguments,即对呼叫站点可见的声明是
int veryImportantFunc();
在这种情况下,调用是未定义的行为,因为用法与实际的函数定义不匹配。
关于优化的所有其他注意事项并不是特别有趣,因为您尝试执行的操作是非法的,但您对此却一视同仁。
我们可以对此进行扩展,并设想一种情况,在这种情况下传递多余的无用参数是合法的,例如可变参数函数从不使用多余的参数。
在这种情况下,与往常一样,只要不影响可观察行为,编译器就可以自由地执行任何此类优化,并且继续执行“仿佛”根据C抽象机执行的操作
鉴于参数传递的细节不是可观察的 1 ,编译器可以原则上优化传递的参数,而参数评估如果对程序状态有一些可观察到的影响,可能仍需要进行。
话虽如此,我很难想象如何在“经典链接模型”中实现这种优化,但是使用LTCG并非不可能。
volatile
变量的读/写。答案 2 :(得分:1)
以下来自C标准的引号与您的不同问题有关:
6.5.2.2函数调用
...
2.如果表示被调用函数的表达式的类型包含原型,则自变量的数量应与参数的数量一致。
...
4.参数可以是任何完整对象类型的表达式。 在准备调用函数时,将评估参数,,并为每个参数分配相应参数的值。
...
6.如果表示被调用函数的表达式的类型不包含原型,则对每个自变量执行整数提升,并将具有float类型的自变量提升为双精度。这些称为默认参数提升。 如果参数的数量与参数的数量不相等,则行为未定义。。如果函数的类型定义为包含原型的类型,并且原型以省略号(,.. 。)或升级后的参数类型与参数类型不兼容,因此行为未定义。如果函数使用不包含原型的类型定义,并且升级后的参数类型与升级后的参数类型不兼容,则行为未定义。
...
10. 在函数指定符和实际参数的求值之后,但在实际调用之前,有一个序列点。调用函数中的每个求值(包括其他函数调用)在没有其他特定顺序之前或相对于被调用函数的执行不确定地确定被调用函数的主体的执行顺序。
答案 3 :(得分:0)
按照Pascals理论,错误的认为编译器可以进行此优化,而不是正确的认为是错误的。错误地定义功能没有任何目的。如果确实需要,可以随时在其前面放置一个存根:
int RealSlimShady(void) {
return Dufus;
}
int MaybeSlimShady(int Mathew, int Mathers) {
Used(Mathew);
Used(Mathers);
return RealSlimShady();
}
每个人都很高兴,如果您的编译器物有所值,那么代码开销为0。