【问题标题】:Adding two numbers添加两个数字
【发布时间】:2012-01-18 10:51:24
【问题描述】:

我正在尝试使用 GCC 的内联汇编器熟悉 x86 汇编。我正在尝试添加两个数字(ab)并将结果存储在c 中。我有四种略有不同的尝试,其中三种有效;最后一个没有产生预期的结果。

前两个例子使用了一个中间寄存器,它们都可以正常工作。第三个和第四个示例尝试在不使用中间寄存器的情况下直接将两个值相加,但结果会因优化级别和我添加输入值的顺序而异。我哪里错了?

环境是:

i686-apple-darwin10-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5666) (dot 3)

首先,变量声明如下:

int a = 4;
int b = 7;
int c;

示例 1:

asm("   movl    %1,%%eax;"
    "   addl    %2,%%eax;"
    "   movl    %%eax,%0;"
    : "=r" (c)
    : "r" (a), "r" (b)
    : "%eax"
   );
printf("a=%d, b=%d, c=%d\n", a, b, c);
// output: a=4, b=7, c=11

示例 2:

asm("   movl    %2,%%eax;"
    "   addl    %1,%%eax;"
    "   movl    %%eax,%0;"
    : "=r" (c)
    : "r" (a), "r" (b)
    : "%eax"
   );
printf("a=%d, b=%d, c=%d\n", a, b, c);
// output: a=4, b=7, c=11

示例 3:

asm("   movl    %2,%0;"
    "   addl    %1,%0;"
    : "=r" (c)
    : "r" (a), "r" (b)
   );
printf("a=%d, b=%d, c=%d\n", a, b, c);
// output with -O0: a=4, b=7, c=11
// output with -O3: a=4, b=7, c=14

示例 4:

// this one appears to calculate a+a instead of a+b
asm("   movl    %1,%0;"
    "   addl    %2,%0;"
    : "=r" (c)
    : "r" (a), "r" (b)
   );
printf("a=%d, b=%d, c=%d\n", a, b, c);
// output with -O0: a=4, b=7, c=8
// output with -O3: a=4, b=7, c=11

已解决。 Matthew Slattery's answer 是正确的。之前,它试图为bc 重用eax

movl    -4(%rbp), %edx
movl    -8(%rbp), %eax
movl    %edx, %eax
addl    %eax, %eax

随着 Matthew 建议的修复到位,它现在使用 ecx 来单独保存 c

movl    -4(%rbp), %edx
movl    -8(%rbp), %eax
movl    %edx, %ecx
addl    %eax, %ecx

【问题讨论】:

  • 对我来说效果很好,无论是否启用优化。尝试使用 -S 进行编译,以获得汇编语言列表。然后你可以看到正在使用哪些寄存器。
  • 刚刚注意到我得到不同的结果取决于优化级别。使用新输出更新了代码示例。
  • 那么组装清单告诉你什么?
  • 使用修复前后的程序集列表更新了问题。

标签: gcc x86 inline-assembly


【解决方案1】:

默认情况下,gcc 将假定内联 asm 块将在更新输出操作数之前完成使用输入操作数。这意味着输入和输出都可以分配给同一个寄存器。

但是,在您的示例 3 和 4 中,情况不一定如此。

例如在示例 3 中:

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

...在阅读 a (%1) 之前,您已更新 c (%0)。如果gcc 碰巧将相同的寄存器分配给%0%1,那么它将计算c = b; c += c,因此会以您观察到的方式失败:

printf("a=%d, b=%d, c=%d\n", a, b, c);
// output with -O0: a=4, b=7, c=11
// output with -O3: a=4, b=7, c=14

您可以通过告诉gcc 可以在使用输入之前使用输出操作数来修复它,方法是在操作数中添加“&”修饰符,如下所示:

asm("   movl    %2,%0;"
    "   addl    %1,%0;"
    : "=&r" (c)
    : "r" (a), "r" (b)
   );

(请参阅 gcc 文档中的 "Constraint Modifier Characters"。)

【讨论】:

  • 就是这样,谢谢。使用修复前后的程序集列表更新了问题。
  • 谢谢!这是我需要理解的东西,但文档相当令人生畏。像这样的提示非常有价值。
【解决方案2】:

嗨,我没有看到那里有问题,它在这里编译和工作正常。然而,一个小提示:我很快就对未命名的变量/寄存器感到困惑,所以我决定使用命名变量。例如,您可以像这样实现添加的东西:

static inline void atomicAdd32(volInt32 *dest, int32_t source) {
// IMPLEMENTS:  add m32, r32
__asm__ __volatile__(
        "lock; addl %[in], %[out]"
        : [out] "+m"(*dest)
        : [in] "ir"(source)//, "[out]" "m"(*dest)
        );
return;
  }

(你现在可以忽略 atomic/ lock 的事情),这清楚地表明了发生了什么:

1) 哪些寄存器是可写、可读或两者兼有的

2) 使用什么(内存、寄存器),这在性能和时钟周期方面可能很重要,因为寄存器操作比访问内存要快。

干杯, G.

P.S.:你检查过你的编译器是否重新排列代码吗?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-01-09
    • 1970-01-01
    • 2016-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多