gpt4 book ai didi

c++ - 为什么 volatile 局部变量的优化与 volatile 参数不同,为什么优化器会从后者生成无操作循环?

转载 作者:可可西里 更新时间:2023-11-01 17:58:17 25 4
gpt4 key购买 nike

背景

这是受到这个问题/答案以及随后在评论中的讨论的启发:Is the definition of “volatile” this volatile, or is GCC having some standard compliancy problems? .根据其他人和我对应该发生的事情的解释,如评论中所述,我已将其提交给 GCC Bugzilla:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71793仍然欢迎其他相关回复。

此外,该线程已经引起了这个问题:Does accessing a declared non-volatile object through a volatile reference/pointer confer volatile rules upon said accesses?

介绍

我知道 volatile不是大多数人认为的那样,而是一个实现定义的毒蛇巢。而且我当然不想在任何实际代码中使用以下结构。也就是说,我对这些例子中发生的事情完全感到困惑,所以我真的很感激任何解释。

我的猜测是这是由于对标准的高度细微的解释或(更有可能?)只是所用优化器的极端情况。无论哪种方式,虽然更学术而不是实际,但我希望这被认为是有值(value)的分析,特别是考虑到通常被误解的情况 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 stable 又名 Jessie) 与命令行 g++ -std=c++14 -O3 -S test.cppmain() 生成以下 ASM .版本 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 没有太多意义。本地值(value)观。但是 C++ 允许我们声明它们并尝试用它们做一些事情。就这样,我们一如既往地迷茫,蹒跚前行。
  • g() : 什么。通过移动 volatile source 转换为传值参数,它仍然只是另一个局部变量,GCC 以某种方式决定它不是或更少 volatile ,所以它不需要每次迭代都读取它......但它仍然执行循环,尽管它的主体现在什么都不做。
  • h() : 取已通过 volatile作为传递引用,与 f() 的有效行为相同已恢复,因此循环执行 volatile读。
  • 由于上述针对 f() 的原因,仅此案例对我来说就具有实际意义。 .详细说明:想象一下x指的是硬件寄存器,每次读取都会产生副作用。你不会想跳过其中任何一个。

  • 添加 #define volatile /**/导致 main()正如你所期望的那样,是一个空运。所以,当存在时,即使是局部变量 volatile确实做了一些事情......我只是不知道在 g() 的情况下是什么情况| .那里到底发生了什么?

    问题
  • 为什么在体内声明的局部值与按值参数产生不同的结果,而前者允许优化读取?两者均已声明 volatile .也没有地址传递出去 - 也没有 static地址,排除任何内联 ASM POKE ry - 所以它们永远不能在函数之外被修改。编译器可以看到每个都是常量,永远不需要重新读取,并且volatile只是不是真的 -
  • 所以(A)在这样的约束下是否可以被省略? (表现得好像他们没有被声明 volatile )-
  • (B) 为什么只有一个被省略?有的volatile局部变量更多 volatile相对于其它的?
  • 暂时搁置这种不一致:在读取被优化掉之后,为什么编译器仍然生成循环?它什么都不做!为什么优化器不忽略它,就像没有编码循环一样?

  • 由于优化分析的顺序等原因,这是一个奇怪的极端情况吗?由于代码是一个愚蠢的思想实验,我不会为此谴责 GCC,但肯定会很好。 (或者 g() 是人们多年来梦寐以求的手动计时循环?)如果我们得出结论,其中任何一项都没有标准影响,我会将其移至他们的 Bugzilla,仅供引用。

    当然,从实际角度来看,更重要的问题是,尽管我不希望这掩盖了编译器极客的潜力......如果有的话,根据标准,哪些是明确定义/正确的?

    最佳答案

    对于 f:GCC 消除了非 volatile 存储(但不是加载,如果源位置是内存映射硬件寄存器,则可能会产生副作用)。这里真的没有什么令人惊讶的。

    对于 g:因为 x86_64 ABI参数 xg在寄存器中分配(即 rdx )并且在内存中没有位置。读取通用寄存器没有任何可观察到的副作用,因此消除了死读。

    关于c++ - 为什么 volatile 局部变量的优化与 volatile 参数不同,为什么优化器会从后者生成无操作循环?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38235112/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com