给出一个单核CPU嵌入式环境,该环境中保证读写变量是原子的,下面是示例:
struct Example
{
bool TheFlag;
void SetTheFlag(bool f) {
Theflag = f;
}
void UseTheFlag() {
if (TheFlag) {
// Do some stuff that has no effect on TheFlag
}
// Do some more stuff that has no effect on TheFlag
if (TheFlag) {
...
}
}
};
很明显,如果在UseTheFlag的两次使用TheFlag之间偶然地在另一个线程(或中断)上调用了SetTheFlag,则可能会发生意外行为(或在这种情况下有人会认为这是预期的行为!)。 / p>
可以使用以下变通办法来保证行为吗?
void UseTheFlag() {
auto f = TheFlag;
if (f) {
// Do some stuff that has no effect on TheFlag
}
// Do some more stuff that has no effect on TheFlag
if (f) {
...
}
}
我的实际测试表明,变量f从未被优化过,并且无法从TheFlag(GCC 10,ARM Cortex M4)中复制一次。但是,我想确定编译器是否保证f不会被优化?
我知道有更好的设计实践,关键部分,禁用中断等,但这是关于此用例中编译器优化的行为。
答案 0 :(得分:1)
您可以从“假设”规则的角度考虑这一点,该规则宽松地指出,编译器应用的任何优化都不得改变代码的原始含义。
因此,除非,编译器可以证明TheFlag
在f
的生存期内没有变化,因此必须进行本地复制。
也就是说,我不确定'证明'是否扩展到另一个线程或ISR对TheFlag
所做的修改。将TheFlag
标记为atomic
(对于ISR,则标记为volatile
)。
答案 1 :(得分:1)
在这种情况下,C ++标准没有说什么。只是UB,因为可以在一个线程中修改对象,而另一个线程正在访问它。
您只能说平台指定这些操作是原子的。显然,这还不足以确保此代码正确运行。原子性仅保证两次并发写入将值保留为两个写入值之一,并且一次或多次写入期间的读取将永远不会看到未写入的值。它没有说明在这种情况下会发生什么。
任何优化都不会破坏该代码。特别是,原子性不会阻止其他线程中的读取操作看到在该读取操作之前写入的值,除非使用了已知的同步方法。
如果编译器发现寄存器压力,则没有什么阻止它简单地读取两次TheFlag
而不是创建本地副本。如果编译可以推断出该线程中的中间代码无法修改TheFlag
,则该优化是合法的。除非您遵循规则并使用定义的事物进行同步,或者仅需要明确的保证原子性答复,否则优化器不必考虑其他线程可能会做什么。
您超越了这一点,因此所有赌注都关闭了。对于TheFlag
,您需要的不仅仅是原子性,所以不要使用仅是原子的类型-这还不够。