寻找一种方法,在编译时,如果 c++ 中的多个行设置了特定变量,则标记/抛出错误。即类似
~特别~ int a;
int main(){
while(1){
fn1();
fn2();
print(a);
}
}
void fn1(){
a++;
}
void fn2(){
print("hi");
}
应该可以正常编译,但是将 fn2() 更改为
void fn2(){
a = 2;
}
应该抛出一个错误,因为“a”被 fn1 和 fn2 都修改了。 注意“a”必须在运行时经常修改(因此将其设置为 const 或创建一个在运行时设置一次后锁定的函数不起作用),它不应该被多个函数修改。
抱歉,如果这是一个重复的问题,我确实进行了搜索,但一切都是为了仅在一行上设置变量一次(即 const)。
(这个问题的其余部分只是上下文,如果您已经得到答案,请跳过)
对“你不应该这样做!”的批评“全局变量坏了!”或者有些东西没有帮助 - 这是一个非常的特定情况(多核裸机实时嵌入式软件,其执行路径必须根据来自多个不同核心/线程的任何输入完全改变) - 这有点恶心,但是像这样的事情是最干净的方法
请注意,可以使用以下方法解决此问题:
void fn1(){
modifyA(1);
}
void fn2(){
modifyA(2);
}
void modifyA(int in){
a = in;
}
不起作用(尽管如果这个问题的答案不能理解这个也没关系)
但是类似
#define setA(in) *some preprocessor magic*(a=in)*more magic*
(所以也许类似
#ifdef A_IS_SET #error "a set twice" #else #define A_IS_SET ; a=*whatever* #endif
,?但这不太管用)
程序中两次“setA()”会导致错误。
原因是有一个巨大的全局状态向量(约 100 个变量),每个变量只能由一个核心/线程设置,但需要被所有人读取。 每个核心/线程都有全局状态的本地“副本”,并且全局状态有一个互斥体。每个核心都会获取互斥锁,修改全局状态中的一些变量,将其中的其他所有内容复制到其本地副本(因此它与全局状态同步),然后释放互斥锁。这可能在任何时间以任何顺序发生(因此核心 0 可能会连续执行五次,然后是核心 2,然后再次是 0,然后是 1,或其他)。
简化示例:
int a;
int b;
int c;
float d;
boolean e;
mutex_t globalMutex;
int core0_a; //"coren_x" is local copy of global var x, for core/thread n (yes this part is handled better in the real thing, simplified here)
...
int core0_e;
int core1_a;
... (and so on for all other cores/vars)
fnRunByCore0(){
getMutex(globalMutex);
a = core0_a; //so a is now "owned" by core0
c = core0_c; //so c is now "owned" by core0 too
core0_b = b;
core0_d = d;
core0_e = e;
releaseMutex(globalMutex);
}
fnRunByCore1(){
getMutex(globalMutex);
b = core1_b; //so b is now "owned" by core1
d = core1_d; //so d is now "owned" by core1
core1_a = a;
core1_c = c;
core1_e = e;
releaseMutex(globalMutex);
}
fnRunByCore2()
...(同样的交易,但仅设置 e)
这个问题的原因是为了确保我跟踪每个核心/线程/函数编写了哪些全局变量。理论上,我应该只需要阅读每个函数并确保它们每个只设置一次,在实践中,需要大量的肉眼检查,这只是为了双重检查。 硬件没有足够的性能开销来保存诸如索引之类的东西,该索引在运行时检查核心拥有每个变量的内容,或单独的互斥体,或类似的东西 - 每个循环运行约 100 种类型,任何运行时代码都会使表现不可接受。此外,这表明程序中存在错误,因此无论如何都应该在编译时捕获。
你是对的,你原来的
modifyA
对你没有任何作用,但你可以编写一个检测并发修改的。
据我所知,目标是只有一个线程可以写入变量,这可以使用原子来完成。
// initially, a is unclaimed
std::atomic<std::thread::id> a_owner = {};
int a = 0;
void modifyA(int new_a) {
auto me = std::this_thread::get_id();
if (a_owner.load(std::memory_order::relaxed) == me) {
a = new_a; // we own a, we can modify it, everything OK
}
// atomically do the following:
// if (a_owner == std::thread::id{}) a_owner = me;
std::thread::id expected = {};
if (a_owner.compare_exchange_strong(expected, me)) {
a = new_a; // we have become the new owner
}
else {
throw ":("; // someone else already owns a, or we have failed to claim ownership
// note: the id of the thread who stole a from us is now stored in expected
}
}
您还可以将其包装在更好的界面中,例如
struct UniquelyModified {
private:
std::atomic<std::thread::id> ownership = {};
int value = {};
public:
UniquelyModified() = default;
UniquelyModified& operator=(int x) {
value = x;
return *this;
}
// TODO: delete other assignment operators, add a getter, etc.
};