【发布时间】:2019-09-19 17:37:23
【问题描述】:
我有一个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++ 编译它,它会在 vfoo = foo_value 行失败,并显示“错误:将 'volatile foo' 作为 'this' 参数传递会丢弃限定符。”根据this question,这是因为隐式定义的赋值运算符未声明为易失性,我需要定义一个易失性复制赋值运算符才能分配给易失性对象。但是,如果我这样做:
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 结构分配一个新值,而不会使结构不可复制?
【问题讨论】:
-
如果并行进程/线程可以更改数据,那么您有同步问题。 Volatile 不会让你摆脱困境,你需要一些同步原语。这不是 XY 问题吗?
-
唯一可平凡复制的类型是标量类型、可平凡复制的类和此类类型/类的数组(可能是 const 限定的,但不是 volatile 限定的)。跨度>
-
我已经确定 volatile 是这个系统的正确选择。带有 volatile 标记的结构被传递给 RDMA 网络驱动程序,该驱动程序发出异步请求以在单向发送中发布该内存区域。由于请求保证在我调用发送函数之后发生,我不需要额外的同步,但我确实需要保证在我调用发送函数之前发生在代码中的写入实际上已经写入内存我调用这个函数。
-
你考虑过重载赋值运算符吗?
-
不是答案,而是咆哮——我注意到
volatile在 C++ 中被视为二等公民。这确实令人沮丧。