代码的执行效率分析
◼ 思考:if-else和switch,谁的效率高?
int no = 4;
if (no == 1) {
printf("no is 2");
} else if (no == 2) {
printf("no is 2");
} else if (no == 3) {
printf("no is 3");
} else if (no == 4) {
printf("no is 4");
} else if (no == 5) {
printf("no is 5");
} else {
printf("other no");
}
int no = 4;
switch (no) {
case 1:
printf("no is 1");
break;
case 2:
printf("no is 2");
break;
case 3:
printf("no is 3");
break;
case 4:
printf("no is 4");
break;
case 5:
printf("no is 5");
break;
default:
printf("other no");
break;
}
$SG28198 DB 'no is 1', 00H
$SG28201 DB 'no is 2', 00H
$SG28204 DB 'no is 3', 00H
$SG28207 DB 'no is 4', 00H
$SG28210 DB 'no is 5', 00H
$SG28211 DB 'other no', 00H
unsigned __int64 `__local_stdio_printf_options'::`2'::_OptionsStorage DQ 01H DUP (?) ; `__local_stdio_printf_options'::`2'::_OptionsStorage
_no$ = -4 ; size = 4
_main PROC
push ebp
mov ebp, esp
push ecx
mov DWORD PTR _no$[ebp], 4
cmp DWORD PTR _no$[ebp], 1
jne SHORT $LN2@main
push OFFSET $SG28198
call _printf
add esp, 4
jmp SHORT $LN1@main
$LN2@main:
cmp DWORD PTR _no$[ebp], 2
jne SHORT $LN4@main
push OFFSET $SG28201
call _printf
add esp, 4
jmp SHORT $LN1@main
$LN4@main:
cmp DWORD PTR _no$[ebp], 3
jne SHORT $LN6@main
push OFFSET $SG28204
call _printf
add esp, 4
jmp SHORT $LN1@main
$LN6@main:
cmp DWORD PTR _no$[ebp], 4
jne SHORT $LN8@main
push OFFSET $SG28207
call _printf
add esp, 4
jmp SHORT $LN1@main
$LN8@main:
cmp DWORD PTR _no$[ebp], 5
jne SHORT $LN10@main
push OFFSET $SG28210
call _printf
add esp, 4
jmp SHORT $LN1@main
$LN10@main:
push OFFSET $SG28211
call _printf
add esp, 4
$LN1@main:
xor eax, eax
mov esp, ebp
pop ebp
ret 0
_main ENDP
mov DWORD PTR _no$[ebp], 4
将 4 放入[ebp-4]内存地址,no=4
cmp DWORD PTR _no$[ebp], 1
比较no和1的大小
jne
jump not equal,不相等的时候就会跳转 jne SHORT $LN2@main
不相等,就直接跳到 $LN2@main 后面的汇编指令继续执行
$SG28198 DB 'no is 1', 00H
push OFFSET $SG28198
call _printf
如果no=1 相等的话 就不跳转 执行 push OFFSET $SG28198 call _printf
相当于 printf("no is 1");
jmp == jump,无条件跳转 jmp SHORT $LN1@main
跳到程序结尾
从if-else if - else 汇编指令来分析,把最常命中的条件放前面,减少cmp
指令条件的判断
switch
$SG28199 DB 'no is 1', 00H
$SG28201 DB 'no is 2', 00H
$SG28203 DB 'no is 3', 00H
$SG28205 DB 'no is 4', 00H
$SG28207 DB 'no is 5', 00H
$SG28209 DB 'other no', 00H
unsigned __int64 `__local_stdio_printf_options'::`2'::_OptionsStorage DQ 01H DUP (?) ; `__local_stdio_printf_options'::`2'::_OptionsStorage
_no$ = -8 ; size = 4
tv64 = -4 ; size = 4
_main PROC
push ebp
mov ebp, esp
sub esp, 8
mov DWORD PTR _no$[ebp], 4
mov eax, DWORD PTR _no$[ebp]
mov DWORD PTR tv64[ebp], eax
mov ecx, DWORD PTR tv64[ebp]
sub ecx, 1
mov DWORD PTR tv64[ebp], ecx
cmp DWORD PTR tv64[ebp], 4
ja SHORT $LN9@main
mov edx, DWORD PTR tv64[ebp]
jmp DWORD PTR $LN11@main[edx*4]
$LN4@main:
push OFFSET $SG28199
call _printf
add esp, 4
jmp SHORT $LN1@main
$LN5@main:
push OFFSET $SG28201
call _printf
add esp, 4
jmp SHORT $LN1@main
$LN6@main:
push OFFSET $SG28203
call _printf
add esp, 4
jmp SHORT $LN1@main
$LN7@main:
push OFFSET $SG28205
call _printf
add esp, 4
jmp SHORT $LN1@main
$LN8@main:
push OFFSET $SG28207
call _printf
add esp, 4
jmp SHORT $LN1@main
$LN9@main:
push OFFSET $SG28209
call _printf
add esp, 4
$LN1@main:
xor eax, eax
mov esp, ebp
pop ebp
ret 0
npad 2
$LN11@main:
DD $LN4@main //0-3
DD $LN5@main //4-7
DD $LN6@main //8-11
DD $LN7@main //12-15
DD $LN8@main //16-19
_main ENDP
内存中的存了代码跳转的地址
switch核心汇编分析
mov DWORD PTR _no$[ebp], 4 //no = 4
mov eax, DWORD PTR _no$[ebp] //eax = 4
mov DWORD PTR tv64[ebp], eax //tv64 = 4 tv64临时变量
mov ecx, DWORD PTR tv64[ebp] //ecx = 4
sub ecx, 1 //ecx = 3
mov DWORD PTR tv64[ebp], ecx //tv64 = 3
cmp DWORD PTR tv64[ebp], 4 //比较tv64和4的大小
ja SHORT $LN9@main //直接跳转到$LN9@main
mov edx, DWORD PTR tv64[ebp] //edx = 3
jmp DWORD PTR $LN11@main[edx*4] //无条件跳转到 $LN11@main 3*4=12
开辟了20个字节的存储空间,作为jmp比较跳转地址的保存空间,在x86环境下,一个指针的地址占四个字节,所以是45=20个字节的地址,case在这段代码的汇编,用了空间换时间的优化思想*。
◼ 情景分析-1-将条件减少一点
switch条件比较少的情况下,没有生成跳转位置表
◼ 编译器并没有做任何优化,if-else跟switch效率差不多
int main(){
int no = 4;
switch (no) {
case 1:
printf("no is 1");
break;
case 2:
printf("no is 2");
break;
default:
printf("other no");
break;
}
}
//assembly
_no$ = -8 ; size = 4
tv64 = -4 ; size = 4
_main PROC
push ebp
mov ebp, esp
sub esp, 8
mov DWORD PTR _no$[ebp], 4
mov eax, DWORD PTR _no$[ebp]
mov DWORD PTR tv64[ebp], eax
cmp DWORD PTR tv64[ebp], 1 //和1比较
je SHORT $LN4@main //je 相等就跳转
cmp DWORD PTR tv64[ebp], 2 //和2比较
je SHORT $LN5@main
jmp SHORT $LN6@main //jmp 无条件跳转到$LN6@main other
$LN4@main:
push OFFSET $SG28199
call _printf
add esp, 4
jmp SHORT $LN1@main
$LN5@main:
push OFFSET $SG28201
call _printf
add esp, 4
jmp SHORT $LN1@main
$LN6@main:
push OFFSET $SG28203
call _printf
add esp, 4
$LN1@main:
xor eax, eax
mov esp, ebp
pop ebp
ret 0
_main ENDP
◼情景分析-2- case的值连续
◼情景分析-3
◼ case的值不连续
// 条件比较多
void test3() {
int no = 8;
switch (no) {
case 1:
printf("no is 1");
break;
case 3:
printf("no is 3");
break;
case 5:
printf("no is 5");
break;
case 6:
printf("no is 6");
break;
default:
printf("other no");
break;
}
int age = 4;
}
$SG28200 DB 'no is 1', 00H
$SG28202 DB 'no is 3', 00H
$SG28204 DB 'no is 5', 00H
$SG28206 DB 'no is 6', 00H
$SG28208 DB 'other no', 00H
unsigned __int64 `__local_stdio_printf_options'::`2'::_OptionsStorage DQ 01H DUP (?) ; `__local_stdio_printf_options'::`2'::_OptionsStorage
_age$ = -12 ; size = 4
_no$ = -8 ; size = 4
tv64 = -4 ; size = 4
_main PROC
push ebp
mov ebp, esp
sub esp, 12 ; 0000000cH
mov DWORD PTR _no$[ebp], 8
mov eax, DWORD PTR _no$[ebp]
mov DWORD PTR tv64[ebp], eax
mov ecx, DWORD PTR tv64[ebp]
sub ecx, 1 //-1
mov DWORD PTR tv64[ebp], ecx
cmp DWORD PTR tv64[ebp], 5
ja SHORT $LN8@main
mov edx, DWORD PTR tv64[ebp]
jmp DWORD PTR $LN10@main[edx*4]
$LN4@main:
push OFFSET $SG28200
call _printf
add esp, 4
jmp SHORT $LN2@main
$LN5@main:
push OFFSET $SG28202
call _printf
add esp, 4
jmp SHORT $LN2@main
$LN6@main:
push OFFSET $SG28204
call _printf
add esp, 4
jmp SHORT $LN2@main
$LN7@main:
push OFFSET $SG28206
call _printf
add esp, 4
jmp SHORT $LN2@main
$LN8@main:
push OFFSET $SG28208
call _printf
add esp, 4
$LN2@main:
mov DWORD PTR _age$[ebp], 4
xor eax, eax
mov esp, ebp
pop ebp
ret 0
npad 2
$LN10@main:
DD $LN4@main //case 1
DD $LN8@main //case 2
DD $LN5@main //case 3
DD $LN8@main //case 4
DD $LN6@main //case 5
DD $LN7@main //case 6
_main ENDP
jmp DWORD PTR $LN10@main[edx*4]
$LN10@main:
DD $LN4@main //case 1
DD $LN8@main //case 2
DD $LN5@main //case 3
DD $LN8@main //case 4
DD $LN6@main //case 5
DD $LN7@main //case 6
跳转表里面加了case2 和case4的地址,填入的other的地址,同样开辟了内存空间存储了跳转表来计算case情况的跳转地址!
◼ case的值差距较大
int main(){
int no = 100;
switch (no) {
case 1:
printf("no is 1");
break;
case 3:
printf("no is 3");
break;
case 8:
printf("no is 8");
break;
case 12:
printf("no is 100");
break;
default:
printf("other no");
break;
}
int age = 4;
}
_age$ = -12 ; size = 4
_no$ = -8 ; size = 4
tv64 = -4 ; size = 4
_main PROC
push ebp
mov ebp, esp
sub esp, 12 ; 0000000cH
mov DWORD PTR _no$[ebp], 4
mov eax, DWORD PTR _no$[ebp]
mov DWORD PTR tv64[ebp], eax
mov ecx, DWORD PTR tv64[ebp]
sub ecx, 1 //ecx = 3
mov DWORD PTR tv64[ebp], ecx //tv64 = 3
cmp DWORD PTR tv64[ebp], 11 ; 0000000bH //3>11 ?
ja SHORT $LN8@main //不大于11
mov edx, DWORD PTR tv64[ebp] //edx = 3
movzx eax, BYTE PTR $LN10@main[edx] //eax = 4
jmp DWORD PTR $LN11@main[eax*4] //跳转到$LN11@main[4] -->$LN8@main 即other
$LN4@main:
push OFFSET $SG28200
call _printf
add esp, 4
jmp SHORT $LN2@main
.......
$LN8@main:
push OFFSET $SG28208 //$SG28208 DB 'other no', 00H
call _printf
add esp, 4
$LN11@main:
DD $LN4@main //0
DD $LN5@main //1
DD $LN6@main //2
DD $LN7@main //3
DD $LN8@main //4
$LN10@main:
DB 0
DB 4
DB 1
DB 4
DB 4
DB 4
DB 4
DB 2
DB 4
DB 4
DB 4
DB 3
_main ENDP
这种case语句,开辟了两张表,一张case语句代码执行地址的表,一共5个地址4*5=20个字节,又开辟了一个12个byte的内存地址表,存储了0-11这些情况需要计算内存地址偏移地址长度的个数
◼ case的值差距非常大
退化为if方式
int main(){
int no = 100;
switch (no) {
case 1:
printf("no is 1");
break;
case 3:
printf("no is 3");
break;
case 8:
printf("no is 8");
break;
case 10000:
printf("no is 100");
break;
default:
printf("other no");
break;
}
}
_main PROC
push ebp
mov ebp, esp
sub esp, 8
mov DWORD PTR _no$[ebp], 100 ; 00000064H
mov eax, DWORD PTR _no$[ebp]
mov DWORD PTR tv64[ebp], eax
cmp DWORD PTR tv64[ebp], 8
jg SHORT $LN10@main //是否大于8
cmp DWORD PTR tv64[ebp], 8
je SHORT $LN6@main //==8
cmp DWORD PTR tv64[ebp], 1
je SHORT $LN4@main //==1
cmp DWORD PTR tv64[ebp], 3
je SHORT $LN5@main //==3
jmp SHORT $LN8@main
$LN10@main:
cmp DWORD PTR tv64[ebp], 10000 ; 00002710H
je SHORT $LN7@main //==10000
jmp SHORT $LN8@main //跳转到other
$LN4@main:
push OFFSET $SG28199
call _printf
add esp, 4
jmp SHORT $LN1@main
$LN5@main:
push OFFSET $SG28201
call _printf
add esp, 4
jmp SHORT $LN1@main
$LN6@main:
push OFFSET $SG28203
call _printf
add esp, 4
jmp SHORT $LN1@main
$LN7@main:
push OFFSET $SG28205
call _printf
add esp, 4
jmp SHORT $LN1@main
$LN8@main:
push OFFSET $SG28207
call _printf
add esp, 4
$LN1@main:
xor eax, eax
mov esp, ebp
pop ebp
ret 0
_main ENDP
总结
◼ 对比if-else,编译器会对switch作一定的优化,提高执行效率
a++和++a
◼ 在C++中,++a是可以被赋值的,a++不可以被赋值
void test6() {
int a = 5;
int b = a++ + 2;
printf("a == %d\n", a);
printf("b == %d\n", b);
}
a == 6
b == 7
_b$ = -8 ; size = 4
_a$ = -4 ; size = 4
_main PROC
push ebp
mov ebp, esp
sub esp, 8
mov DWORD PTR _a$[ebp], 5 //a=5
mov eax, DWORD PTR _a$[ebp] //eax=5
add eax, 2 //eax=7
mov DWORD PTR _b$[ebp], eax //b = 7
mov ecx, DWORD PTR _a$[ebp] //ecx = 5
add ecx, 1 //exc = 6
mov DWORD PTR _a$[ebp], ecx //a= ecx = 6
mov edx, DWORD PTR _a$[ebp]
push edx
push OFFSET $SG28197
call _printf
add esp, 8
mov eax, DWORD PTR _b$[ebp]
push eax
push OFFSET $SG28198
call _printf
add esp, 8
xor eax, eax
mov esp, ebp
pop ebp
ret 0
_main ENDP
int main(){
int a = 5;
int b = ++a + 2;
}
_b$ = -8 ; size = 4
_a$ = -4 ; size = 4
_main PROC
push ebp
mov ebp, esp
sub esp, 8
mov DWORD PTR _a$[ebp], 5 //a=5
mov eax, DWORD PTR _a$[ebp] //eax = a = 5
add eax, 1 //eax = 6
mov DWORD PTR _a$[ebp], eax //a = eax = 6
mov ecx, DWORD PTR _a$[ebp] //exc = eax = 6
add ecx, 2 //ecx = (6+2) = 8
mov DWORD PTR _b$[ebp], ecx //b = ecx = 8
xor eax, eax
mov esp, ebp
pop ebp
ret 0
_main ENDP
a == 6
b == 8
a++ = 7 is not assignable
构造函数
◼ 构造函数(也叫构造器),在对象创建的时候自动调用,一般用于完成对象的初始化工作
◼ 一个广为流传的、很多教程\书籍都推崇的错误结论:
默认情况下,编译器会为每一个类生成空的无参的构造函数
正确理解:==在某些特定的情况下,编译器才会为类生成空的无参的构造函数==,比如
✓ 成员变量在声明的同时进行了初始化
✓ 包含了对象类型的成员,且这个成员有构造函数
✓ 父类有构造函数
✓ ......等等
class Car {
public:
int m_price;
Car() {
printf("Car()----");
}
};
int main(){
Car *car = new Car();
car->m_price = 100;
}
_car$ = -28 ; size = 4
$T2 = -24 ; size = 4
tv75 = -20 ; size = 4
$T3 = -16 ; size = 4
__$EHRec$ = -12 ; size = 12
_main PROC
push ebp
mov ebp, esp
push -1
push __ehhandler$_main
mov eax, DWORD PTR fs:0
push eax
sub esp, 16 ; 00000010H
mov eax, DWORD PTR ___security_cookie
xor eax, ebp
push eax
lea eax, DWORD PTR __$EHRec$[ebp]
mov DWORD PTR fs:0, eax
push 4
call void * operator new(unsigned int) ; operator new
add esp, 4
mov DWORD PTR $T3[ebp], eax
mov DWORD PTR __$EHRec$[ebp+8], 0
cmp DWORD PTR $T3[ebp], 0
je SHORT $LN3@main
mov ecx, DWORD PTR $T3[ebp]
call Car::Car(void) ; Car::Car
mov DWORD PTR tv75[ebp], eax
jmp SHORT $LN4@main
$LN3@main:
mov DWORD PTR tv75[ebp], 0
$LN4@main:
mov eax, DWORD PTR tv75[ebp]
mov DWORD PTR $T2[ebp], eax
mov DWORD PTR __$EHRec$[ebp+8], -1
mov ecx, DWORD PTR $T2[ebp]
mov DWORD PTR _car$[ebp], ecx
mov edx, DWORD PTR _car$[ebp]
mov DWORD PTR [edx], 100 ; 00000064H
xor eax, eax
mov ecx, DWORD PTR __$EHRec$[ebp]
mov DWORD PTR fs:0, ecx
pop ecx
mov esp, ebp
pop ebp
ret 0
__unwindfunclet$_main$0:
push 4
mov eax, DWORD PTR $T3[ebp]
push eax
call void operator delete(void *,unsigned int) ; operator delete
add esp, 8
ret 0
__ehhandler$_main:
mov edx, DWORD PTR [esp+8]
lea eax, DWORD PTR [edx+12]
mov ecx, DWORD PTR [edx-20]
xor ecx, eax
call @__security_check_cookie@4
mov eax, OFFSET __ehfuncinfo$_main
jmp ___CxxFrameHandler3
_main ENDP
call Car::Car(void) ; Car::Car
car类已经定义了默认的构造函数的情况下 new的情况下,主动调用call函数执行默认构造函数,并且汇编生成了car的汇编代码
去掉默认构造函数,没有实现的情况下呢?接着分析情况。
class Car {
public:
int m_price ;
};
int main(){
Car *car = new Car();
printf("car=%d",car->m_price);
car->m_price = 100;
}
从汇编代码编译看到,没有为car类生成任何汇编代码,直接mov DWORD PTR _car$[ebp], eax
给了car变量一个指针内存地址。
_car$ = -12 ; size = 4
tv70 = -8 ; size = 4
$T1 = -4 ; size = 4
_main PROC
push ebp
mov ebp, esp
sub esp, 12 ; 0000000cH
push 4
call void * operator new(unsigned int) ; operator new
add esp, 4
mov DWORD PTR $T1[ebp], eax
cmp DWORD PTR $T1[ebp], 0
je SHORT $LN3@main
xor eax, eax
mov ecx, DWORD PTR $T1[ebp]
mov DWORD PTR [ecx], eax
mov edx, DWORD PTR $T1[ebp]
mov DWORD PTR tv70[ebp], edx
jmp SHORT $LN4@main
$LN3@main:
mov DWORD PTR tv70[ebp], 0
$LN4@main:
mov eax, DWORD PTR tv70[ebp]
mov DWORD PTR _car$[ebp], eax
mov ecx, DWORD PTR _car$[ebp]
mov edx, DWORD PTR [ecx]
push edx
push OFFSET $SG28210
call _printf
add esp, 8
mov eax, DWORD PTR _car$[ebp]
mov DWORD PTR [eax], 100 ; 00000064H
xor eax, eax
mov esp, ebp
pop ebp
ret 0
_main ENDP
成员变量在声明的同时进行了初始化
class Car {
public:
int m_price = 5 ;
};
int main(){
Car *car = new Car();
printf("car=%d",car->m_price);
car->m_price = 100;
}
包含了对象类型的成员,且这个成员有构造函数
class Car {
public:
int m_price;
Car() {
m_price = 5;
}
};
class Person {
Car car;
};
int main(){
Person *p = new Person();
}
我们再来看一下,成员没有构造函数的情况
父类有构造函数
函数的内存布局
EBP:基址指针寄存器,指向栈帧的底部,基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。
ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。
EIP:指令寄存器,存储的是 CPU 下次要执行的指令的地址,ARM 下为 PC,寄存器为 R15。
void test1(int v1, int v2) {
int a = v1 + 1;
int b = v2 + 2;
// test2(3);
}
int main(){
test1(1,2);
}
_b$ = -8 ; size = 4
_a$ = -4 ; size = 4
_v1$ = 8 ; size = 4
_v2$ = 12 ; size = 4
void test1(int,int) PROC ; test1
push ebp
mov ebp, esp
sub esp, 8
mov eax, DWORD PTR _v1$[ebp]
add eax, 1
mov DWORD PTR _a$[ebp], eax
mov ecx, DWORD PTR _v2$[ebp]
add ecx, 2
mov DWORD PTR _b$[ebp], ecx
mov esp, ebp
pop ebp
ret 0
void test1(int,int) ENDP ; test1
_main PROC
push ebp
mov ebp, esp
push 2
push 1
call void test1(int,int) ; test1
add esp, 8
xor eax, eax
pop ebp
ret 0
_main ENDP
- push 2
- push 1
- call void test1(int,int)
- push ebp
- mov ebp, esp
- sub esp, 8
- mov eax, DWORD PTR _v1$[ebp]
eax=1
- add eax, 1 eax=2
- mov DWORD PTR _a$[ebp], eax
- mov ecx, DWORD PTR _v2$[ebp]
- add ecx, 2 ecx=2+2 =4
- mov DWORD PTR _b$[ebp], ecx
- mov esp, ebp
- pop ebp
- ret 0
函数从ret返回后 ESP EBP 和调用前,指向的栈地址完全恢复 EIP回到了调用test函数前的下一条地址,完成函数栈平衡和调用堆栈恢复