为什么 C + + 编译器不优化这个条件布尔赋值作为一个无条件赋值呢?

考虑以下函数:

void func(bool& flag)
{
if(!flag) flag=true;
}

在我看来,如果标志有一个有效的布尔值,这将等同于无条件设置为 true,像这样:

void func(bool& flag)
{
flag=true;
}

然而,gcc 和 clang 都没有以这种方式进行优化ーー它们都在 -O3优化级别产生以下结果:

_Z4funcRb:
.LFB0:
.cfi_startproc
cmp BYTE PTR [rdi], 0
jne .L1
mov BYTE PTR [rdi], 1
.L1:
rep ret

My question is: is it just that the code is too special-case to care to optimize, or are there any good reasons why such optimization would be undesired, given that flag is not a reference to volatile? It seems the only reason which might be is that flag could somehow have a non-true-or-false value without undefined behavior at the point of reading it, but I'm not sure whether this is possible.

7887 次浏览

由于 缓存一致性的考虑,这可能会对程序的性能产生负面影响。每次调用 func()时写入 flag将会弄脏包含的缓存行。无论写入的值与写入之前在目标地址找到的位是否完全匹配,这种情况都会发生。


剪辑

Hvd 提供了另一个 good reason来防止这种优化。这是一个更有说服力的反对提议的优化的论点,因为它可能导致未定义行为,而我(原来)的答案只涉及性能方面。

经过一些思考之后,我可以再举一个例子来说明为什么应该强烈禁止编译器——除非它们能够证明转换对于特定上下文是安全的——来引入无条件写。考虑下面的代码:

const bool foo = true;


int main()
{
func(const_cast<bool&>(foo));
}

使用 func()中的无条件写操作肯定会触发未定义行为(写只读内存会终止程序,即使写操作的效果不是这样也是无效的)。

除了里昂对表演的回答:

假设 flagtrue。假设两个线程不断地调用 func(flag)。在这种情况下,所编写的函数不存储任何内容到 flag,因此这应该是线程安全的。两个线程访问相同的内存,但只是读取它。无条件地将 flag设置为 true意味着两个不同的线程将写入同一内存。这是不安全的,即使写入的数据与已经存在的数据相同,这也是不安全的。

我不确定 C + + 在这里的行为,但是在 C 中,内存可能会改变,因为如果内存中包含除1之外的非零值,那么在检查时它将保持不变,但是在检查时它将变为1。

但是由于我不太熟悉 C + + ,我不知道这种情况是否可能发生。