指向堆栈变量的指针是否易失

Should a pointer to stack variable be volatile?

本文关键字:指针 是否 易失 变量 堆栈      更新时间:2023-10-16

我知道我应该使用volatile关键字告诉编译器不要优化对变量的内存读\写。我也知道,在大多数情况下,它应该只用于与非C++记忆通信。

但是,我想知道在持有指向某个局部(堆栈)变量的指针时是否必须使用volatile

例如:

//global or member variable
/* volatile? */bool* p_stop;
void worker()
{
/* volatile? */ bool stop = false;
p_stop = &stop;
while(!stop)
{
//Do some work
//No usage of "stop" or p_stop" here
}
}
void stop_worker()
{
*p_stop = true;
}

在我看来,具有一定优化级别的编译器可能会看到stop是一个局部变量,永远不会更改,并且可以用while(true)替换while(!stop),从而在什么都不做的情况下更改*p_stop

那么,在这种情况下是否需要将指针标记为易失性?

PS:请不要教我为什么不使用它,使用这个黑客的真正代码这样做是出于(复杂解释)的原因。

编辑:

  1. 我没有提到这两个函数在不同的线程上运行。worker()是第一个线程的函数,应使用p_stop指针从另一个线程停止它。

  2. 我不想知道有什么更好的方法来解决这种黑客背后的真正原因。我只是想知道这是否是C++中定义\未定义的行为(就此而言为 11),以及这是否依赖于编译器\平台\等。到目前为止,我看到@Puppy说每个人都错了,这是错误的,但没有参考表示这一点的具体标准。

我知道你们中的一些人被"不要教训我"部分冒犯了,但请坚持真正的问题 - 我应该使用volatile还是UB? 如果你能通过提供完整的答案来帮助我(和其他人)学习新的东西。

我只是想知道这是否是C++中定义\未定义的行为(就此而言为 11)

Ta-da(来自N3337,"准C++11")

如果两个表达式计算修改内存位置

[..],而另一个表达式访问或修改相同的内存位置,则两个表达式计算将发生冲突。

§1.10/4

和:

如果程序在不同线程中包含两个冲突的操作,则程序的执行包含数据争用,其中至少一个不是原子的,并且两者都不会先于另一个发生。任何此类数据争用都会导致未定义的行为。[..]

§1.10/21

您正在从不同的线程访问对象stop(内存位置),两次访问都不是原子的,因此也没有"发生之前"的关系。简而言之,您有数据竞争,因此存在未定义的行为

我不想知道有什么更好的方法来解决这种黑客背后的真正原因。

原子操作(由C++标准定义)是(可靠地)解决此问题的唯一方法。

那么,在这种情况下是否需要将指针标记为易失性?

不。这不是必需的,主要是因为易失性甚至不能远程覆盖在这种情况下需要它做什么。必须使用实际的同步基元,如原子操作或互斥锁。在这里使用volatile是未定义的行为,您的程序将爆炸。

volatile对并发没有用。它可能对实现并发原语很有用,但远远不够。

坦率地说,是否要使用实际的同步原语无关紧要。如果你想写正确的代码,你别无选择

PS:请不要教我为什么不使用这个,

我不确定我们应该说什么。编译器管理堆栈,因此您对堆栈执行的任何操作在技术上都是未定义的行为,并且在升级到下一版本的编译器时可能无法正常工作。

您还正在做出的假设可能与编译器优化时的假设不同。这是使用(或不使用)volatile的真正原因;您可以向编译器提供指导,帮助它确定优化是否安全。使用volatile告诉编译器,它应该假定这些变量可能会由于外部影响(其他线程或特殊硬件行为)而更改。

所以是的,在这种情况下,看起来您需要用volatile限定符标记p_stopstop

(注意:这是必要的,但还不够,因为它不会导致在具有宽松内存模型的语言实现中发生适当的行为,该模型需要屏障来确保正确性。见 https://en.wikipedia.org/wiki/Memory_ordering#Runtime_memory_ordering)

这个问题根本无法从提供的细节中得到回答。

如问题中所述,这是一种完全不支持的线程之间通信方式。

所以唯一的答案是:

指定您正在使用的编译器版本,并希望有人知道其最黑暗的秘密或参考您的文档。所有C++标准都会告诉你这是行不通的,任何人都可以告诉你的是"可能有效但不起作用"。

没有"哦,来吧,伙计们,每个人都知道它几乎有效,我该怎么做作为解决方法?眨眨"的回答。

除非编译器不支持原子或适当的并发机制,否则没有合理的理由这样做。 "它不受支持"不是"解释复杂",所以我会根据该代码片段着迷于了解不正确执行此操作的可能原因(古代编译器除外)。