假设我有以下代码:
int f() {
int foo = 0;
int bar = 0;
foo++;
bar++;
// many more repeated operations in actual code
foo++;
bar++;
return foo+bar;
}
将重复的代码抽象为单独的函数,我们得到
static void change_locals(int *foo_p, int *bar_p) {
*foo_p++;
*bar_p++;
}
int f() {
int foo = 0;
int bar = 0;
change_locals(&foo, &bar);
change_locals(&foo, &bar);
return foo+bar;
}
我希望编译器内联change_locals
函数,并将结果代码中*(&foo)++
之类的内容优化为foo++
。
如果我没记错的话,取一个局部变量的地址通常会阻止一些优化(例如它不能存储在寄存器中),但是如果没有对地址进行指针运算并且它没有从功能?使用较大的change_locals
,如果在MSVC中声明inline
(__inline
)会有所不同吗?
我对GCC和MSVC编译器的行为特别感兴趣。
答案 0 :(得分:3)
inline
(及其所有表兄_inline
,__inline
...)。除了较低的优化级别外,它可能内联它决定的任何优势。
gcc -O3 for x86的代码过程是:
.text
.p2align 4,,15
.globl f
.type f, @function
f:
pushl %ebp
xorl %eax, %eax
movl %esp, %ebp
popl %ebp
ret
.ident "GCC: (GNU) 4.4.4 20100630 (Red Hat 4.4.4-10)"
它返回零,因为* ptr ++没有按你的想法行事。将增量更正为:
(*foo_p)++;
(*bar_p)++;
结果
.text
.p2align 4,,15
.globl f
.type f, @function
f:
pushl %ebp
movl $4, %eax
movl %esp, %ebp
popl %ebp
ret
所以它直接返回4.它不仅内联它们,而且还优化了计算。
来自vs 2005的Vc ++提供了类似的代码,但它也为change_locals()
创建了无法访问的代码。我使用命令行
/O2 /FD /EHsc /MD /FA /c /TP
答案 1 :(得分:2)
如果我没记错的话,服用 通常是局部变量的地址 阻止一些优化(例如它 不能存储在寄存器中),但是 没有指针时这是否适用 算术是在地址上完成的 它没有逃脱功能吗?
一般的答案是,如果编译器可以确保没有其他人会改变其背后的值,则可以安全地将其放在寄存器中。
将此想象为编译器首先执行内联,然后将所有那些*&foo
(由内联产生)转换为简单foo
,然后再决定是否应将它们放在内存中的寄存器中堆栈。
有了更大的change_locals,是吗? 如果宣布它会有所作为 内联(MSVC中的__inline)?
同样,一般来说,编译器是否决定使用启发式方法来内联。如果您明确指定要内联的内容,编译器可能会将其加权到其决策过程中。
答案 2 :(得分:1)
我使用以下方法测试了gcc 4.5,MSC和IntelC:
#include <stdio.h>
void change_locals(int *foo_p, int *bar_p) {
(*foo_p)++;
(*bar_p)++;
}
int main() {
int foo = printf("");
int bar = printf("");
change_locals(&foo, &bar);
change_locals(&foo, &bar);
printf( "%i\n", foo+bar );
}
他们所有人都内联/优化了foo + bar值,但也做到了 生成change_locals()的代码(但没有使用它)。
不幸的是,仍然无法保证他们会这样做 任何一种这样的“本地功能”。
GCC
__Z13change_localsPiS_:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %edx
movl 12(%ebp), %eax
incl (%edx)
incl (%eax)
leave
ret
_main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
pushl %ebx
subl $28, %esp
call ___main
movl $LC0, (%esp)
call _printf
movl %eax, %ebx
movl $LC0, (%esp)
call _printf
leal 4(%ebx,%eax), %eax
movl %eax, 4(%esp)
movl $LC1, (%esp)
call _printf
xorl %eax, %eax
addl $28, %esp
popl %ebx
leave
ret