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);
}
Run Code Online (Sandbox Code Playgroud)
g++
来自gcc (Debian 4.9.2-10) 4.9.2
(Debian stable
aka 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
Run Code Online (Sandbox Code Playgroud)
所有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 POKE
ry - 所以它们永远不能被修改掉.编译器可以看到每个都是常量,永远不需要重新读取,并且volatile
不是真的 -
volatile
) -volatile
局部变量是否volatile
比其他变量更多?由于优化分析的顺序等,这是一个奇怪的角落案例吗?由于代码是一个愚蠢的思想实验,我不会因此而惩罚海湾合作委员会,但肯定知道这一点很好.(或者是g()
这些年来人们梦寐以求的手动定时循环?)如果我们得出结论,对此任何内容都没有标准,我将把它移到他们的Bugzilla只是为了他们的信息.
当然,从实际的角度来看,更重要的问题,虽然我不希望这会掩盖编译器极客的可能性......根据标准,哪些(如果有的话)是明确的/正确的?
对于 f:GCC 消除了非易失性存储(但没有消除加载,如果源位置是内存映射硬件寄存器,加载可能会产生副作用)。这里确实没有什么令人惊讶的。
对于 g:由于x86_64 ABI,参数x
被g
分配在寄存器中(即rdx
)并且在内存中没有位置。读取通用寄存器不会产生任何可观察到的副作用,因此消除了死读。
归档时间: |
|
查看次数: |
994 次 |
最近记录: |