【问题标题】:Why VS does imul rax,rax,0 instead of simple move?为什么VS做imul rax,rax,0而不是简单的移动?
【发布时间】:2016-06-04 10:49:10
【问题描述】:

我正在查看 Visual Studio 为这个简单的 x64 程序生成的程序集:

struct Point {
    int a, b;

    Point() {
        a = 0; b = 1;
    }
};

int main(int argc, char* argv[])
{
    Point arr[3];
    arr[0].b = 2;
    return 0;
}

当它遇到arr[0].b = 2时,它会生成这个:

mov eax, 8
imul rax, rax, 0
mov dword ptr [rbp+rax+4],2

为什么它使用 imul rax, rax, 0 而不是简单的 mov rax, 0,甚至是 xor rax, rax?如果有的话,imul 如何更有效?

【问题讨论】:

  • 尝试使用优化开关进行编译。
  • 我试过没有优化和使用 /02,但它仍然是 imul rax,rax,0。通过完全优化,我什至无法让它真正向 arr[0] 写入任何内容,它会将所有内容存储为临时变量或类似的东西
  • 你的VS看起来和我的不一样。正如预期的那样,这根本不会使用 /O2 生成任何代码。重新调整代码以使优化器无法删除分配,从而按预期生成 mov dword ptr [rsp+24h],2 。 imul 只会在未优化的构建中生成。

标签: c++ assembly 64-bit


【解决方案1】:

卡马克

原因是程序集正在计算数组中 Point 对象(恰好位于堆栈上)的偏移量,以及变量 b 的偏移量。

带有三 (3) 个操作数状态的 imul 的英特尔文档:

三操作数形式——这种形式需要一个目标操作数( 第一个操作数)和两个源操作数(第二个和第三个 操作数)。这里,第一个源操作数(可以是 通用寄存器或内存位置)乘以 第二个源操作数(立即数)。中间产品 (第一个源操作数大小的两倍)被截断并存储 在目标操作数(通用寄存器)中。

在您的情况下,它正在计算数组中对象的偏移量,从而导致寻址堆栈上的第一个(第零个)Point 位置。解决了这个问题,然后添加.b 的偏移量,即+4。所以分解:

mov  eax,8                   ; prepare to offset into the Point array
imul rax, rax, 0             ; Calculate which Point object is being referred to
mov  dword ptr [rbp+rax+4],2 ; Add the offset to b and move value 2 in

说明。所有这些都解析为arr[0].b = 2。

我认为您没有通过积极优化进行编译。当进行直接编译(无优化、调试等)时,编译器不会对寻址做出任何假设。

与clang的比较

在带有clang 3.9.0 且没有优化标志的OS X (El Capitan) 上,一旦Point 对象在数组中被实例化,.b = 2 的赋值很简单:

mov dword ptr [rbp - 44], 2

在这种情况下,clang 在默认优化期间对偏移和解析寻址非常聪明。

【讨论】:

  • 没有解释为什么编译器不只是mov dword ptr [rbp+4], 2。
  • @EOF 想象一下它是arr[10].b。它需要将Point 的大小乘以10 以获得数组元素的偏移量。
  • @EOF 那么它将是imul rax, rax, 10
  • @Barmar:如果是arr[10].b,那将是越界访问的未定义行为。不要试图跟我耍聪明,你不擅长。
  • @EOF 关键是它只是天真地将索引放入imul 指令中。如果代码经过优化,它可能会做得更好。
猜你喜欢
  • 2015-06-07
  • 2020-07-17
  • 2014-06-08
  • 1970-01-01
  • 2017-05-05
  • 1970-01-01
  • 2022-12-12
  • 2017-10-14
  • 2021-06-17
相关资源
最近更新 更多