我想知道:用C编程,让我们说我们有两个功能:
int get_a_value();
int calculate_something(int number);
第三个版本的两个版本:
/* version 1 */
int main()
{
int value = get_a_value();
int result = calculate_something(value);
return result;
}
/* version 2 */
int main()
{
int result = calculate_something(get_a_value());
return result;
}
从理论上讲,在正确性,内存使用和效率方面,同一事物的这两个版本之间会有什么区别?他们会产生不同的指示吗?另一方面,在什么情况下会使现实中的可能差异显着?
提前致谢。
答案 0 :(得分:3)
复制这两个版本,使用gcc -S
编译每个版本以获取机器语言输出,并使用sdiff
进行并排比较。
使用gcc版本4.1.2 20070115(SUSE Linux)的结果:
没有优化:
main: main:
.LFB2: .LFB2:
pushq %rbp pushq %rbp
.LCFI0: .LCFI0:
movq %rsp, %rbp movq %rsp, %rbp
.LCFI1: .LCFI1:
subq $16, %rsp subq $16, %rsp
.LCFI2: .LCFI2:
movl $0, %eax movl $0, %eax
call get_a_value call get_a_value
movl %eax, -8(%rbp) | movl %eax, %edi
movl -8(%rbp), %edi <
movl $0, %eax movl $0, %eax
call calculate_something call calculate_something
movl %eax, -4(%rbp) movl %eax, -4(%rbp)
movl -4(%rbp), %eax movl -4(%rbp), %eax
leave leave
ret ret
基本上,一个额外的移动指令。两者都分配相同数量的堆栈空间(subq $16, %rsp
为堆栈保留16个字节),因此在内存方面没有区别。
1级优化(-O1):
main: main:
.LFB2: .LFB2:
subq $8, %rsp subq $8, %rsp
.LCFI0: .LCFI0:
movl $0, %eax movl $0, %eax
call get_a_value call get_a_value
movl %eax, %edi movl %eax, %edi
movl $0, %eax movl $0, %eax
call calculate_something call calculate_something
addq $8, %rsp addq $8, %rsp
ret ret
没有差异。
结果使用gcc版本2.96 20000731(Red Hat Linux 7.2 2.96-112.7.2):
没有优化:
main: main:
pushl %ebp pushl %ebp
movl %esp, %ebp movl %esp, %ebp
subl $8, %esp subl $8, %esp
> subl $12, %esp
> subl $4, %esp
call get_a_value call get_a_value
> addl $4, %esp
movl %eax, %eax movl %eax, %eax
movl %eax, -4(%ebp) | pushl %eax
subl $12, %esp <
pushl -4(%ebp) <
call calculate_something call calculate_something
addl $16, %esp addl $16, %esp
movl %eax, %eax movl %eax, %eax
movl %eax, -8(%ebp) | movl %eax, -4(%ebp)
movl -8(%ebp), %eax | movl -4(%ebp), %eax
movl %eax, %eax movl %eax, %eax
leave leave
ret ret
大致相同数量的指令,订购略有不同。
1级优化(-O1):
main: main:
pushl %ebp pushl %ebp
movl %esp, %ebp movl %esp, %ebp
subl $8, %esp | subl $24, %esp
call get_a_value call get_a_value
subl $12, %esp | movl %eax, (%esp)
pushl %eax <
call calculate_something call calculate_something
leave leave
ret ret
看起来第二个版本保留了更多的堆栈空间。
因此,对于这些特定编译器的特定示例,两个版本之间没有太大差异。在这种情况下,由于以下原因,我赞成第一个版本:
get_a_value
返回的值,然后再将其传递给calculate_something
; calculate_something
对某些输入的表现不佳,它会为您提供一个进行健全检查的地方; 请记住,简洁并不一定意味着快速或高效,以及一个人的快速/高效特定的编译器/硬件组合可以在不同的编译器/硬件组合下无可救药地被破坏。有些编译器实际上更容易优化以清晰方式编写的代码。
您的代码应按顺序:
答案 1 :(得分:2)
我进行了一些测试,为2个版本生成了汇编代码。简单地从bash运行diff命令表明第一个版本比第二个版本有2个指令 如果您想自己尝试,只需使用此命令进行编译
gcc -S main.c -o asmout.s
gcc -S main2.c -o asmout2.s
然后检查与
的差异diff asmout.s asmout2.s
我为第一个提供了更多这两条指令:
movl %eax, -8(%rbp)
movl -8(%rbp), %eax
编辑:
正如Keith Thompson建议使用优化选项编译时,生成的汇编代码对于两个版本都是相同的。
答案 2 :(得分:1)
这实际上取决于平台和编译器,但是通过优化它们通常应该生成相同的代码。在最坏的情况下,版本1将为额外的int分配空间。如果将get_a_value的值放在变量中会使代码更具可读性,那么我会继续这样做。我建议不这样做的唯一一次是深度递归功能。