我试图理解纯函数,并且一直在阅读有关该主题的维基百科文章。我编写了最小的示例程序,如下所示:
#include <stdio.h>
static int a = 1;
static __attribute__((pure)) int pure_function(int x, int y)
{
return x + y;
}
static __attribute__((pure)) int impure_function(int x, int y)
{
a++;
return x + y;
}
int main(void)
{
printf("pure_function(0, 0) = %dn", pure_function(0, 0));
printf("impure_function(0, 0) = %dn", impure_function(0, 0));
return 0;
}
我用gcc -O2 -Wall -Wextra
编译了这个程序,期望应该发出错误,或者至少是一个警告,用于用__attribute__((pure))
装饰impure_function()
。但是,我没有收到任何警告或错误,并且该程序也运行没有问题。
用__attribute__((pure))
标记impure_function()
不是不正确吗?如果是这样,为什么即使使用-Wextra
和-Wall
标志,它也能编译时没有任何错误或警告?
提前感谢!
这样做是不正确的,您有责任正确使用该属性。
请看这个例子:
static __attribute__((pure)) int impure_function(int x, int y)
{
extern int a;
a++;
return x + y;
}
void caller()
{
impure_function(1, 1);
}
GCC(带-O1
)为函数caller
生成的代码为:
caller():
ret
如您所见,impure_function
调用已被完全删除,因为编译器将其视为"纯"。
如果 GCC 看到函数的定义,它可以在内部自动将函数标记为"纯":
static __attribute__((noinline)) int pure_function(int x, int y)
{
return x + y;
}
void caller()
{
pure_function(1, 1);
}
生成的代码:
caller():
ret
因此,在编译器可见的函数上使用此属性是没有意义的。当定义不可用时,例如,当在另一个 DLL 中定义函数时,应该使用它。这意味着当它在适当的位置使用时,编译器无论如何都无法执行健全性检查。因此,实现警告不是很有用(尽管并非毫无意义)。
我认为没有什么可以阻止GCC开发人员实施这样的警告,除了必须花费的时间。
纯函数是优化编译器的提示。也许,当你只传递-O0
时,gcc
并不关心纯函数(默认优化)。因此,如果f
是纯的(并且在翻译单元之外定义,例如在某些外部库中),GCC 编译器可能会将y = f(x) + f(x);
优化为类似
{
int tmp = f(x); /// tmp is a fresh variable, not appearing elsewhere
y = tmp + tmp;
}
但是如果f
不是纯粹的(这是通常的情况:想想f
调用printf
或malloc
),则禁止进行此类优化。
像sin
或sqrt
这样的标准数学函数是纯粹的(除了IEEE舍入模式的疯狂,请参阅 http://floating-point-gui.de/和Fluctuat了解更多信息),并且它们足够复杂,可以计算以使此类优化值得。
您可以使用gcc -O2 -Wall -fdump-tree-all
编译代码,以猜测编译器内部发生的情况。您可以添加-fverbose-asm -S
标志以获取生成的*.s
汇编程序文件。
您还可以阅读比斯蒙报告草案(特别是其§1.4部分)。它可能会给出一些与您的问题相关的直觉。
在你的特殊情况下,我猜gcc
正在内联你的电话;然后纯度就不那么重要了。
如果你有时间,你可以考虑编写自己的GCC插件来发出这样的警告。您将花费数月时间编写它!这些旧幻灯片可能仍然对您有用,即使详细信息已过时。
在理论层面上,要注意赖斯定理。其结果是,纯函数的完美优化可能是不可能的。
请注意位于孟买的海湾合作委员会资源中心。