und*_*e_d 8 c++ optimization g++ volatile pass-by-value
这是受到这个问题/答案以及随后在评论中讨论的启发:"易变"这个定义是不稳定的,还是GCC有一些标准的合规性问题?.基于其他人以及我对应该发生的事情的解释,如评论中所讨论的,我已将其提交给GCC Bugzilla:https://gcc.gnu.org/bugzilla/show_bug.cgi ? id = 71793 其他相关回复仍然存在欢迎.
此外,该线程引发了这个问题:通过易失性引用/指针访问声明的非易失性对象是否会在所述访问时赋予易失性规则?
我知道volatile这不是大多数人认为的,而且是实施定义的毒蛇巢.我当然不想在任何实际代码中使用以下结构.也就是说,我对这些例子中发生的事情感到非常困惑,所以我真的很感激任何解释.
我的猜测是,这是由于对标准的高度细微差别的解释,或者(更可能的是)对于所使用的优化器的角落情况.无论哪种方式,虽然更具学术性而非实际性,但我希望这对于分析是有价值的,特别是考虑到通常误解的volatile情况.一些更多的数据点 - 或者更有可能是针对它的点 - 必须是好的.
鉴于此代码:
#include <cstddef>
void f(void *const p, std::size_t n)
{
    unsigned char *y = static_cast<unsigned char *>(p);
    volatile unsigned char const x = 42;
    // N.B. Yeah, const is weird, but it doesn't change anything
    while (n--) {
        *y++ = x;
    }
}
void g(void *const p, std::size_t n, volatile unsigned char const x)
{
    unsigned char *y = static_cast<unsigned char *>(p);
    while (n--) {
        *y++ = x;
    }
}
void h(void *const p, std::size_t n, volatile unsigned char const &x)
{
    unsigned char *y = static_cast<unsigned char *>(p);
    while (n--) {
        *y++ = x;
    }
}
int main(int, char **)
{
    int y[1000];
    f(&y, sizeof y);
    volatile unsigned char const x{99};
    g(&y, sizeof y, x);
    h(&y, sizeof y, x);
}
g++来自gcc (Debian 4.9.2-10) 4.9.2(Debian stableaka Jessie)的命令行g++ -std=c++14 -O3 -S test.cpp生成以下ASM main().版本Debian 5.4.0-6(当前unstable)产生等效代码,但我碰巧先运行旧代码,所以这里是:
main:
.LFB3:
    .cfi_startproc
# f()
    movb    $42, -1(%rsp)
    movl    $4000, %eax
    .p2align 4,,10
    .p2align 3
.L21:
    subq    $1, %rax
    movzbl  -1(%rsp), %edx
    jne .L21
# x = 99
    movb    $99, -2(%rsp)
    movzbl  -2(%rsp), %eax
# g()
    movl    $4000, %eax
    .p2align 4,,10
    .p2align 3
.L22:
    subq    $1, %rax
    jne .L22
# h()
    movl    $4000, %eax
    .p2align 4,,10
    .p2align 3
.L23:
    subq    $1, %rax
    movzbl  -2(%rsp), %edx
    jne .L23
# return 0;
    xorl    %eax, %eax
    ret
    .cfi_endproc
所有3个函数都是内联的,并且volatile由于相当明显的原因,它们都在堆栈中分配局部变量.但这是他们共享的唯一事情......
f()确保从x每次迭代读取,可能是由于它volatile- 但只是将结果转储到edx,可能是因为目标y未被声明volatile且永远不会被读取,这意味着对它的更改可以在as-if规则下固定.好的,有道理.
volatile它实际上是硬件寄存器,显然本地值不能是其中之一 - 否则不能以某种volatile方式修改,除非它的地址被传递出来......但事实并非如此.看,没有太多意义可以超越volatile当地的价值观.但是C++允许我们声明它们并试图用它们做些什么.所以,一如既往地困惑,我们绊倒了.g():什么 通过将volatile源移动到pass-by-value参数(仍然只是另一个局部变量),GCC以某种方式决定它不是或更少 volatile,因此它不需要每次迭代都读取它...但它仍然执行循环,尽管它的身体现在什么都不做.
h():通过将传递volatile作为pass-by-reference,f()恢复相同的有效行为,因此循环执行volatile读取.
f().详细说明:Imagine x是指硬件寄存器,每次读取都有副作用.你不想跳过任何这些.正如您所期望的那样,添加#define volatile /**/导致main()无操作.因此,当存在时,即使在局部变量volatile上也会做某事......我只是不知道在什么情况下g().地球上到底发生了什么?
volatile.没有地址传出 - 并且没有static地址,排除任何内联ASM POKEry  - 所以它们永远不能被修改掉.编译器可以看到每个都是常量,永远不需要重新读取,并且volatile不是真的 -
volatile) -volatile局部变量是否volatile比其他变量更多?由于优化分析的顺序等,这是一个奇怪的角落案例吗?由于代码是一个愚蠢的思想实验,我不会因此而惩罚海湾合作委员会,但肯定知道这一点很好.(或者是g()这些年来人们梦寐以求的手动定时循环?)如果我们得出结论,对此任何内容都没有标准,我将把它移到他们的Bugzilla只是为了他们的信息.
当然,从实际的角度来看,更重要的问题,虽然我不希望这会掩盖编译器极客的可能性......根据标准,哪些(如果有的话)是明确的/正确的?
对于 f:GCC 消除了非易失性存储(但没有消除加载,如果源位置是内存映射硬件寄存器,加载可能会产生副作用)。这里确实没有什么令人惊讶的。
对于 g:由于x86_64 ABI,参数x被g分配在寄存器中(即rdx)并且在内存中没有位置。读取通用寄存器不会产生任何可观察到的副作用,因此消除了死读。
| 归档时间: | 
 | 
| 查看次数: | 994 次 | 
| 最近记录: |