如何使易失性结构体在赋值过程中表现得完全像易失性int

How can I make a volatile struct behave exactly like a volatile int during assignment?

本文关键字:int 易失性 过程中 何使易 结构体 赋值      更新时间:2023-10-16

当一个非易失性的int被赋值给volatile int时,编译器当从同一类型的非易失性struct分配volatile struct时,编译器似乎非常不高兴。

考虑下面的简单程序:

struct Bar {
    int a;
};
volatile int foo;
int foo2;
volatile Bar bar;
Bar bar2;
int main(){
    foo = foo2;
    bar = bar2;
}

当我尝试编译这段代码时,我在main的第二行得到错误,而不是第一行。

g++     Main.cc   -o Main
Main.cc: In function ‘int main()’:
Main.cc:13:9: error: passing ‘volatile Bar’ as ‘this’ argument discards qualifiers [-fpermissive]
     bar = bar2;
         ^
Main.cc:1:8: note:   in call to ‘Bar& Bar::operator=(const Bar&)’
 struct Bar {

似乎出现问题是因为volatile Bar被传递到赋值操作符的左侧,尽管我不确定为什么这对int来说不是问题。

我看了这个答案,它建议以下修复。

struct Bar {
    int a;
    volatile Bar& operator= (const Bar& other) volatile {
       *this = other; 
    }
};

不幸的是,这导致了以下两个警告:

g++     Main.cc   -o Main
Main.cc: In member function ‘volatile Bar& Bar::operator=(const Bar&) volatile’:
Main.cc:4:21: warning: implicit dereference will not access object of type ‘volatile Bar’ in statement
        *this = other; 
                     ^
Main.cc: In function ‘int main()’:
Main.cc:16:15: warning: implicit dereference will not access object of type ‘volatile Bar’ in statement
     bar = bar2;

然后我看了看这个答案,它提到我应该将引用强制转换为右值,但是我不确定在这种情况下应该转换哪个引用,以及使用哪种强制转换语法。

什么是正确的咒语,使main的第2行上的赋值行为完全像main的第1行一样,没有警告或错误?

您最初的问题是因为隐式赋值操作符具有签名

Bar& operator=(const Bar& rhs);

…这对于volatile对象来说是不可调用的。警告是因为您更新的函数返回一个volatile引用,但该引用从未使用过。GCC认为这可能是个问题。解决这个问题的最简单方法是将返回类型更改为void!

还有另一个问题:你的函数将在无限递归中调用自己。我的建议如下:

struct Bar {
    int a;
    Bar& operator=(const Bar&rhs) = default;
    void operator=(const volatile Bar& rhs) volatile // Note void return.
    {
         // Caution: This const_cast removes the volatile from
         // the reference.  This may lose the point of the original
         // volatile qualification.
         //
         // If this is a problem, use "a = rhs.a;" instead - but this
         // obviously doesn't generalize so well.
         const_cast<Bar&>(*this) = const_cast<const Bar&>(rhs);
    }
};
volatile Bar vbar;
Bar bar;
int main(){
    vbar = bar;  // All four combinations work.
    bar = vbar;
    vbar = vbar;
    bar = bar;
    return 0;
}

这意味着在使用易失性结构体时不能将赋值操作符链接起来。我敢说这不是什么大损失。

最后的旁白:为什么你使用volatile -它不是很有用的多线程代码(它是有用的内存映射IO)

@martin很好地描述了问题发生的原因,但没有提供不丢弃volatile的通用解决方案。

如果抛弃volatile是可以的,那么它可能应该在赋值之前在函数外部进行。将其隐藏在复制构造函数中会增加不可预见的bug潜入的可能性。

稍微调整一下解决方案,得到以下更一般的解决方案:

struct Bar {
    int a;
    Bar& operator=(const Bar&rhs) = default;
    auto & operator=(const volatile Bar& rhs) volatile
    {
         // Solution 1: depends on struct definition and can't be copied to many
         static_assert(sizeof(*this) == sizeof(a), "struct elements added or changed. update below");
         a = rhs.a;
         // Solution 2: very general solution that could be used if copying to many structs
         // Do default byte-by-byte copy operation, but can't use memcpy for volatile
         static_assert(is_trivially_copy_assignable_v<remove_cvref_t<decltype(*this)>>>, "Byte-by-byte copy may not be possible with this class");
         std::copy_n(reinterpret_cast<const volatile std::byte*>(&rhs), sizeof(*this), reinterpret_cast<volatile std::byte*>(this));
         // Support chaining of assignments
         //return *this;  // Okay, but returning a volatile will generate spurious warnings if that value is not read afterword
         return rhs; // assumes that you want to just chain assignments and not do anything weird
    }
};

这个版本的复制构造函数适用于所有4种易失性/非易失性源和目标的组合,但是,如果您希望对每种情况进行优化,您也可以将其分成不同的组合。