内联asm访问共享变量是否算作C ++ 11中的数据竞争?

时间:2016-09-07 21:40:10

标签: c++ c++11 x86 atomic lock-free

#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加载/存储对齐的双字是原子的。

可能它满足数据竞争的定义。

1 个答案:

答案 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都保证了这一点。
  • 我们知道asm将存储器作为单个指令包含在存储器中,而不是一次复制一个字节。 (无论如何,没有理智的编译器会这样做。)
  • 100%确定我们可以保证商店直接转到i的共享值,而不是抓住堆栈上的空间,然后编译器会复制自。执行此操作的邪恶编译器会破坏代码的 lot ,包括使用内联asm以使用内存操作数对共享变量运行i ed指令的Linux内核。

因此,如果我们可以假设一个非恶意编译器,我们可以非常确定编译器输出将做什么。或者,对于共享值,lock操作数的行为可能应该被视为GNU C中明确定义的行为,因此我们可以说这是明确定义的。