是否允许 C 编译器优化掉未使用的函数参数?
Is C compiler allowed to optimize away unused function arguments?
我知道如何将参数传递给函数不是 C 标准的一部分,它取决于硬件体系结构和调用约定。
我也知道优化编译器可能会自动内联函数以节省调用开销,并省略没有 "side effects".
的代码
但是,我有一个关于具体案例的问题:
假设有一个不能内联或删除的重要函数,必须调用,声明不带参数:
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吗?
编辑: 澄清一下:我无意编写示例中的那种代码,而且我在我正在处理的任何代码中都没有发现这一点。
这道题是想了解编译器是怎么工作的,相关标准是怎么说的,所以对所有建议我远离这种代码的人:谢谢,但我已经知道了。
根据 Pascals 理论,认为编译器可以进行这种优化是错误的,而不是认为它不能进行优化是正确的。错误地定义一个函数是没有意义的;如果你真的必须,你总是可以在它前面放一个存根:
int RealSlimShady(void) {
return Dufus;
}
int MaybeSlimShady(int Mathew, int Mathers) {
Used(Mathew);
Used(Mathers);
return RealSlimShady();
}
大家都很高兴,如果您的编译器物有所值,那么代码开销将为 0。
Lets say there is a non trivial function that can not be inlined or removed, and must be called, that is declared to take no arguments:
int veryImportantFunc() {
/* do some important stuff */
return result;
}
But this function is called with arguments:
有两种可能:
函数使用 "full" 原型声明,例如
int veryImportantFunc(void);
在这种情况下,带有额外参数的调用将无法编译,因为参数和参数的数量必须匹配;
函数被声明为taking an unspecified number of arguments,即声明对调用点可见是
int veryImportantFunc();
在这种情况下,调用是未定义行为,因为用法与实际函数定义不匹配。
关于优化的所有其他考虑都不是特别有趣,因为无论您怎么看,您尝试做的事情都是非法的。
我们可以扩展这一点并想象一种情况,其中传递额外的无用参数是合法的,例如从不使用额外参数的可变参数函数。
在这种情况下,与往常一样,只要可观察到的行为不受影响,编译器就可以自由执行任何此类优化,并继续执行"as if"根据C抽象机。
鉴于传递参数的细节不是可观察的1,编译器可以 原则上优化了参数传递,而如果参数评估对程序状态有一些可观察到的影响,则可能仍需要进行评估。
话虽这么说,但我很难想象如何在 "classical linking model" 中实现这种优化,但使用 LTCG 应该不是不可能的。
- 根据 C 标准,唯一可观察到的影响是 IO 和
volatile
变量上的 reads/writes。
C 标准中的以下引述与您的不同问题相关:
6.5.2.2 Function calls
...
2. If the expression that denotes the called function has a type that includes a prototype, the number of arguments shall agree with the number of parameters.
...
4. An argument may be an expression of any complete object type. In preparing for the call to a function, the arguments are evaluated, and each parameter is assigned the value of the corresponding argument.
...
6. If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions. If the number of arguments does not equal the number of parameters, the behavior is undefined. If the function is defined with a type that includes a prototype, and either the prototype ends with an ellipsis (, ...) or the types of the arguments after promotion are not compatible with the types of the parameters, the behavior is undefined. If the function is defined with a type that does not include a prototype, and the types of the arguments after promotion are not compatible with those of the parameters after promotion, the behavior is undefined.
...
10. There is a sequence point after the evaluations of the function designator and the actual arguments but before the actual call. Every evaluation in the calling function (including other function calls) that is not otherwise specifically sequenced before or after the execution of the body of the called function is indeterminately sequenced with respect to the execution of the called function.
首先,"declared to take no arguments" 是错误的。 int veryImportantFunc()
是一个接受任何参数的函数。这是过时的 C 风格,不应使用。对于不带参数的函数,使用 (void)
.
Is the compiler allowed to call the function without passing these arguments?
如果实际函数定义与参数数量不匹配,则行为未定义。
Also, what if argument evaluation has side effects
没关系,因为在调用函数之前会计算参数(以未指定的顺序)。
Is the compiler obligated to evaluate even without passing the result, or would it be legal to drop the evaluation leaving counter == 1?
它将评估参数然后调用未定义的行为。什么事都有可能发生。
And finally, what about extra arguments:
您的示例无法编译,因为它不是有效的 C。
我知道如何将参数传递给函数不是 C 标准的一部分,它取决于硬件体系结构和调用约定。
我也知道优化编译器可能会自动内联函数以节省调用开销,并省略没有 "side effects".
的代码但是,我有一个关于具体案例的问题:
假设有一个不能内联或删除的重要函数,必须调用,声明不带参数:
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吗?
编辑: 澄清一下:我无意编写示例中的那种代码,而且我在我正在处理的任何代码中都没有发现这一点。
这道题是想了解编译器是怎么工作的,相关标准是怎么说的,所以对所有建议我远离这种代码的人:谢谢,但我已经知道了。
根据 Pascals 理论,认为编译器可以进行这种优化是错误的,而不是认为它不能进行优化是正确的。错误地定义一个函数是没有意义的;如果你真的必须,你总是可以在它前面放一个存根:
int RealSlimShady(void) {
return Dufus;
}
int MaybeSlimShady(int Mathew, int Mathers) {
Used(Mathew);
Used(Mathers);
return RealSlimShady();
}
大家都很高兴,如果您的编译器物有所值,那么代码开销将为 0。
Lets say there is a non trivial function that can not be inlined or removed, and must be called, that is declared to take no arguments:
int veryImportantFunc() { /* do some important stuff */ return result; }
But this function is called with arguments:
有两种可能:
函数使用 "full" 原型声明,例如
int veryImportantFunc(void);
在这种情况下,带有额外参数的调用将无法编译,因为参数和参数的数量必须匹配;
函数被声明为taking an unspecified number of arguments,即声明对调用点可见是
int veryImportantFunc();
在这种情况下,调用是未定义行为,因为用法与实际函数定义不匹配。
关于优化的所有其他考虑都不是特别有趣,因为无论您怎么看,您尝试做的事情都是非法的。
我们可以扩展这一点并想象一种情况,其中传递额外的无用参数是合法的,例如从不使用额外参数的可变参数函数。
在这种情况下,与往常一样,只要可观察到的行为不受影响,编译器就可以自由执行任何此类优化,并继续执行"as if"根据C抽象机。
鉴于传递参数的细节不是可观察的1,编译器可以 原则上优化了参数传递,而如果参数评估对程序状态有一些可观察到的影响,则可能仍需要进行评估。
话虽这么说,但我很难想象如何在 "classical linking model" 中实现这种优化,但使用 LTCG 应该不是不可能的。
- 根据 C 标准,唯一可观察到的影响是 IO 和
volatile
变量上的 reads/writes。
C 标准中的以下引述与您的不同问题相关:
6.5.2.2 Function calls
...
2. If the expression that denotes the called function has a type that includes a prototype, the number of arguments shall agree with the number of parameters.
...
4. An argument may be an expression of any complete object type. In preparing for the call to a function, the arguments are evaluated, and each parameter is assigned the value of the corresponding argument.
...
6. If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions. If the number of arguments does not equal the number of parameters, the behavior is undefined. If the function is defined with a type that includes a prototype, and either the prototype ends with an ellipsis (, ...) or the types of the arguments after promotion are not compatible with the types of the parameters, the behavior is undefined. If the function is defined with a type that does not include a prototype, and the types of the arguments after promotion are not compatible with those of the parameters after promotion, the behavior is undefined.
...
10. There is a sequence point after the evaluations of the function designator and the actual arguments but before the actual call. Every evaluation in the calling function (including other function calls) that is not otherwise specifically sequenced before or after the execution of the body of the called function is indeterminately sequenced with respect to the execution of the called function.
首先,"declared to take no arguments" 是错误的。 int veryImportantFunc()
是一个接受任何参数的函数。这是过时的 C 风格,不应使用。对于不带参数的函数,使用 (void)
.
Is the compiler allowed to call the function without passing these arguments?
如果实际函数定义与参数数量不匹配,则行为未定义。
Also, what if argument evaluation has side effects
没关系,因为在调用函数之前会计算参数(以未指定的顺序)。
Is the compiler obligated to evaluate even without passing the result, or would it be legal to drop the evaluation leaving counter == 1?
它将评估参数然后调用未定义的行为。什么事都有可能发生。
And finally, what about extra arguments:
您的示例无法编译,因为它不是有效的 C。