5

我编译了以下 C 代码:

typedef struct {
    long x, y, z;
} Foo;

long Bar(Foo *f, long i)
{
    return f[i].x + f[i].y + f[i].z;
}

用命令gcc -S -O3 test.c。这是输出中的 Bar 函数:

    .section    __TEXT,__text,regular,pure_instructions
    .globl  _Bar
    .align  4, 0x90
_Bar:
Leh_func_begin1:
    pushq   %rbp
Ltmp0:
    movq    %rsp, %rbp
Ltmp1:
    leaq    (%rsi,%rsi,2), %rcx
    movq    8(%rdi,%rcx,8), %rax
    addq    (%rdi,%rcx,8), %rax
    addq    16(%rdi,%rcx,8), %rax
    popq    %rbp
    ret
Leh_func_end1:

我对这个汇编代码有几个问题:

  1. 如果函数主体中既不使用也不使用“ pushq %rbp”、“ movq %rsp, %rbp”和“ ”的目的是什么?popq %rbprbprsp
  2. 为什么rsirdi自动包含 C 函数的参数(if,分别)而不从堆栈中读取它们?
  3. 我尝试将 Foo 的大小增加到 88 字节(11long秒)并且leaq指令变成了imulq. 将我的结构设计为具有“更圆”的大小以避免乘法指令(以优化数组访问)是否有意义?该leaq指令被替换为:

    imulq   $88, %rsi, %rcx
    
4

3 回答 3

7
  1. 该函数只是使用这些指令构建自己的堆栈帧。他们并没有什么不寻常的地方。但是,您应该注意,由于此函数的大小很小,在代码中使用它时可能会被内联。但是,编译器总是需要生成函数的“正常”版本。另外,@ouah 在他的回答中说了什么。

  2. 这是因为这就是 AMD64 ABI 指定应将参数传递给函数的方式。

    如果类是 INTEGER,则使用序列 %rdi、%rsi、%rdx、%rcx、%r8 和 %r9 的下一个可用寄存器。

    第 20 页,AMD64 ABI 草案 0.99.5 – 2010 年 9 月 3 日

  3. 这与结构大小没有直接关系,而是 - 函数必须访问的绝对地址。如果结构的大小是 24 字节,f是包含结构的数组的地址,并且i是必须访问数组的索引,那么每个结构的字节偏移量是i*24。在这种情况下,乘以 24 是通过lea和 SIB 寻址的组合来实现的。第一lea条指令简单地计算i*3,然后每个后续指令都使用它i*3并将其进一步乘以 8,因此以所需的绝对字节偏移量访问数组,然后使用立即位移来访问各个结构成员((%rdi,%rcx,8).8(%rdi,%rcx,8)16(%rdi,%rcx,8))。lea如果您将结构的大小设置为 88 字节,则根本无法通过组合和任何类型的寻址来快速完成这样的事情。编译器只是假设一个简单imull的在计算i*88方面比一系列的移位、加法、leas 或其他任何东西更有效。

于 2012-06-04T19:16:40.313 回答
2
  1. What is the purpose of pushq %rbp, movq %rsp, %rbp, and popq %rbp, if neither rbp nor rsp is used in the body of the function?

To keep track of the frames when you use a debugger. Add -fomit-frame-pointer to optimize (note that it should be enabled at -O3 but in a lot of gcc versions I used it is not).

于 2012-06-04T19:12:27.227 回答
0
3. I tried increasing the size of Foo to 88 bytes (11 longs) and the leaq instruction became an imulq. Would it make sense to design my structs to have "rounder" sizes to avoid the multiply instructions (in order to optimize array access)?

leaq 调用(本质上和在这个 cae 中)计算 k*a+b,其中“k”是 1、2、4 或 8,“a”和“b”是寄存器。如果“a”和“b”相同,则可以用于 1、2、3、4、5、8 和 9 长的结构。

像 16 longs 这样的大型结构可以通过计算“k”的偏移量和加倍来优化,但我不知道编译器是否会这样做;你将不得不测试。

于 2012-06-04T19:26:17.650 回答