函数return值优化?

Function return value optimization?

我正在编写自己的编程语言,由于各种原因,它编译为 C。(其中之一是我对汇编知之甚少)。

我有一个关于编译器(例如 GCC 或 Clang)如何优化 returning 函数值的问题。假设我有这样的代码:

int FUNC()
{
    int A = 3;
    return A;
}

int main()
{
    int B = FUNC();
}

我的理解是,您希望变量 A 在 return 从 FUNC 复制到 B(如果 A 和 B 是结构,这可能会很昂贵)。编译器会认识到在这种情况下 B 可以指向 A 所在的位置并且不需要副本吗?

如果 main() 看起来像这样怎么办:?

int main()
{
    int C;
    C = FUNC();
}

谢谢!

基本上有两种情况。 (1) return 值不是结构,(2) return 值是结构。

对于情况 (1),return 值通常在寄存器中——它过去总是 r0,或者在浮点 returns 的情况下可能是 f0,或者在 long int returns.

的情况下可能是 r0+r1

在那种情况下,当你有类似

的东西时
int a()
{
    return 3;
}

int b()
{
    return a();
}

编译器基本上将b()编译成一些调用函数a的代码,仅此而已。函数 a return 的值在任何寄存器 int 值函数 return 中,而这正是函数 b 到 [=49] 需要的地方=] 它,所以没有别的事可做;该值已经在它所属的位置。因此,至少在这种情况下,不涉及额外的复制。

(这也可能导致看似“错误”的代码仍然有效的情况,因此这个常见问题:Function returns value without return statement?。)

但是,在你的函数 main 中你所做的 int B = b(),那么是的,可能有一个从 return 寄存器到 B 的“副本”位置。 (尽管如今,聪明的编译器可能记得“目前,r0B”。)

另一方面,对于结构(即情况 2),尤其是对于大型结构,编译器通常会传递一个额外的隐藏参数,该参数是指向调用者中 return 值应该去。也就是说,如果你有

struct largestruct bb();

int main()
{
    struct largestruct B;
    B = bb();
}

它或多或少会像你写的那样被编译

void bb(struct largestruct *);

int main()
{
    struct largestruct B;
    bb(&B);
}

所以如果你有

extern struct largestruct aa();

struct largestruct bb()
{
    return aa();
}

大概会像写的一样编译出来

extern void aa(struct largestruct *);

void bb(struct largestruct *__retp)
{
    aa(__retp);
}

而且,“指针指向正确的位置,不需要复制”这句话或多或少是对的。

一般情况下可以随意写成:

int tmp = a + b;
int c = tmp;

tmp 将被优化。

您的函数将首先通过不分配 A 而只是返回 3 进行类似的优化,取自只读内存(与机器代码一起内联)。

函数内联的工作方式也类似,在这种情况下整个函数调用将被替换为 3。

现在,如果我们添加像打印这样的副作用,printf("%d", B);,您的整个代码都会像这样“伪代码汇编程序”一样针对汇编程序进行优化:

  • 将 3 移入寄存器 x
  • 将字符串文字“%d”的地址移动到寄存器 y
  • 调用 printf(x,y)

在真正的 x86 asm 中是这样的:

mov     esi, 3
mov     edi, OFFSET FLAT:.LC0
call    printf

变量AB和函数都优化掉了

好的编译器超越了您建议的优化。带有 -O3 的 Apple Clang 11 将您的 main 例程编译为:

_main:
    pushq   %rbp
    movq    %rsp, %rbp
    xorl    %eax, %eax
    popq    %rbp
    retq

因此,编译器超出了您建议合并 AB 的范围;它已将它们完全删除。

如果我们包含<stdio.h>并在main中插入printf("%d\n", B);,它被编译为:

_main:
    pushq   %rbp
    movq    %rsp, %rbp
    leaq    L_.str(%rip), %rdi
    movl    , %esi
    xorl    %eax, %eax
    callq   _printf
    xorl    %eax, %eax
    popq    %rbp
    retq

现在AB并没有完全删除,而是在一条指令中缩减为一个立即数。