16.1.7 返回值优化

拷贝的避免和返回值的优化使我们能够消除不必要的拷贝操作。

局部变量往往是在函数的栈上被创建。所以如果一个函数返回了一个结构体类型的实例,函数需要在栈上先创建这个变量,然后将变量拷贝到外面的世界(除非变量可以被存储到 rax 和 rdx 两个寄存器上)。

列表 16-14 展示了一个例子。

Listing 16-14. nrvo.c

struct p  {
    long x;
    long y;
    long z;
};

__attribute__ ((noinline))
    struct p f(void) {
        struct p copy;
        copy.x = 1;
        copy.y = 2;
        copy.z = 3;
        return copy;
}

int main(int argc, char** argv) {
    volatile struct p inst = f();
    return 0;
}

一个名叫 copy 的 struct p 的实例在栈帧上创建并离开。它的字段被赋值为 1,2 和 3,然后被拷贝到外部,我们推测可能是被 f 作为一个隐藏的参数所接收了。

列表 16-15 是结果的汇编代码。

Listing 16-15. nrvo_off.asm
00000000004004b6 <f>:
; prologue
4004b6:  55                             push   rbp
4004b7:  48 89 e5                       mov    rbp,rsp
; A hidden argument is the address of a structure which will hold the  result.
; It is saved into stack.
4004ba:  48 89 7d d8                    mov    QWORD PTR [rbp-0x28],rdi
; Filling the fields of `copy` local variable
4004be:  48 c7 45 e0 01 00 00
4004c5:  00
4004c6:  48 c7 45 e8 02 00 00
4004cd:  00
mov    QWORD PTR [rbp-0x20],0x1
mov    QWORD PTR [rbp-0x18],0x2
mov    QWORD PTR [rbp-0x10],0x3
4004ce:  48 c7 45 f0 03 00 00
4004d5:  00
; rax = address of the destination struct

4004d6:  48 8b 45 d8                   mov    rax,QWORD PTR [rbp-0x28]
; [rax] = 1 (taken from `copy.x`)
4004da:  48 8b 55 e0                   mov    rdx,QWORD PTR [rbp-0x20]
4004de:  48 89 10                      mov    QWORD PTR [rax],rdx
; [rax + 8] = 2 (taken from `copy.y`)
4004da:  48 8b 55 e0                   mov    rdx,QWORD PTR [rbp-0x20]
4004e1:  48 8b 55 e8                   mov    rdx,QWORD PTR [rbp-0x18]
4004e5:  48 89 50 08                   mov    QWORD PTR [rax+0x8],rdx
; [rax + 10] = 3 (taken from `copy.z`)
4004e9:  48 8b 55 f0                   mov    rdx,QWORD PTR [rbp-0x10]
4004ed:  48 89 50 10                   mov    QWORD PTR [rax+0x10],rdx
; rax =  address where we have put the structure contents
; (it was the hidden argument)
4004f1:  48 8b 45 d8                   mov    rax,QWORD PTR [rbp-0x28]
4004f5:  5d                            pop    rbp
4004f6:  c3                            ret

00000000004004f7 <main>:
4004f7:  55                             push   rbp
4004f8:  48 89 e5                       mov    rbp,rsp
4004fb:  48 83 ec 30                    sub    rsp,0x30
4004ff:  89 7d dc                       mov    DWORD PTR [rbp-0x24],edi
400502:  48 89 75 d0                    mov    QWORD PTR [rbp-0x30],rsi
400506:  48 8d 45 e0                    lea    rax,[rbp-0x20]
40050a:  48 89 c7                       mov    rdi,rax
40050d:  e8 a4 ff ff ff                 call   4004b6 <f>
400512:  b8 00 00 00 00                 mov    eax,0x0
400517:  c9                             leave
400518:  c3                             ret
400519:  0f 1f 80 00 00 00 00           nop    DWORD PTR [rax+0x0]

编译器能够产生更高效的代码,如列表 16-16 所示。

Listing 16-16.nrvo_on.asm

00000000004004b6 <f>:
4004b6:  48 89 f8                       mov     rax,rdi
4004b9:  48 c7 07 01 00 00 00           mov     QWORD PTR [rdi],0x1
4004c0:  48 c7 47 08 02 00 00           mov     QWORD PTR [rdi+0x8],0x2
4004c7:  00
4004c8:  48 c7 47 10 03 00 00           mov QWORD PTR [rdi+0x10],0x3
4004cf:  00
4004d0:  c3                             ret

00000000004004d1 <main>:
4004d1:  48 83 ec 20                    sub     rsp,0x20
4004d5:  48 89 e7                       mov     rdi,rsp
4004d8:  e8 d9 ff ff ff                 call    4004b6 <f>
4004dd:  b8 00 00 00 00                 mov     eax,0x0
4004e2:  48 83 c4 20                    add     rsp,0x20
4004e6:  c3                             ret
4004e7:  66 0f 1f 84 00 00 00           nop     WORD PTR [rax+rax*1+0x0]
4004ee:  00 00

我们没有为了拷贝而在栈帧上分配任何空间!而是直接操作结构体并将其作为一个隐藏参数传给我们。

如何活用本节结论?如果你想写一个函数来填充某个结构体,传入预分配好内存空间的指针并没有什么收益。

results matching ""

    No results matching ""