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

Rus*_*lan 116 c++ optimization

考虑以下功能:

void func(bool& flag)
{
    if(!flag) flag=true;
}
Run Code Online (Sandbox Code Playgroud)

在我看来,如果flag有一个有效的布尔值,这相当于无条件设置它true,如下所示:

void func(bool& flag)
{
    flag=true;
}
Run Code Online (Sandbox Code Playgroud)

然而,gcc和clang都没有这样优化它 - 都在-O3优化级别生成以下内容:

_Z4funcRb:
.LFB0:
    .cfi_startproc
    cmp BYTE PTR [rdi], 0
    jne .L1
    mov BYTE PTR [rdi], 1
.L1:
    rep ret
Run Code Online (Sandbox Code Playgroud)

我的问题是:只是代码太特殊flag而不关心优化,或者是否有任何好的理由为什么这样的优化是不希望的,因为它不是一个参考volatile?看来这可能是唯一的原因是,flag可以在某种程度上具有非true-或- false在阅读它的点不未定义行为的价值,但我不知道这是否是可能的.

Leo*_*eon 102

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


编辑

hvd提供了阻止这种优化的另一个好理由.对于提议的优化,这是一个更有说服力的论据,因为它可能导致未定义的行为,而我的(原始)答案仅解决了性能方面.

经过多一点反思之后,我可以提出另一个例子,为什么编译器应该被强烈禁止 - 除非他们能够证明转换对于特定的上下文是安全的 - 从引入无条件写入.考虑以下代码:

const bool foo = true;

int main()
{
    func(const_cast<bool&>(foo));
}
Run Code Online (Sandbox Code Playgroud)

使用无条件写入func()肯定会触发未定义的行为(写入只读存储器将终止程序,即使写入的效果否则将是无操作).

  • @Yakk这取决于"只读内存"的含义.不,它不在ROM芯片中,但它经常在加载到没有启用写访问权的页面的部分中,并且当您尝试写入时,您将获得例如SIGSEGV信号或STATUS_ACCESS_VIOLATION异常. (16认同)
  • 它可能也会影响性能,因为你摆脱了分支.因此,如果没有一个非常具体的系统,我认为这个特殊情况没有意义. (7认同)
  • 它是`const`的转换,传递给一个函数_,它可以修改作为未定义行为来源的data_,而不是无条件写入.医生,我这样做很痛.... (7认同)
  • "这肯定会触发未定义的行为".不可以.未定义的行为是抽象机器的属性.这是代码所说的,它决定了UB是否存在.编译器不能导致它(尽管如果编译错误导致程序行为不正确). (5认同)
  • @Yakk行为定义不受目标平台的影响.说它将终止程序是不正确的,但UB本身可能会产生深远的影响,包括鼻子恶魔. (3认同)
  • @JanDvorak允许各个平台定义标准未定义的行为.Yakk的印象是写入const对象在"现代PC OS /系统"中定义明确 (2认同)
  • @Spencer 即使如此,编译器优化也不应该破坏不调用 UB 的代码。只要“foo”为 true,此代码就符合标准。 (2认同)
  • @EricMSchmidt“这肯定会触发未定义的行为” - 是的,因为标准说它是未定义的。该句子的其余部分是不正确的,因为它试图定义在未定义的构造之后您应该期望的行为。写入只读存储器不需要终止程序。 (2认同)

小智 48

除了莱昂对绩效的回答:

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

  • 这只是不安全*如果编译器所针对的系统使其不安全*.我从来没有开发过一种系统,它将"0x01"写入一个已经是"0x01"的字节会导致"不安全"的行为.在具有word或dword内存访问权限的系统上,它会; 但优化器应该意识到这一点.在现代PC或手机操作系统上,不会出现问题.所以这不是一个正当理由. (12认同)
  • 很有意思.所以我把它读作:编译器*永远不允许"优化"一个抽象机器没有的编写操作. (10认同)
  • 我认为*这是应用[`[intro.races]/21`]的结果(http://eel.is/c++draft/intro.races#21). (9认同)
  • @Yakk实际上,想到更多,我认为这毕竟是正确的,即使是普通的处理器.我认为当CPU可以直接写入内存时你是对的,但是假设`flag`在写时复制页面上.现在,在CPU级别,可能会定义行为(页面错误,让操作系统处理它),但在操作系统级别,它可能仍未定义,对吧? (4认同)
  • @MartinBa大部分都是这样.但是,如果编译器可以证明它无关紧要,例如因为它可以证明没有其他线程可以访问该特定变量,那么它可能没问题. (3认同)

glg*_*lgl 13

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

但由于我不熟悉C++,我不知道这种情况是否可行.

  • 在C中,如果内存包含ABI未说明的值对其类型有效,则它是陷阱表示,并且读取陷阱表示是未定义的行为.在C++中,这只能在读取未初始化的对象时发生,并且它正在读取未初始化的UB对象.但是如果你能找到一个ABI,说任何非零值对于类型`bool` /`_Bool`都有效并且意味着'true`,那么在那个特定的ABI中,你可能是对的. (5认同)