为什么局部变量不能在 GNU C 基本内联 asm 语句中使用?

Her*_*man 5 c gcc language-design inline-assembly

main为什么我不能在基本 asm 内联中使用局部变量?它只允许在扩展汇编中使用,但为什么会这样呢?

(我知道局部变量在返回地址之后位于堆栈上(因此一旦函数返回就不能使用),但这不应成为不使用它们的原因)

以及基本汇编的示例:

int a = 10; //global a
int b = 20; //global b
int result;

int main() {
    asm ( "pusha\n\t"
          "movl a, %eax\n\t"
          "movl b, %ebx\n\t"
          "imull %ebx, %eax\n\t"
          "movl %eax, result\n\t"
          "popa");

    printf("the answer is %d\n", result);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

扩展的例子:

int main (void) {
    int data1 = 10;  //local var - could be used in extended
    int data2 = 20;
    int result;

    asm ( "imull %%edx, %%ecx\n\t"
          "movl %%ecx, %%eax" 
          : "=a"(result)
          : "d"(data1), "c"(data2));

    printf("The result is %d\n",result);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

编译为: gcc -m32 somefile.c

平台: uname -aLinux 5.0.0-32-generic #34-Ubuntu SMP Wed Oct 2 02:06:48 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

Eri*_*hil 6

您可以在扩展程序集中使用局部变量,但您需要将它们告知扩展程序集构造。考虑:

\n
#include <stdio.h>\n\n\nint main (void)\n{\n    int data1 = 10;\n    int data2 = 20;\n    int result;\n\n    __asm__(\n        "   movl    %[mydata1], %[myresult]\\n"\n        "   imull   %[mydata2], %[myresult]\\n"\n        : [myresult] "=&r" (result)\n        : [mydata1] "r" (data1), [mydata2] "r" (data2));\n\n    printf("The result is %d\\n",result);\n\n    return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

在此[myresult] "=&r" (result)表示选择一个寄存器 ( r) 用作=左值的输出 ( ) 值result,并且该寄存器将在汇编中被称为并且%[myresult]必须与输入寄存器 ( &) 不同。(您可以在两个地方使用相同的文本,result而不是myresult;我只是为了说明而将其不同。)

\n

类似地[mydata1] "r" (data1),将表达式的值放入data1寄存器中,并且在汇编中将其称为%[mydata1]

\n

我修改了程序集中的代码,使其仅修改输出寄存器。您的原始代码会进行修改%ecx,但不会告诉编译器它正在这样做。您可以告诉编译器,通过在"ecx"第三个 后面放置:,这是 \xe2\x80\x9cclobbered\xe2\x80\x9d 寄存器列表所在的位置。但是,由于我的代码让编译器分配一个寄存器,因此我不会在破坏的寄存器中列出特定的寄存器。可能有一种方法告诉编译器其中一个输入寄存器将被修改但输出不需要,但我不知道。(文档位于此处。)对于此任务,更好的解决方案是告诉编译器对输入之一使用与输出相同的寄存器:

\n
    __asm__(\n        "   imull   %[mydata1], %[myresult]\\n"\n        : [myresult] "=r" (result)\n        : [mydata1] "r" (data1), [mydata2] "0" (data2));\n
Run Code Online (Sandbox Code Playgroud)\n

在此,0withdata2表示使其与操作数 0 相同。操作数按照它们出现的顺序进行编号,从第一个输出操作数 0 开始,一直到输入操作数。因此,当汇编代码开始时,%[myresult]将引用data2已放置值的某个寄存器,并且编译器将期望result当汇编完成时新值位于该寄存器中。

\n

执行此操作时,您必须将约束与事物在装配中的使用方式相匹配。对于该r约束,编译器提供了一些可以在接受通用处理器寄存器的汇编语言中使用的文本。其他包括m内存引用和i立即操作数。

\n


R..*_*R.. 4

“基本汇编”和“扩展汇编”之间几乎没有区别;“basic asm”只是一种特殊情况,其中__asm__语句没有输出、输入或破坏列表。编译器不会%在汇编字符串中对 Basic asm 进行替换。如果你想要输入或输出,你必须指定它们,这就是人们所说的“扩展汇编”。

实际上,可以从“basic asm”访问外部(甚至文件范围静态)对象。这是因为这些对象将(分别可能)在程序集级别具有符号名称。但是,要执行此类访问,您需要注意它是否与位置无关(如果您的代码将链接到库或 PIE 可执行文件中)并满足链接时可能施加的其他 ABI 约束,并且有各种考虑因素与链接时优化和编译器可能执行的其他转换的兼容性。简而言之,这是一个坏主意,因为您无法告诉编译器基本的 asm 语句修改了内存。没有办法保证安全。

clobber "memory"(扩展 asm)可以使从 asm 模板按名称安全地访问静态存储变量。

基本 asm 的用例是仅修改机器状态,例如asm("cli")在内核中禁用中断,而不读取或写入任何 C 变量。(即使如此,您也经常使用“内存”破坏器来确保编译器在更改机器状态之前已完成早期的内存操作。)

本地(自动存储,而不是静态变量)变量从根本上来说从来没有符号名称,因为它们不存在于单个实例中;在运行时,声明它们的块的每个活动实例都有一个对象。因此,访问它们的唯一可能的方法是通过输入/输出约束。

来自 MSVC 领域的用户可能会发现这一点令人惊讶,因为 MSVC 的内联汇编方案通过将其内联汇编版本中的局部变量引用转换为堆栈指针相对访问等来解决这个问题。然而,它提供的内联汇编版本与优化编译器不兼容,并且在使用该类型内联汇编的函数中几乎不会发生优化。GCC 和与 C 一起从 UNIX 中发展出来的更大的编译器世界并没有做任何类似的事情。

  • 有。在基本汇编中,您不必在“%”前加上“%”前缀。在扩展装配中你会这样做。我没有投反对票。 (3认同)