函数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
的“副本”位置。 (尽管如今,聪明的编译器可能记得“目前,r0
是 B
”。)
另一方面,对于结构(即情况 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
变量A
B
和函数都优化掉了
好的编译器超越了您建议的优化。带有 -O3
的 Apple Clang 11 将您的 main
例程编译为:
_main:
pushq %rbp
movq %rsp, %rbp
xorl %eax, %eax
popq %rbp
retq
因此,编译器超出了您建议合并 A
和 B
的范围;它已将它们完全删除。
如果我们包含<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
现在A
和B
并没有完全删除,而是在一条指令中缩减为一个立即数。
我正在编写自己的编程语言,由于各种原因,它编译为 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.
在那种情况下,当你有类似
的东西时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
的“副本”位置。 (尽管如今,聪明的编译器可能记得“目前,r0
是 B
”。)
另一方面,对于结构(即情况 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
变量A
B
和函数都优化掉了
好的编译器超越了您建议的优化。带有 -O3
的 Apple Clang 11 将您的 main
例程编译为:
_main:
pushq %rbp
movq %rsp, %rbp
xorl %eax, %eax
popq %rbp
retq
因此,编译器超出了您建议合并 A
和 B
的范围;它已将它们完全删除。
如果我们包含<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
现在A
和B
并没有完全删除,而是在一条指令中缩减为一个立即数。