c - 为什么我的 "=r"(var) 输出没有选择与 "a"(var) 输入相同的寄存器?

标签 c gcc assembly x86 inline-assembly

我正在学习如何使用 __asm__ volatile在 GCC 中遇到了一个问题。我想实现一个执行原子比较和交换并返回先前存储在目标中的值的函数。

为什么 "=a"(expected) 输出约束起作用,而 "=r"(expected) 约束让编译器生成的代码不起作用工作?

案例 1.

#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>

uint64_t atomic_cas(uint64_t * destination, uint64_t expected, uint64_t value){
    __asm__ volatile (
        "lock cmpxchgq %3, %1":
        "=a" (expected) :
        "m" (*destination), "a" (expected), "r" (value) :
        "memory"
    );

    return expected;
}

int main(void){
    uint64_t v1 = 10;
    uint64_t result = atomic_cas(&v1, 10, 5);
    printf("%" PRIu64 "\n", result);           //prints 10, the value before, OK
    printf("%" PRIu64 "\n", v1);               //prints 5, the new value, OK
}

它按预期工作。现在考虑以下情况:

案例 2.

#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>

uint64_t atomic_cas(uint64_t * destination, uint64_t expected, uint64_t value){
    __asm__ volatile (
        "lock cmpxchgq %3, %1":
        "=r" (expected) ://<----- I changed a with r and expected GCC understood it from the inputs 
        "m" (*destination), "a" (expected), "r" (value) :
        "memory"
    );

    return expected;
}

int main(void){
    uint64_t v1 = 10;
    uint64_t result = atomic_cas(&v1, 10, 5);
    printf("%" PRIu64 "\n", result);            //prints 5, wrong
    printf("%" PRIu64 "\n", v1);                //prints 5, the new value, OK 
}

我检查了生成的程序集并注意到以下几点:

我。在这两种情况下,功能代码都是相同的,看起来像

   0x0000555555554760 <+0>:     mov    rax,rsi
   0x0000555555554763 <+3>:     lock cmpxchg QWORD PTR [rdi],rdx
   0x0000555555554768 <+8>:     ret 

二。当 GCC 内联 atomic_cas 时,问题就出现了,所以在后面的情况下,正确的值没有传递给 printf 函数。这是disas main的相关片段:

0x00000000000005f6 <+38>:    lock cmpxchg QWORD PTR [rsp],rdx
0x00000000000005fc <+44>:    lea    rsi,[rip+0x1f1]        # 0x7f4
0x0000000000000603 <+51>:    mov    rdx,rax ;  <-----This instruction is absent in the Case 2.
0x0000000000000606 <+54>:    mov    edi,0x1
0x000000000000060b <+59>:    xor    eax,eax

问题: 为什么将 rax(a) 替换为任意寄存器 (r ) 产生错误的结果?我希望它在这两种情况下都有效?

更新。我使用以下标志编译 -Wl,-z,lazy -Warray-bounds -Wextra -Wall -g3 -O3

最佳答案

cmpxchg 指令总是将结果放入rax 寄存器。所以你需要使用 a 约束来告诉 GCC 从那个寄存器中移动。在情况 2 中,您通过使用 r 告诉 GCC 使用任意寄存器,但您没有在该寄存器中放置任何内容。

如果你想使用r,你必须添加一个mov指令来将结果从rax移动到那个寄存器( movq %%rax, %0).您还必须告诉 GCC rax 寄存器已被指令更改,例如将其添加到 asm 语句的“clobbers”部分。对于您的情况,没有理由以这种方式使事情复杂化。

关于c - 为什么我的 "=r"(var) 输出没有选择与 "a"(var) 输入相同的寄存器?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58238986/

相关文章:

c - 使用指针访问结构成员时产生垃圾数据

c - 使用 native 编译器和交叉编译器生成相同的目标文件

c++ - 'memcpy' 未在此范围内声明

assembly - 从 CD 加载扇区

c++ - 使用指针翻转数组

c - 在二维数组/矩阵中查找由 1 组成的正方形

c - 如何计算覆盖矩形的最小数量的固定半径圆的圆心坐标?

c++ - 有-O2 和没有-O2 的成员变量的不同行为

linux - 对于微型程序,链接后最小的可执行文件大小现在比 2 年前大 10 倍?

c# - 为什么这两个简单语句的反汇编略有不同?