为必须可平凡复制的易失性结构分配一个值

Assign a value to a volatile struct that must be trivially-copyable

我有一个 struct 需要声明一些实例 volatile 因为它们表示与驱动程序共享的内存(即内存可能会被我的 C++ 程序之外的进程更改) . struct 也需要是平凡可复制的,因为我将与一些代码共享它的实例,这些代码要求其所有输入都是平凡可复制的。这两个要求似乎意味着我不可能安全地将新值分配给 struct.

volatile 个实例

这是我正在尝试做的一个简化示例:

struct foo {
    uint16_t a;
    uint16_t b;
};

int main() {
    static_assert(std::is_trivially_copyable<foo>::value, "Oh no!");
    volatile foo vfoo;
    foo foo_value{10, 20};
    vfoo = foo_value;
}

如果我尝试用 g++ 编译它,根据 this question,它在 vfoo = foo_value 和 "Error: passing 'volatile foo' as 'this' argument discards qualifiers." 行失败,那是因为隐式定义的赋值运算符没有声明为 volatile ,并且我需要定义一个易失性复制赋值运算符以便分配给易失性对象。但是,如果我这样做:

struct foo {
    uint16_t a;
    uint16_t b;
    volatile foo& operator=(const foo& f) volatile {
        if(this != &f) {
            a = f.a;
            b = f.b;
        }
        return *this;
    }
}

然后静态断言失败,因为如果 foo 具有用户定义的赋值运算符,则它不再是平凡可复制的。

由于编译器显然已经决定不允许我执行这个非常简单的操作,我目前正在使用这个解决方法:

int main() {
    static_assert(std::is_trivially_copyable<foo>::value, "Oh no!");
    volatile foo vfoo;
    foo foo_value{10, 20};
    memcpy(const_cast<foo*>(&vfoo), &foo_value, sizeof(foo));
    std::atomic_signal_fence(std::memory_order_acq_rel);
}

显然,放弃 volatile 不是一个好主意,因为这意味着编译器现在可以违反 volatile 应该强制执行的语义(即每次读取和写入代码被转换为实际读取或写入内存)。我试图通过用 memcpy 替换赋值来缓解这种情况,这应该意味着编译器无法优化写入(即使它认为写入对程序的其余部分不可见),并且通过在赋值后添加内存栅栏,这应该意味着编译器不能选择将写入延迟到很晚。

这是我能做的最好的了吗?是否有更好的解决方法可以更接近 volatile 的正确语义?或者有没有办法让编译器让我为 volatile 结构分配一个新值而不使该结构不可平凡复制?

如果您不一定需要使用赋值运算符(即,如果您认为 memcpy 替代方案可行),那么您可以改为编写非运算符赋值函数:

volatile foo& volatile_assign(volatile foo& f, const foo& o) {
    if(&f != &o) {
        f.a = o.a;
        f.b = o.b;
    }
    return f;
}

如果您愿意,可以使用成员函数。

我是根据您的示例编写的,但请考虑自赋值检查是否对可变语义有效。不应该重写相同的值吗?我不认为这种情况是有效的,除非对象实际上是非易失性的,否则我们将通过非易失性引用读取易失性对象(也许您还需要对其他操作数进行易失性限定)。