我可以告诉我的编译器忽略语句或函数的副作用吗?
Can I tell my compiler to ignore the side effects of a statement or a function?
假设我的代码具有以下功能:
inline int foo() {
bar();
int y = baz();
return y;
}
并假设 bar()
和 baz()
有副作用。
如果我写:
int z = foo();
printf("z is %d\n", z);
那么显然 bar()
和 baz()
都必须执行。但是如果我写:
foo();
并且不使用 return 值,调用 baz()
的唯一原因是它的副作用。
有什么方法可以告诉我的编译器(对于 GCC、clang、MSVC、Intel 中的任何一个)类似“您可以忽略 baz()
的副作用来决定是否对其进行优化离开”?或者“为了决定是否将其优化掉,您可以忽略该指令的副作用”?
此问题是针对 C 和 C++ 编写的,但可能与所有类型的过程语言相关。让我们把它变成 C++,因为那是我现在用得最多的。
注:
bar()
、baz()
不在我的控制范围内。
foo()
的签名不能更改。当然,如果我愿意,我可以使用一些代理并懒惰地应用 baz()
;或者为不使用 y
的情况编写一个不同的函数。这不是我要问的。
作为标准 C 和 C++ 的扩展,gcc 和 clang 支持 pure
function attribute 通知编译器该函数没有副作用,可以删除不需要的调用。如果你用这个属性声明一个函数,即使你在说谎,编译器也会相信你。
#include <iostream>
static int foo() __attribute__((pure));
static int foo() {
std::cout << "I am a side effect!" << std::endl;
return 17;
}
int main() {
foo();
return 0;
}
This prints no output. 请注意,clang
会发出警告,提示您忽略了 pure
函数的 return 值。
(特别是,当你在 C 中做类似的事情时,gcc optimizes out the call with -O0
, but leaves it in with -O2
!)
函数的定义不需要在作用域内,即使先前的声明已经在作用域内,您也可以 re-declare 具有此属性的函数。所以它也可以用于库函数。 Example.
当然,对编译器说谎总是需要您自担风险,并且可以想象这可能会产生我没有想到的潜在不良影响。如果您愿意仔细检查生成的代码以确保它确实在做您想要的事情,这可能是一个有用的 hack,但我不会太相信它。
在很多情况下,函数的副作用只有在 return 值最终被使用时才相关,但据我所知,C 和 C++ 都没有指示它们的方法。
作为一个常见的例子,执行一些计算和 return 结果的例程可能很有用,但如果(例如由于溢出)它们无法 return 一个没有明显错误的结果。根据函数的使用方式,让它陷入陷阱可能比 return 一个 observably-wrong 答案更可取,但可能并不比让它的行为方式与以某种方式产生正确的方式无法区分更好回答。如果一个程序执行了十几个计算,但只使用了其中的十个,那么完全跳过未使用的计算可能比生成代码以查看它们是否会发生溢出更有效。
也许有用的是测试编译器是否可以保证特定左值的值永远不会明显影响程序行为的结构;一个实现总是被允许说它不能,但是在编译器可以提供这样的保证的情况下,它会允许 suitably-written 代码说,例如“如果 x 的值,即此函数将 return,将永远不会被使用,请不要费心去计算它;否则,检查是否会发生溢出和陷阱;如果 return 值被使用并且不会溢出,计算函数的值。"
不过,我不知道有任何 C 实现提供了这样的功能。
假设我的代码具有以下功能:
inline int foo() {
bar();
int y = baz();
return y;
}
并假设 bar()
和 baz()
有副作用。
如果我写:
int z = foo();
printf("z is %d\n", z);
那么显然 bar()
和 baz()
都必须执行。但是如果我写:
foo();
并且不使用 return 值,调用 baz()
的唯一原因是它的副作用。
有什么方法可以告诉我的编译器(对于 GCC、clang、MSVC、Intel 中的任何一个)类似“您可以忽略 baz()
的副作用来决定是否对其进行优化离开”?或者“为了决定是否将其优化掉,您可以忽略该指令的副作用”?
此问题是针对 C 和 C++ 编写的,但可能与所有类型的过程语言相关。让我们把它变成 C++,因为那是我现在用得最多的。
注:
bar()
、baz()
不在我的控制范围内。foo()
的签名不能更改。当然,如果我愿意,我可以使用一些代理并懒惰地应用baz()
;或者为不使用y
的情况编写一个不同的函数。这不是我要问的。
作为标准 C 和 C++ 的扩展,gcc 和 clang 支持 pure
function attribute 通知编译器该函数没有副作用,可以删除不需要的调用。如果你用这个属性声明一个函数,即使你在说谎,编译器也会相信你。
#include <iostream>
static int foo() __attribute__((pure));
static int foo() {
std::cout << "I am a side effect!" << std::endl;
return 17;
}
int main() {
foo();
return 0;
}
This prints no output. 请注意,clang
会发出警告,提示您忽略了 pure
函数的 return 值。
(特别是,当你在 C 中做类似的事情时,gcc optimizes out the call with -O0
, but leaves it in with -O2
!)
函数的定义不需要在作用域内,即使先前的声明已经在作用域内,您也可以 re-declare 具有此属性的函数。所以它也可以用于库函数。 Example.
当然,对编译器说谎总是需要您自担风险,并且可以想象这可能会产生我没有想到的潜在不良影响。如果您愿意仔细检查生成的代码以确保它确实在做您想要的事情,这可能是一个有用的 hack,但我不会太相信它。
在很多情况下,函数的副作用只有在 return 值最终被使用时才相关,但据我所知,C 和 C++ 都没有指示它们的方法。
作为一个常见的例子,执行一些计算和 return 结果的例程可能很有用,但如果(例如由于溢出)它们无法 return 一个没有明显错误的结果。根据函数的使用方式,让它陷入陷阱可能比 return 一个 observably-wrong 答案更可取,但可能并不比让它的行为方式与以某种方式产生正确的方式无法区分更好回答。如果一个程序执行了十几个计算,但只使用了其中的十个,那么完全跳过未使用的计算可能比生成代码以查看它们是否会发生溢出更有效。
也许有用的是测试编译器是否可以保证特定左值的值永远不会明显影响程序行为的结构;一个实现总是被允许说它不能,但是在编译器可以提供这样的保证的情况下,它会允许 suitably-written 代码说,例如“如果 x 的值,即此函数将 return,将永远不会被使用,请不要费心去计算它;否则,检查是否会发生溢出和陷阱;如果 return 值被使用并且不会溢出,计算函数的值。"
不过,我不知道有任何 C 实现提供了这样的功能。