汇编与 C 代码的比较

Posted

技术标签:

【中文标题】汇编与 C 代码的比较【英文标题】:Assembly Compared With C code 【发布时间】:2016-04-24 03:07:23 【问题描述】:

我目前正在学习汇编和 C 编程语言,对此我有几个问题。

C 代码

int arith(int x, int y, int z) 
    int t1 = x + y;
    int t2 = z*48;
    int t3 = t1 & 0xFFFF;
    int t4 = t2 * t3;
    return t4;

汇编代码

movl  16(%ebp),%eax         z
leal  (%eax,%eax,2), %eax   z*3
sall  $4,%eax               t2 = z*48
movl  12(%ebp),%edx         y
addl  8(%ebp),%edx          t1 = x+y
andl  $65535,%edx           t3 = t1&0xFFFF
imull %edx,%eax             Return t4 = t2*t3

我可以使用 imull $48, %eax 代替 leal 然后移动 4 以将 z 乘以 48 吗?

另外,这是多次使用 %edx 寄存器。这是否意味着 t1 被覆盖?换句话说,如果我愿意,我还能在 t4 之前检索 t1 吗?

【问题讨论】:

是,否(变量t1 已优化掉),否。对于最后一个问题x+y 已计算但从未保存。 EDXaddl 8(%ebp),%edx 之后具有值x+y,但指令andl $65535,%edx 破坏了它。如果您在addl 8(%ebp),%edx 之后将 EDX 移动到像 ECX 这样的寄存器,那么您仍然可以访问计算的 x+y 部分。 酷。 C 代码真的会在幕后被翻译成不会将每个变量存储到自己的寄存器中吗? 如果不需要,就不要。这就是优化编译器的力量。 一个 C 优化器可以很容易地优化这样的表达式。您会注意到在您的代码中没有与 t1、t2、t3、t4 关联的内存。在很短的一段时间内,计算的 x+y 部分在 EDX 中,但该值很快被覆盖以继续计算。它被简化为return (z*48) * ((x+y) & 0xffff) 【参考方案1】:

尝试将程序集与您的代码逐行匹配可能不是解决此问题的最佳方法。编译器进行了多项优化,以使您的程序尽可能高效地运行,这就是为什么您可能会注意到代码之间存在一些不一致的原因。

要回答您的第一个问题,从技术上讲这可行,但编译器再次进行了多项优化。因此,虽然使用 imul 似乎更直观,但编译器确定 leal 和 sall 更有效。 编辑:我只想指出,如果可能,几乎总是使用位移运算符而不是 imul。位移对于 CPU 来说要便宜得多,因为它实际上只是位移位值,而不是尝试执行一些可能需要更多 CPU 时间的数学运算。

现在关于“覆盖” t1。 Assembly 没有关于程序变量的任何信息——它所知道的只是它需要对某些值执行一些操作。虽然程序集可能使用 4 个不同的寄存器来存储 t1-4,但编译器确定它是不必要的,并且您只需要 2 个寄存器来存储所有值。如果你仔细想想,这应该是有道理的。您的功能可以简化为几行代码。显然这不是一个好主意,因为这会使它无法阅读,但是汇编不一定被设计为“可读”的。如果您返回程序并在返回 t4 之前使用 t1 执行了一些其他操作,您可能会注意到您的程序集与以前不同,并且它可能正在使用另一个寄存器,具体取决于值的使用方式。

如果您真的想要汇编程序的准系统版本,请使用 -Og 标志进行编译以关闭编译器优化。它可能仍然与您的代码不完全匹配,但它可能会让您更容易理解发生了什么。

【讨论】:

谢谢。你说的一切对我来说都是有道理的。像这样在幕后进行优化的方式很酷。 可以在Agner Fog document 中找到一个很好的指令优化信息来源。根据架构,LEAL 甚至可能无法到达 ALU。在某些 x86 架构上,它是作为 AGU 的一部分完成的。

以上是关于汇编与 C 代码的比较的主要内容,如果未能解决你的问题,请参考以下文章

Hexagon DSP系列记录——plain C/intrinsics/asm汇编代码实现耗时比较

Hexagon DSP系列记录——plain C/intrinsics/asm汇编代码实现耗时比较

嵌入式Linux ARM汇编——C语言与ARM汇编混合编程

深入理解C

x86-64 汇编中的数组元素比较(AT&T 语法)

如何通过比较数字来获得所需的功能?