繁体   English   中英

在 GNU C 内联汇编中与 push/assignment/pop 交换?

[英]Swap with push / assignment / pop in GNU C inline assembly?

我在这里阅读了一些答案和问题,并不断提出这个建议,但我注意到没有人真正“准确地”解释过你需要做什么来做到这一点,在使用英特尔和 GCC 编译器的 Windows 上。 下面评论正是我想要做的。

#include <stdio.h>

int main()
{
    int x = 1;
    int y = 2;
    //assembly code begin
    /*
      push x into stack; < Need Help
      x=y;               < With This
      pop stack into y;  < Please
    */
    //assembly code end
    printf("x=%d,y=%d",x,y);
    getchar();
    return 0;
}

让编译器选择寄存器,使用k前缀表示int类型的32位寄存器(因此它在x86-64上按预期工作):

__asm__ ("pushl %k0\n\t"
         "movl %k1, %k0\n\t"
         "popl %k1"
         : "+r" (x), "+r" (y));

在这种情况下,指定任何被破坏的操作数都没有必要(实际上是错误的)。


显而易见, xy在这里是可交换的,即交换操作数应该仍然产生相同的结果使用: "+%r" (x), "+r" (y)其中%表示该操作数和下一个操作数操作数可以通勤。

你不能只是从内联 asm 安全地推送/弹出,如果它可以移植到带有红色区域的系统。 这包括所有非 Windows x86-64 平台。 没有办法告诉 gcc 你想破坏它)。 好吧,您可以先add rsp, -128以在推送/弹出任何内容之前跳过红色区域,然后再将其恢复。 但是你不能使用"m"约束,因为编译器可能会使用带有偏移量的 RSP 相对寻址,假设 RSP 没有被修改。

但实际上,在 inline asm 中这样做是一件荒谬的事情。

以下是使用 inline-asm 交换两个 C 变量的方法:

#include <stdio.h>

int main()
{
    int x = 1;
    int y = 2;

    asm(""                  // no actual instructions.
        : "=r"(y), "=r"(x)   // request both outputs in the compiler's choice of register
        :  "0"(x),  "1"(y)   // matching constraints: request each input in the same register as the other output
        );
    // apparently "=m" doesn't compile: you can't use a matching constraint on a memory operand

    printf("x=%d,y=%d\n",x,y);
    // getchar();  // Set up your terminal not to close after the program exits if you want similar behaviour: don't embed it into your programs
    return 0;
}

来自Godbolt 编译器资源管理器的gcc -O3 输出(针对 x86-64 System V ABI,而不是 Windows):

.section .rodata
.LC0:
    .string "x=%d,y=%d"
.section .text
main:
    sub     rsp, 8
    mov     edi, OFFSET FLAT:.LC0
    xor     eax, eax
    mov     edx, 1
    mov     esi, 2
#APP
# 8 "/tmp/gcc-explorer-compiler116814-16347-5i3lz1/example.cpp" 1
            # I used "\n" instead of just "" so we could see exactly where our inline-asm code ended up.

# 0 "" 2
#NO_APP
    call    printf
    xor     eax, eax
    add     rsp, 8
    ret

C 变量是一个高级概念; 决定相同的寄存器现在在逻辑上保存不同的命名变量不需要任何成本,而不是在不更改 varname->register 映射的情况下交换寄存器内容。

手写 asm 时,使用注释来跟踪不同寄存器或向量寄存器的一部分的当前逻辑含义。


inline-asm 也不会导致 inline-asm 块之外的任何额外指令,因此在这种情况下它非常有效。 尽管如此,编译器还是看不透它,也不知道这些值仍然是 1 和 2,因此进一步的常量传播将失败。 https://gcc.gnu.org/wiki/DontUseInlineAsm

您可以使用扩展内联汇编 它是一个编译器功能,允许您在 C 代码中编写汇编指令。 内联 gcc 汇编的一个很好的参考可以在这里找到

以下代码使用poppush指令将x的值复制到y
(在 x86_64 上使用 gcc 编译和测试)

这只有在使用-mno-red-zone编译时才安全,或者在推送任何内容之前从 RSP 中减去 128。 它会在某些功能中正常工作:使用一组周围代码进行测试不足以验证您使用 GNU C 内联汇编所做的某些事情的正确性。

  #include <stdio.h>

    int main()
    {
        int x = 1;
        int y = 2;

   asm volatile ( 
        "pushq  %%rax\n"          /* Push x into the stack */ 
        "movq   %%rbx, %%rax\n"   /* Copy y into x         */ 
        "popq   %%rbx\n"          /* Pop  x into y         */
      : "=b"(y), "=a"(x)          /* OUTPUT values         */ 
      : "a"(x),  "b"(y)           /* INPUT  values         */
      :    /*No need for the clobber list, since the compiler knows
             which registers have been modified            */
    ); 


        printf("x=%d,y=%d",x,y);
        getchar();
        return 0;
    }

结果x=2 y=1 ,如您所料。

intel 编译器的工作方式类似,我认为您只需将关键字asm更改为__asm__ 您可以在此处找到有关 INTEL 编译器的内联汇编的信息。

#include <stdio.h>

int main()
{
    int x=1;
    int y=2;
    printf("x::%d,y::%d\n",x,y);
    __asm__( "movl %1, %%eax;"
             "movl %%eax, %0;"
             :"=r"(y)
             :"r"(x)
             :"%eax"
            );
    printf("x::%d,y::%d\n",x,y);
    return 0;
}

/* Load x to eax
Load eax to y */

如果要交换值,也可以使用这种方式来完成。 请注意,这会指示 GCC 处理损坏的 EAX 寄存器。 出于教育目的,这是可以的,但我发现将微优化留给编译器更合适。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM