我的gcc版本是4.4.1
先来看const和define以及enum定义,编译器会做什么样的优化:
ok,我们然后来看对应的汇编代码(我这里用O2编译):
1 可以看到编译器会将它们的值直接计算然后再调用函数a,也就是+那一步被编译器优化掉了。
2 没有static修饰的const变量的那块内存被保留了,尽管foo并没有引用这块内存。
接下来看宏和内联,编译器会做什么不同的优化:
先看测试代码:
然后来看汇编代码。这里只是汇编代码·片断:
bar和foo2的代码完全一样,由此可见bar函数自动被内联了。
下面这个测试我用的gcc是4.3
现在我们来去掉static,然后来看会出现什么情况,源码:
来看汇编:
可以看到依然会被内联,可是和没有加static相比,会生成abs2这个段.这是因为编译器要做最保守的处理,如果不加static的话,abs2可能还被其他的文件所调用,因此会保留这个abs2.
这里来做个总结吧.实验代码就不贴了,有兴趣可以自己去试试.
1 用O3编译,函数加不加static ,都会被内联.
2 如果函数被调用一次,并且函数体很大
如果为static修饰的函数,他无论如何都会被内联.
如果不加static的话,就需要你强制O3优化了,用O3也会生成abs2那个段.如果O2编译,则会生成跳转指令.
3 如果函数被调用多次.
加不加static修饰的函数,调用次数超过一定的数值都会生成跳转指令.
因此我们一个文件内的内部函数尽量都声明为static的。
接下来来看数组的边界检测,编译器会如何优化:
然后来看对应的汇编代码,其中L6表示第一个赋值,L7为第二个赋值:
可以看到生成了相同的代码,边界检测被编译器优化了(也就是删除掉了).也就是循环的时候(如果我们for循环的边界检测刚好包含本身的边界检测的话,我们不需要多余的边界检测.)如果将for循环的边界检测改为大于100000的话,我们就会看到,编译器会生成相应的边界检测的.
汇编代码(只看write_to那部分):
可以看到当for的边界检测包含write_to的边界的时候就会生成这段.
再来看另外的一些边界检测优化:
来看regular的汇编代码:
可以看到i>5 && i<100被优化为 i-6 <93。并且编译器知道exit不会有返回值,因此还会设置正确的返回值。
接下来来看for和while,编译器会有什么不一样的处理:
来看生成的汇编:
可以看到完全一模一样,我记得在老的版本的gcc中,for和while生成的汇编是不一样的。
还有gcc的switch优化为跳转表可以看我前面的blog.
还有一些就是数值计算的优化了,不过这里就不介绍了。
先来看const和define以及enum定义,编译器会做什么样的优化:
enum { constant=23 };
#define CONSTANT 23
static const int Static_Constant=23;
const int Constant = 23;
int foo() {
a(constant+3);
a(CONSTANT+4);
a(Static_Constant+5);
return Static_Constant + Constant;
}
ok,我们然后来看对应的汇编代码(我这里用O2编译):
foo:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
//constant+3=26
movl $26, (%esp)
call a
///CONSTANT+4=27
movl $27, (%esp)
call a
///Static_Constant+5=28
movl $28, (%esp)
call a
///Static_Constant + Constant = 23 + 23 = 46
movl $46, %eax
leave
ret
.size foo, .-foo
.......................................
///被保留。
Constant:
.long 23
.ident "GCC: (Ubuntu 4.4.1-4ubuntu8) 4.4.1"
1 可以看到编译器会将它们的值直接计算然后再调用函数a,也就是+那一步被编译器优化掉了。
2 没有static修饰的const变量的那块内存被保留了,尽管foo并没有引用这块内存。
接下来看宏和内联,编译器会做什么不同的优化:
先看测试代码:
#define abs(x) ((x)>0?(x):-(x))
static long abs2(long x) {
return x >= 0 ? x : -x;
}
long foo2(long a) {
return abs(a);
}
long bar(long a) {
return abs2(a);
}
然后来看汇编代码。这里只是汇编代码·片断:
foo2:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
popl %ebp
movl %eax, %edx
sarl $31, %edx
xorl %edx, %eax
subl %edx, %eax
ret
.size foo2, .-foo2
.p2align 4,,15
.globl bar
.type bar, @function
bar:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
popl %ebp
movl %eax, %edx
sarl $31, %edx
xorl %edx, %eax
subl %edx, %eax
ret
.size bar, .-bar
.p2align 4,,15
bar和foo2的代码完全一样,由此可见bar函数自动被内联了。
下面这个测试我用的gcc是4.3
现在我们来去掉static,然后来看会出现什么情况,源码:
long abs2(long x) {
return x >= 0 ? x : -x;
}
long foo2(long a) {
return abs(a);
}
long bar(long a) {
return abs2(a);
}
来看汇编:
abs2:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
popl %ebp
movl %eax, %edx
sarl $31, %edx
xorl %edx, %eax
subl %edx, %eax
ret
bar:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
popl %ebp
movl %eax, %edx
sarl $31, %edx
xorl %edx, %eax
subl %edx, %eax
ret
可以看到依然会被内联,可是和没有加static相比,会生成abs2这个段.这是因为编译器要做最保守的处理,如果不加static的话,abs2可能还被其他的文件所调用,因此会保留这个abs2.
这里来做个总结吧.实验代码就不贴了,有兴趣可以自己去试试.
1 用O3编译,函数加不加static ,都会被内联.
2 如果函数被调用一次,并且函数体很大
如果为static修饰的函数,他无论如何都会被内联.
如果不加static的话,就需要你强制O3优化了,用O3也会生成abs2那个段.如果O2编译,则会生成跳转指令.
3 如果函数被调用多次.
加不加static修饰的函数,调用次数超过一定的数值都会生成跳转指令.
因此我们一个文件内的内部函数尽量都声明为static的。
接下来来看数组的边界检测,编译器会如何优化:
static char array[100000];
static int write_to(int ofs,char val) {
///检测边界(这个会被优化掉)
if (ofs>=0 && ofs<100000)
array[ofs]=val;
}
int main() {
int i;
for (i=0; i<100000; ++i) array[i]=0;//<这里没有检测边界
for (i=0; i<100000; ++i) write_to(i,-1);
}
然后来看对应的汇编代码,其中L6表示第一个赋值,L7为第二个赋值:
.L6:
movl $0, array(%eax)
addl $4, %eax
cmpl $100000, %eax
jne .L6
xorl %eax, %eax
.p2align 4,,7
.p2align 3
.L7:
movl $-1, array(%eax)
addl $4, %eax
cmpl $100000, %eax
///可以看到边界检测被优化掉了
jne .L7
popl %ebp
ret
.size main, .-main
.p2align 4,,15
可以看到生成了相同的代码,边界检测被编译器优化了(也就是删除掉了).也就是循环的时候(如果我们for循环的边界检测刚好包含本身的边界检测的话,我们不需要多余的边界检测.)如果将for循环的边界检测改为大于100000的话,我们就会看到,编译器会生成相应的边界检测的.
static char array[100000];
static int write_to(int ofs,char val){
if(ofs>=0&&ofs<99999)
array[ofs]=val;
}
int main(){
int i;
for(i=0;i<99998;++i)array[i]=0;
for(i=1;i<100000;++i)write_to(i,-1);
}
汇编代码(只看write_to那部分):
.L6:
movb $-1, array(%eax)
addl $1, %eax
cmpl $100000, %eax
je .L4
cmpl $99999, %eax
jne .L6
.L4:
popl %ecx
popl %ebp
leal -4(%ecx), %esp
可以看到当for的边界检测包含write_to的边界的时候就会生成这段.
再来看另外的一些边界检测优化:
int regular(int i) {
///这里判断i的边界。
if (i>5 && i<100)
return 1;
exit(0);
}
来看regular的汇编代码:
regular:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
movl 8(%ebp), %eax
///先将i减去6
subl $6, %eax
///然后和93比较
cmpl $93, %eax
ja .L18
movl $1, %eax
leave
ret
.L18:
///设置返回值为0
movl $0, (%esp)
call exit
可以看到i>5 && i<100被优化为 i-6 <93。并且编译器知道exit不会有返回值,因此还会设置正确的返回值。
接下来来看for和while,编译器会有什么不一样的处理:
char array[100000];
int foo5(int a)
{
int i;
for (i=1; i<a; i++)
array[i]=array[i-1]+1;
}
void foo6(int a)
{
int i =1;
while (i<a) {
array[i]=array[i-1]+1;
i++;
}
}
来看生成的汇编:
foo5:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %ecx
pushl %ebx
cmpl $1, %ecx
jle .L23
movzbl array, %ebx
movl $1, %eax
foo6:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %ecx
pushl %ebx
cmpl $1, %ecx
jle .L29
movzbl array, %ebx
movl $1, %eax
可以看到完全一模一样,我记得在老的版本的gcc中,for和while生成的汇编是不一样的。
还有gcc的switch优化为跳转表可以看我前面的blog.
还有一些就是数值计算的优化了,不过这里就不介绍了。