内联汇编约束修饰符 = 和 +

Posted

技术标签:

【中文标题】内联汇编约束修饰符 = 和 +【英文标题】:Inline assembly constraint modifiers = and + 【发布时间】:2013-05-22 15:09:48 【问题描述】:

我编写了一个包含内联汇编代码的简单程序。 我的代码只是添加变量 a 和 b 并返回 结果是 b。

让我困惑的是为什么下面的代码会生成 此指令 movl 28(%esp), %ecx.

我不完全理解修饰符 + 和 = 在输入和输出列表中的作用。 因此,如果您能对此有所了解,我们将不胜感激。

#include <cstdio>

int main( int argc , char ** argv )

    int a = 2, b = 7;

    __asm__
    (
     "addl %1,%0;"      
    :"+r"(b)           
    :"r"(a), "r"(b)     
    );

    printf("b = %d\n",b);

    return 0;

    movl    $2, 24(%esp)
    movl    $7, 28(%esp)

    movl    24(%esp), %edx
    movl    28(%esp), %ecx
    movl    28(%esp), %eax

    addl %edx,%eax

    movl    %eax, 28(%esp)

我接下来要展示的内容是错误的。但这是为了让我更好地了解 GCC 中发生的事情。

好的,现在我从 +r 更改为 =r。这是 GCC 生成的汇编代码。

#include <cstdio>

int main( int argc , char ** argv )

    int a = 2, b = 7;

    __asm__
    (
     "addl %1,%0;"      
    :"=r"(b)           
    :"r"(a), "r"(b)     
    );

    printf("b = %d\n",b);

    return 0;

    movl    $2, 24(%esp)
    movl    $7, 28(%esp)
    movl    24(%esp), %eax
    movl    28(%esp), %edx

    addl %eax,%eax;

    movl    %eax, 28(%esp)

现在输出为 4,这是错误的。 我的问题是为什么使用 "=r" GCC 决定为 b 重用寄存器 eax,如图所示 这条指令addl %eax,%eax;

【问题讨论】:

【参考方案1】:

让我困惑的是为什么下面的代码会生成这条指令movl 28(%esp), %ecx

因为您已将b 列为位于两个单独的输入寄存器中;第一部分中的+ 表示程序集读取和修改寄存器。所以它被加载到两个寄存器中,即使程序集不使用第二个。

程序集应该是:

"addl %1,%0;" : "+r"(b) : "r"(a)

我不完全理解修饰符 + 和 = 在输入和输出列表中的作用。

+ 表示寄存器被读写,因此在汇编开始之前它必须具有变量的值。 = 表示它只是被写入,并且在组装之前可以具有任何值。

详情请参阅documentation。

我的问题是为什么使用 "=r" GCC 决定将寄存器 eax 用于 b,如本指令所示 addl %eax,%eax;

因为现在你的约束是错误的。您告诉编译器您只写入addl 指令的第二个操作数(%0),因此它假定它可以使用与输入之一相同的寄存器。事实上,该操作数也是addl 的输入。然后你仍然告诉它你需要在程序集不使用的单独寄存器中的b 的第二个副本。

如上所述,在第一个(输出)列表中使用"+r"(b) 表示%0b,用于输入和输出,在第二个(输入)列表中使用"r"(a)表示%1a 并且仅用于输入。不要放入第三个寄存器,因为程序集中没有 %2

【讨论】:

感谢您的回答。我开始理解这个概念。但我还有另一个例子。我将很快用一个新示例更新我的帖子。 @takwing:我已经更新了新问题。简短的回答是:如果寄存器约束与程序集所做的不匹配,那么寄存器分配将是虚假的。 非常感谢您的回答。我很清楚这一点。至于文档,我一遍又一遍地阅读,但我仍然没有理解整个概念。这是文档中关于两个修饰符的说明: =' 表示此操作数对于此指令是只写的:先前的值被丢弃并被输出数据替换。 ‘+’ 表示该操作数既可以被指令读取也可以被指令写入。让我感到困惑和仍然困惑的是“这个指令”这个词的使用。但是,在 asm() 内部可以有一系列汇编指令。 @takwing:是的,这有点令人困惑。 “本说明书”是指整个组装过程;编译器此时不知道单独的汇编指令。它只是根据约束分配寄存器等,将它们替换为汇编字符串,然后将其与生成的其余代码一起汇编。 我明白了:) 所以编译器对 asm() 中的任何内容都一无所知,除了输入、输出和损坏的寄存器列表?【参考方案2】:

它非常简单——+ 表示输入和输出,而= 表示仅输出。所以当你说:

asm("addl %1,%0;" : "+r"(b) : "r"(a), "r"(b));

你有三个操作数。一个输入/输出寄存器(%0)初始化为b,并将其输出放入b,两个输入寄存器(%1%2)分别初始化为ab。现在你从不使用%2,但无论如何它都在那里。您可以在生成的代码中看到它:

movl    24(%esp), %edx
movl    28(%esp), %ecx
movl    28(%esp), %eax

addl %edx,%eax

movl    %eax, 28(%esp)

所以编译器使用%eax 代表%0%edx 代表%1%ecx 代表%2。这三个都在内联代码之前加载,%0 在之后写回。

当你这样做时:

asm("addl %1,%0;" : "=r"(b) : "r"(a), "r"(b));

现在%0 只是输出(不是输入)。所以编译器可以产生:

movl    24(%esp), %eax
movl    28(%esp), %edx

addl %eax,%eax;

movl    %eax, 28(%esp)

%eax 用于%0%1%edx 用于%2

获得你想要的东西的另一种方式是:

asm("addl %1,%0;" : "=r"(b) : "r"(a), "0"(b));

%2 上使用0 约束意味着编译器必须将其放在与 %0 所以最终生成:

movl    24(%esp), %edx
movl    28(%esp), %eax

addl %edx,%eax

movl    %eax, 28(%esp)

%eax 用于%0%2,将%edx 用于%1

【讨论】:

【参考方案3】:

您不应该使用"r" 重复b - 使用"0"(输出参数编号)。现在编译器正在将b 加载到未使用的%ecx 中。

【讨论】:

在 "r"(b) 中重复 r 意味着什么?为什么会导致 GCC 将 b 加载到不同的寄存器中,在这种情况下是 ECX?【参考方案4】:

“我写了一个包含内联汇编代码的简单程序。”

"我的代码只是简单地将变量 a 和 b 添加并在 b 中返回结果。 哈哈哈哈哈哈”

在哪里?

mov ax,2
mov bx,3 

add bx,ax

如果你愿意,可以叫我爱因斯坦……

这让我想起了“恶搞”采访

阅读和哭泣

http://www-users.cs.york.ac.uk/susan/joke/cpp.htm

【讨论】:

以上是关于内联汇编约束修饰符 = 和 +的主要内容,如果未能解决你的问题,请参考以下文章

优化系列汇编优化技术:x86架构内联汇编及demo

实用技能分享,充分利用内联函数,内联汇编,内部函数和嵌入式汇编提升代码执行效率和便捷性(2021-12-17)

GCC内联汇编常见陷阱

java代码效率优化

JAVA代码效率优化

v-model指令的详细用法--收集表单数据&修饰符