根据我的理解,我说这是正确的pure and const,但如果有人对两者有准确的定义,请说出来。这变得很棘手,因为 GCC 文档没有准确说明函数“除了返回值之外没有任何影响”的含义(例如pure)或“不检查除参数之外的任何值”(对于const)。显然所有的函数都有some影响(他们使用处理器周期,修改内存)并检查some值(功能代码、常量)。
“副作用”必须根据 C 编程语言的语义来定义,但我们可以根据这些属性的目的猜测 GCC 人员的意思,即实现额外的优化(至少,这就是我的意思)假设它们是为了)。
如果以下某些内容太基础,请原谅我......
纯函数可以参与公共子表达式消除。它们的特点是不修改环境,因此编译器可以自由地调用它更少的次数,而不会改变程序的语义。
z = f(x);
y = f(x);
becomes:
z = y = f(x);
或者被完全淘汰,如果z
and y
未使用。
所以我最好的猜测是“纯粹”的工作定义是“任何可以在不改变程序语义的情况下调用更少次数的函数”。但是,函数调用可能无法移动,例如,
size_t l = strlen(str); // strlen is pure
*some_ptr = '\0';
// Obviously, strlen can't be moved here...
常量函数可以重新排序,因为它们不依赖于动态环境。
// Assuming x and y not aliased, sin can be moved anywhere
*some_ptr = '\0';
double y = sin(x);
*other_ptr = '\0';
所以我最好的猜测是“const”的工作定义是“可以在任何时候调用而不改变程序语义的任何函数”。然而,存在一个危险:
__attribute__((const))
double big_math_func(double x, double theta, double iota)
{
static double table[512];
static bool initted = false;
if (!initted) {
...
initted = true;
}
...
return result;
}
由于它是 const,编译器可以对其重新排序......
pthread_mutex_lock(&mutex);
...
z = big_math_func(x, theta, iota);
...
pthread_mutex_unlock(&mutex);
// big_math_func might go here, if the compiler wants to
在这种情况下,即使它只出现在代码的关键部分内,也可以从两个处理器同时调用它。然后处理器可以决定推迟更改table
更改为后initted
已经经历了,这是坏消息。您可以通过内存屏障或pthread_once
.
我认为这个错误不会出现在 x86 上,而且我认为它不会出现在许多没有多个物理处理器(不是核心)的系统上。因此,它可以正常工作很长时间,然后在双插槽 POWER 计算机上突然出现故障。
结论:这些定义的优点是它们清楚地表明编译器在存在这些属性的情况下可以进行什么样的更改,而这(我认为)在 GCC 文档中有些模糊。缺点是不清楚这些是 GCC 团队使用的定义。
例如,如果您查看 Haskell 语言规范,您会发现纯度的定义更加精确,因为纯度对于 Haskell 语言非常重要。
Edit:我无法强迫 GCC 或 Clang 移动一个单独的__attribute__((const))
函数调用跨越另一个函数调用,但将来似乎完全有可能发生类似的事情。记得当-fstrict-aliasing
成为默认设置,然后每个人的程序中突然出现了更多错误?诸如此类的事情让我变得谨慎。
在我看来,当你标记一个函数时__attribute__((const))
,您向编译器承诺,只要参数相同,无论在程序执行期间何时调用该函数,调用的结果都是相同的。
然而,我确实想出了一种将 const 函数移出关键部分的方法,尽管我所做的方法可以被称为某种“作弊”。
__attribute__((const))
extern int const_func(int x);
int func(int x)
{
int y1, y2;
y1 = const_func(x);
pthread_mutex_lock(&mutex);
y2 = const_func(x);
pthread_mutex_unlock(&mutex);
return y1 + y2;
}
编译器将其转换为以下代码(来自程序集):
int func(int x)
{
int y;
y = const_func(x);
pthread_mutex_lock(&mutex);
pthread_mutex_unlock(&mutex);
return y * 2;
}
请注意,这不会发生在仅__attribute__((pure))
, the const
属性并且仅const
属性触发此行为。
正如你所看到的,临界区内的调用消失了。保留早期的调用似乎相当任意,而且我不愿意打赌编译器不会在未来的某个版本中就保留哪个调用做出不同的决定,或者是否会将函数调用移动到某个地方完全是别的。
结论2:请小心行事,因为如果您不知道对编译器做出的承诺,编译器的未来版本可能会让您感到惊讶。