#include <thread>
int i;
int main()
{
std::thread t1([&i]() {
asm volatile("movl $1, %0"
: "=m"(i));
});
std::thread t2([&i]() {
asm volatile("movl $2, %0"
: "=m"(i));
});
t2.join();
t1.join();
return 0;
}
它是C ++中的数据竞争(以及结果UB)吗?让我们假设i
的地址是对齐的,并且我们的CPU加载/存储对齐的双字是原子的。
可能它满足数据竞争的定义。
答案 0 :(得分:1)
由于显而易见的原因,标准保留了内联asm实现定义。要说清楚这一点,我们必须制造东西并亲自动手。
更重要的是,我们不再考虑纯ISO C ++,而是考虑C ++的GNU方言,它定义了ISO C ++留下Undefined的许多行为。例如the gcc manual says通过编写一个联合成员并读取另一个联合成员的类型 - 惩罚在GNU C ++中是明确定义的,即使它是ISO C ++中的UB。在GNU C ++中,很多东西仍然是UB,“无论g ++实际上做什么”都不算作定义。请参阅the manual's implementation-defined behaviour section in the table of contents。
C ++ - &gt; gcc的asm阶段甚至不理解inline-asm语句中的指令,它只是填充操作数并将其传递给汇编程序。它没有“思考”指令正在做什么;它将它视为由输出,输入和clobber约束描述的黑盒子。
由于您使用了"=m"(i)
输出操作数,因此您的asm语句 以C ++方式与C ++变量进行交互(而不是编译器后面的{{1} })。我认为编译器将其视为asm("mov $1, i");
之类的东西。加上i = __builtin_my_asm_statement();
关键字,阻止编译时重新排序/提升/死代码消除。
并非每个数据竞争都是C ++标准的Undefined-Behavior导致Data Race。例如,如果volatile
是std :: atomic类型,则i
的最终值仍然是不确定的,因为race condition。 (但是程序将没有C ++ UB,并且i
将是2或1,没有撕裂。未定义的行为在技术上意味着任何事情都可能发生。)
所以,我们可以对此代码说些什么:
i
是自然对齐的,因为所有通常的x86 ABI都保证了这一点。i
的共享值,而不是抓住堆栈上的空间,然后编译器会复制自。执行此操作的邪恶编译器会破坏代码的 lot ,包括使用内联asm以使用内存操作数对共享变量运行i
ed指令的Linux内核。因此,如果我们可以假设一个非恶意编译器,我们可以非常确定编译器输出将做什么。或者,对于共享值,lock
操作数的行为可能应该被视为GNU C中明确定义的行为,因此我们可以说这是明确定义的。