三、基础语法2(30小时精通C++和外挂实战)
- B-02内联函数
- B-04内联函数与宏
- B-05_const
- B-06引用
- B-07引用的本质
- B-08-汇编1-X86-X64汇编
- B-09-汇编2-内联汇编
- B-10-汇编3-MOV指令
- C-02-汇编5-其他常见指令
- C-05-汇编8-反汇编分析
- C-07-const引用、特点
B-02内联函数
内联函数
作用:将函数调用展开成函数体代码,说白了,如果
#include <iostream>
using namespace std;
inline void func(){
cout << "func()" << endl;
cout << "func()" << endl;
cout << "func()" << endl;
}
inline int sum(int v1, int v2){
return v1 + v2;
}
int main(){
func();
func();
func();
int c =sum(10, 20);
cout << c << endl;
getchar();
return 0;
}
如上代码调用func函数,如果没有内联,汇编很简单几句,就是调用,但如果是内联函数,就相当于下方,直接在函数调用展开称为函数体,执行代码很多,机器码也很多
int main(){
cout << "func()" << endl;
cout << "func()" << endl;
cout << "func()" << endl;
cout << "func()" << endl;
cout << "func()" << endl;
cout << "func()" << endl;
cout << "func()" << endl;
cout << "func()" << endl;
cout << "func()" << endl;
int c =sum(10, 20);
cout << c << endl;
getchar();
return 0;
}
从这个角度想,这样是代码体积变大,好像没有什么意义,还不如不内联,直接调用。
//开辟栈空间
void func(){
cout << "func()" << endl;
cout << "func()" << endl;
cout << "func()" << endl;
}
//结束栈空间
int sum(int v1, int v2){
return v1 + v2;
}
还是有意义的,虽然函数调用的话可以减少体积,平时封装函数,就是减少体积、提高复用率,但是函数调用是有代价的,开辟栈空间,我们每调用一次函数,一个函数开始时开辟栈空间,此函数结束时,调用栈空间。
一旦内联函数,就不存在函数调用了,程序运行时,直接执行函数体中的内容,不存在调用函数,也就是不存在函数栈空间的开辟,也不存在回收栈空间,这样其实执行效率变高了,就不会去分配回收内存了,就直接相加了
什么情况下使用内联函数呢?
第一,调用的函数代码体积不是很大,只有几行
第二,函数经常调用,每隔几秒就调用一次
像体积小,且调用次数高的函数,建议将其声明为内联函数(不是内联,经常调用会频繁开辟、回收栈空间这个操作),一旦内联就不存在这种内存操作了,直接将代码拿来使用,可以提高效率
Inline是建议编译器将函数内联,不一定会执行的,如果代码体积太大,编译器优化时就不会将函数内联。
递归函数不会被内联如下
inline void run(){
run();
}
下面我们来窥探一下内联的本质
我们先不内联,正常调用函数可以发现反汇编有call调用,接着我们内联反汇编
注意当我们写入inline进行函数内联时,进入反汇编并没有看到内联的效果,因为这是debug调试模式下的,而内联函数inline是优化时起作用的,所以我们要进入release模式下看结果。
我们先进入release模式下,将inline先去掉没有内联,反汇编看一看,我们运行后发现代码如下
20: int c =sum(10, 20);
21: cout << c << endl;
8B 0D 54 30 26 00 mov ecx,dword ptr ds:[263054h]
68 90 19 26 00 push 261990h
6A 1E push 1Eh
FF 15 34 30 26 00 call dword ptr ds:[263034h]
8B C8 mov ecx,eax
FF 15 5C 30 26 00 call dword ptr ds:[26305Ch]
即便编译器不是内联,也会在release模式下进行优化,直接就将30的值push进去了,最终代码变成上面那样了,直接代码 cout << 30<< endl;连加法都省掉了,我们现在要证明inline的优化作用,而这样编译器直接优化了会看不出来,此时我们需要将其禁止优化
右击项目属性,禁止优化,此时无inline,反汇编看到存在函数调用无优化
20: int c =sum(10, 20);
6A 14 push 14h
6A 0A push 0Ah
E8 DE FF FF FF call sum (09216A0h)
83 C4 08 add esp,8
89 45 FC mov dword ptr [c],eax
这时我们要加入inline,刚刚在debug模式是不行的,此时我们要在release模式禁用优化下看其作用,但是还不行,我们在属性的C++下的优化下,禁用优化,还需要,将内联函数扩展选为任何适用项,此时就能看出了如下
20: int c =sum(10, 20);
B9 0A 00 00 00 mov ecx,0Ah
83 C1 14 add ecx,14h
19:
20: int c =sum(10, 20);
89 4D FC mov dword ptr [c],ecx
就相当于int c = v1 +v2;这个是直接将函数体中的内容拿过来直接执行,没有进行调用
递归函数就没必要加inline最终在汇编中还是调用函数
刚刚我们是在汇编的方式进行的,现在我们将inline去掉另其运行生成exe在release模式下,我们使用IDA这个工具
有func\sum这两个函数
当加入inline时,发现内联的函数没了,只有一个main函数,是优化掉了,将内联的函数体直接运行,无调用,相当于只有一个main函数执行。
B-04内联函数与宏
内联函数与宏都可以减少函数调用的开销
#define add(v1,v2) v1+v2 #定义宏,add这个宏将左边的东西替换成右边的东西
int main(){
int c = add(10, 20); #宏替换变成int c = v1+v1;
cout << c << endl;
getchar();
return 0;
}
像这种做这些数学运算的,建议将其直接转换成函数代码,就不要存在函数调用,两种方式,一种写成宏,一种写成内联函数,
推荐大家使用内联函数,内联函数目前看来还是个函数,看起来是个函数,我们写代码时是有提示的在内联函数里,在写宏的时候没有提示
函数有个特点,传参
如果看别人写C++代码表达式在左边,将右边的值赋给表达式,不要惊讶,这是可以的,但是C语言是不可以的,这个了解一下
B-05_const
Const这个知识点很重要,我们要重视
#include <iostream>
using namespace std;
int main(){
Const int age = 10;
age = 20;
getchar();
return 0;
}
我们只在第一次定义的时候可以赋值,使用const,以后都不能更改这个值,防止别人更改
Const必须在定义的时候使用
如果修饰的是类、结构体(的指针),其成员也不可以更改
在C/C++中有结构体的概念如
#include <iostream>
using namespace std;
struct Date { #定义了一个结构体
int year;
int month;
int day;
};
int main(){
Date d = { 2011, 2, 5 }; #定义一个结构体变量d并依次赋值,在C语言中的定义需要struct Date d = { 2011, 2, 5 };
d.year =2015;
int age = 10;
age = 20;
getchar();
return 0;
}
Const Date d = { 2011, 2, 5 }; 我们在定义是加入const,成员无法更改
Date *p = &d1;//用一个指针指向d1这个结构体,指针类型为date会执行date类型的
//我们可以使用指针来修改所指向的d1的成员值
p->year = 2015;//这是使用指针简介去修改
//结构体本身访问成员用点,指向结构体的指针去访问结构体的成员使用的是->,语法规定
cout << d1.year << endl;
const Date *p = &d1;/不能通过指针去修改d1的东西
#include <iostream>
using namespace std;
struct Date {
int year;
int month;
int day;
};
int main(){
int age = 10;
const int *p1 = &age;
//p2不是常量,*p2是常量
int const *p2 = &age;
//p3是常量,*p3不是常量
int * const p3 = &age;
//p4是常量,*p4是常量
const int * const p4 = &age;
//p5是常量,*p5是常量
int const * const p5 = &age;
*p4 = 20; //age =20
p4 = &height;
*p4 = 40; //height =40;
getchar();
return 0;
}
引用就下节课再讲,引用这东西非常非常的关键
再下节课就将汇编,讲完后,大家对C++汇编代码就可以自己去看了
我们这只是学语法,学完后我们可以把别人的库拿来,
B-06引用
在C语言中,使用指针(pointer)可以间接获取、修改某个变量的值
int age = 10;
int *p = &age;
*p = 20;
cout << *p << endl;
&age 是取一个地址值
p里面存放着age的地址
*p是取p里面存放的地址对应的age中的值
在C++中,使用引用(reference)可以起到跟指针类似的功能
int age = 10;
#定义了一个引用refage,refage相当于是age的别名,使用refage就是使用age
int &refage = age;
refage = 20;
cout << age << endl;
指针可以修改指向,引用不能修改指向
引用开始 定义指向某个变量,以后就将其作为那个变量
可以利用引用初始化另一个引用,相当于某个变量的多个别名
int age = 10;
int &refage = age;
int &refage1 = refage;
int &refage2= refage1;
引用的价值之一是比指针更安全
指针用的好的话很强大,用的不好的话很危险,指针可以随意修改指向,它就有可能指向不该指向的地址,如某些存放重要数据的地址,引用只有定义时指向,之后便不能修改指向,要安全
void swap(int v1,int v2){
int temp = v1;
v1 = v2;
v2 = temp;
}
int main(){
int a = 10;
int b = 20;
swap(a, b);
cout << "a="<<a<<",b="<<b << endl;
getchar();
return 0;
}
如上程序,是无法将ab的值相互调换的,传值是相当于swap(int v1=a,int v2=b)
,将变量a与b传给V1和V2,在函数swap中没有a与b的值,这不是全局变量,所以调用此函数,无法变换值
而使用地址传递时,相当于swap(int *v1=&a,int *v2=&b)
这个地址在全部空间都能找到,可以直接访问地址内的值然后,将其对换
正确写法如下
void swap(int *v1,int *v2){
int temp = *v1;
*v1 = *v2;
*v2 = temp;
}
int main(){
int a = 10;
int b = 20;
swap(&a, &b);
cout << "a="<<a<<",b="<<b << endl;
getchar();
return 0;
}
这是指针,然后发现会比较麻烦,传递是需要些&,取值需要加*很麻烦,但是有了引用,我们就不需要这么做,我们只需要在函数中定义引用即可,如下
void swap(int &v1,int &v2){
int temp = v1;
v1 = v2;
v2 = temp;
}
int main(){
int a = 10;
int b = 20;
swap(a, b);
cout << "a="<<a<<",b="<<b << endl;
getchar();
return 0;
}
这样是可以交换的,相当于swap(int &v1=a,int &v2=b)
,V1引用A,V2引用B,别名可以作为外面的AB的值,使用引用代码量减少,简单,之前指针的的效果也能达到,在函数里面的V1能指向外面的A
函数每次被调用,都要创建一个变量,产生的V1和V2都是全新的,存储空间也是全新的,当调用完后V1和V2的存储空间就被销毁了,再次调用swap,又会创建新的存储空间所以和之前讲 的从一而终并不矛盾
void swap(int &v1,int &v2){
int temp = v1;
v1 = v2;
v2 = temp;
}
int main(){
int a = 10;
int b = 20;
swap(a, b);
cout << "a="<<a<<",b="<<b << endl;
int c = 2;
int d= 3;
swap(c, d);
cout << "c=" << c << ",d=" << d << endl;
getchar();
return 0;
}
如果在同一个V1,不能重新指向
一定要在定义引用的同时给其一个变量,否则会报错
int &v1=a
默认情况下引用变量的类型必须和指向的类型相同
引用所指向的变量与是否为全局变量无关下面也是可以引用的
Int abc = 1;
Int main(){
Int &ref = abc;
}
B-07引用的本质
引用的本质就是指针,只是编译器削弱了它的功能
指针可以改变指向,而引用不能改变,可以间接修改其值
写法上引用比指针简单,而本质上引用就是指针
一个指针多大与CPU架构有关,
int main(){
int age = 10;
// *p就是age的别名
int *p = &age;
*p = 30;
cout << sizeof(p) << endl;//看指针变量p占多少字节,这个是看运行环境决定,在64位环境中是8个字节,X86的32位运行环境是4字节
cout << sizeof(age) << endl;//整型变量一般就是4个字节和我们的CPU架构是没有什么关系的
//ref就是age的别名,定义好refage那一刻,往后只要使用refage就是使用age,它就是age,所以refage占4个字节
int &refage = age;
refage = 40;
cout << age << endl;
cout << sizeof(refage) << endl;//此处实际在此时age是多少个字节,这样测引用占多少个字节是不标准的
cout << sizeof(&refage) << endl;//变成&refage测也是不对的,这个相当于取出refage的地址,看地址占多少字节就是看指针多少自己与CPU架构相关,这样无法证明引用占多少字节
getchar();
return 0;
}
我们现在使用汇编来看其本质,我们打入断点进入调试状态,接着转汇编,这还不是最终的汇编,里面的类似【P】
之类像变量的,是VS为了帮助我们理解生成的,为了看到最终的汇编,我们右击将显示符号名去掉
26: int *p = &age;
8D 45 F4 lea eax,[age]
89 45 E8 mov dword ptr [p],eax
27: *p = 30;
8B 45 E8 mov eax,dword ptr [p]
C7 00 1E 00 00 00 mov dword ptr [eax],1Eh
32: int &refage = age;
8D 45 F4 lea eax,[age]
89 45 DC mov dword ptr [refage],eax
33: refage = 30;
8B 45 DC mov eax,dword ptr [refage]
C7 00 1E 00 00 00 mov dword ptr [eax],1Eh
为了看到最终的汇编,我们右击将显示符号名去掉 ,以去掉之后就发现不一样了
26: int *p = &age;
8D 45 F4 lea eax,[ebp-0Ch]
89 45 E8 mov dword ptr [ebp-18h],eax
27: *p = 30;
8B 45 E8 mov eax,dword ptr [ebp-18h]
27: *p = 30;
C7 00 1E 00 00 00 mov dword ptr [eax],1Eh
32: int &refage = age;
8D 45 F4 lea eax,[ebp-0Ch]
89 45 DC mov dword ptr [ebp-24h],eax
33: refage = 30;
8B 45 DC mov eax,dword ptr [ebp-24h]
C7 00 1E 00 00 00 mov dword ptr [eax],1Eh
左边的地址值是右边机器码的地址值,我们的机器码、代码都是载入内存的,每一个机器码都有地址,每次启动时代码地址值可能会变,只要改变代码,再启动,地址都会变,或者切换运行环境,代码值也会变
每个应用都有起始地址,如果每个应用的起始地址固定,那么函数地址也固定,但是如果起始地址随机,那么函数地址也随机
我们现在转汇编看的不是内存条的真实的物理地址,是虚拟地址,是操作系统给的虚拟地址,它会将虚拟地址映射到真正的内存条上去
我们发现两者一样,引用的本质就是指针
为什么能通过引用间接访问age,因为它的本质就是指针,这样的写法简单了
int &refage = age;
refage = 30;
本质上refage是个指针,存储着age的地址,将30赋值给refage所指向的age中
还是个弱化的指针,这是编译器的特性,编译器层面已经规定它的写法。
B-08-汇编1-X86-X64汇编
引用的本质是指针,我们现在要读懂汇编代码
利用汇编挖掘编程语言本质(免费课,建议看),课后有时间可以看完
汇编语言较多,它是与CPU挂钩的
ARM
汇编是嵌入式,移动设备上的(iPad、iPhone 、Android)
我们现在着重看X64
汇编,X64
汇编根据编译器不同,有两种书写格式
Intel
AT&T
在我们windows平台,在VS中使用的是Intel汇编,如果是MAC平台一般格式为AT&T
汇编语言是不区分大小写的
学习汇编2大知识点
1,汇编指令
2,寄存器
寄存器是在CPU里面的,程序一启动载进内存,A,B,C变量存储在内存,CPU要对变量进行操作,如进行±运算,会借助寄存器,先将内存中的数据放到寄存器,在CPU计算好,再将值放回内存。
先将数据放到寄存器(离CPU更近)来算更快
39: int a = 3;
C7 45 F8 03 00 00 00 mov dword ptr [a],3
40: int b = a + 1;
8B 45 F8 mov eax,dword ptr [a] #将A取出给寄存器
83 C0 01 add eax,1 #寄存器加1
89 45 EC mov dword ptr [b],eax #将寄存器给B
寄存器非常重要,没有寄存器就没法做很多运算
简单的两个变量间的计算就有寄存器的参与
不同的汇编寄存器是不一样的,如X86,X64,ARM等寄存器不同,汇编也不同
我们现在只学我们用的上的寄存器
Registers寄存器
RAX\RBX\RCX\RDX:通用寄存器(在64位这些寄存器很常用)
剩下的是有特殊用途的寄存器RBP后的,有机会讲
而在32位
EAX\EBX\ECX\EDX:通用寄存器
更久一些
16bit
AX\BX\CX\DX:通用寄存器
一个寄存器存储多大,和指针一样看CPU架构是什么
X64的一个寄存器如RAX能存8个字节的数据
虽然我们现在用的是X64汇编,但还是兼容以前的寄存器的
EAX存在于RAX里面这样就兼容了
Mov eax,10 表面上将10给了eax32位的寄存器,实际也对RAX改变
AH H height 高字节
AL L low 低字节
B-09-汇编2-内联汇编
我们写的C++代码,但我们能否将汇编代码嵌入其中,这就是内联汇编
__asm{
}
在这其中就可以写汇编代码了
我们完全可以C++代码和汇编代码混着用
5: int a=10;
C7 45 F4 0A 00 00 00 mov dword ptr [ebp-0Ch],0Ah
6: __asm{
7: mov eax,a (表面上a是个,实际上转的是Ad的地址值)
8B 45 F4 mov eax,dword ptr [ebp-0Ch]
8: }
我们给RAX赋值一样会影响到EAX的值
B-10-汇编3-MOV指令
Mov dest,src
6: int a = 3;
C7 45 F8 03 00 00 00 mov dword ptr [ebp-0CH],3 #ebp-8是变量a的地址
7: int b = a + 1;
8B 45 F8 mov eax,dword ptr [ebp-0CH]
83 C0 01 add eax,1
89 45 EC mov dword ptr [ebp-14h],eax
int main(){
int a = 3;
int b = a + 1;
getchar();
return 0;
}
ebp的值:010FFE58H
a的地址:010FFE4CH == ebp-0CH
a是局部变量,每次使用都会重新分配地址给它,它的地址是变化的,所以最终生成的地址值不能是写死的006FFAB8H,每次调用函数的时候ebp都是新的值,这样就能保证
函数调用分配空间,函数调用完销毁空间
全局变量的地址值是写死的
18: age = 3;
00E4506E C7 05 08 F0 E4 00 03 00 00 00 mov dword ptr ds:[0E4F008h],3
全局变量是程序一启动就会存在的,不变的
如果想要弄懂可以看 利用汇编挖掘程序语言的本质
每一个字节都有自己的内存地址
Mov [1128h],3 将3放到1128h地址所对应的存储空间(这里是使用一个字节存储3)
int a = 3; 这里使用4个字节存储3
一般我们把3放到某个内存空间时都要指定一下单位
Ptr固定写法,指定空间大小 Word表示2个字节
mov word ptr [1128h],3 这里3通过两个字节存储
只知道一个地址值就一个字节,如果给定一个地址 值,一般来说是向高地址吞并自己
dword为4个字节
mov eax,dword ptr [1128h] 从1128h内存地址开始取出4个字节(1128h、1129h、112Ah、112Bh共4个字节的内容)传给eax
凡是看到【】里面放的都是地址值
凡是看到call 就是调用函数
17: test();
E8 B1 C3 FF FF call 00941424
18: func();
E8 B1 C3 FF FF call 00941429
一个字节是8位,用四个字节存储3的话怎么存
4个字节存储3
16进制
00 00 00 03H
2进制
000000000 000000000 000000000 000000011
CPU大小端模式,我们接触的大部分是小端模式(高高低低,高字节放高地址,低字节放低地址)
比较低字节的东西放到低地址的地方
int a = 10;
我们调试,在下面监视窗口,名称输入&a可以看到a的地址值0x00EFF7B0
我们在调试-窗口-可以找到内存,我们选一个,可以窥探到内存,而且精细到每一个字节
我们在内存中搜索0x00EFF7B0
,结果如下
0x00EFF7B0 0a 00 00 00 cc cc cc cc 08 f8 ef 00 ....????.??.
0x00EFF7BC f9 5b b4 00 01 00 00 00 20 64 12 01 ?[?..... d..
0x00EFF7C8 20 74 12 01 ca cd cc 97 71 12 b4 00 t..????q.?.
0x00EFF7D4 71 12 b4 00 00 20 d5 00 00 00 00 00 q.?.. ?.....
0x00EFF7E0 00 00 00 00 00 00 00 00 00 00 f0 00 ..........?.
我们可以让他们每一列显示一个字节
0x00EFF7B0 0a .
0x00EFF7B1 00 .
0x00EFF7B2 00 .
0x00EFF7B3 00 .
0x00EFF7B4 cc ?
0x00EFF7B5 cc ?
0x00EFF7B6 cc ?
0x00EFF7B7 cc ?
0x00EFF7B8 08 .
0x00EFF7B9 f8 ?
0x00EFF7BA ef ?
0x00EFF7BB 00 .
0x00EFF7BC f9 ?
0x00EFF7BD 5b [
0x00EFF7BE b4 ?
变量a占了4个字节,如上,又是小端模式,所以读数从高像低读,00 00 00 0a 就是10
我们平常编程是不关心大小端模式的
一个变量的地址值,是它所有字节地址中的最小值
寄存器是独立存在的相对于内存,相互独立存在的
汇编;
1,用在性能极致优化
2,外挂
3,软件破解
4,嵌入式开发(一些操作某个硬件的代码只能用汇编)
我们学汇编只是为了弄懂高级语言的的底层是干什么了
C-02-汇编5-其他常见指令
Lea dest,[地址值]
Load effect address(装载有效的地址值,将地址值装载进来)
Lea eax,[1122H]
直接将地址值赋值给eax
相当于 eax == 1122H
mov eax,dword ptr [1122H]
1122H地址对应的存储空间取出4字节的数据(假设为4)赋值给eax
eax == 4
//int a = 10;
//ebp-8是变量a的地址
00B0784E mov dword ptr [ebp-8],0Ah
//int b = 5;
//ebp-14h是变量b的地址
00B07855 mov dword ptr [ebp-14h],5
//eax == a == 10
00B0785C mov eax,dword ptr [ebp-8]
//cmp是compare的简称,比较
//比较eax和b的值是否相等(比较后会有结果,结果会影响到下面jne)
00B0785F cmp eax,dword ptr [ebp-14h]
//jne:jump not equal,比较结果不相等才跳转
00B07862 jne 00B0787D
24: printf("1111");
00B07864 mov esi,esp
00B07866 push 0B0CC70h
00B0786B call dword ptr ds:[00B10194h]
00B07871 add esp,4
00B07874 cmp esi,esp
00B07876 call 00B012D5
25: }
26: else
00B0787B jmp 00B07894
27: {
28: printf("2222");
00B0787D mov esi,esp
00B0787F push 0B0CCB8h
00B07884 call dword ptr ds:[00B10194h]
00B0788A add esp,4
00B0788D cmp esi,esp
00B0788F call 00B012D5
38: getchar();
00B07894 mov esi,esp
00B07896 call dword ptr ds:[00B1017Ch]
00B0789C cmp esi,esp
00B0789E call 00B012D5
39: return 0;
00B078A3 xor eax,eax
40: }
直接看汇编是不容易看出来的,在VS中我们可以显示符号名,会在函数地址的左边显示函数名,对比着看
汇编金手指
权威参考:Intel白皮书
函数的返回值一般放到EAX
函数调用过程中ESP,EBP牵扯到栈
Call之前的push是传参
跳转指令很多,只需掌握使用的即可,不懂的可以在网上查
C-05-汇编8-反汇编分析
21: int a = 1;
003E784E mov dword ptr [a],1
22: int b = 2;
003E7855 mov dword ptr [b],2
23: int c = a + b;
003E785C mov eax,dword ptr [a]
003E785F add eax,dword ptr [b]
003E7862 mov dword ptr [c],eax
为什么要借助寄存器EAX中转一下,为什么不能内存加内存再赋给内存,不行,这是有CPU架构指令的,CPU架构决定有些操作只能针对寄存器,不能直接对内存进行操作
dword ptr [b] 从b内存地址对应的存储空间取值
Mov dword ptr [b] ,dword ptr [a] 此操作错误,mov指令不支持此操作,不能内存到内存
Intel白皮书已经规定了,没有这样的操作
优化分不同程度,不同编译器有不同优化,有的没有用途的代码在汇编中直接就会消失
指针变量和句部变量是一样的,都在内存中,都有内存地址,
//int age = 3;
00DA7CA8 mov dword ptr [ebp-0Ch],3
//eax == ebp-0Ch,存放着age的地址值
00DA7CAF lea eax,[ebp-0Ch]
//ebp-18h是指针变量p的地址值
//将age的地址值存放到指针变量p所在的存储空间
//int *p = &age;
00DA7CB2 mov dword ptr [ebp-18h],eax
//*p = 5;
//将age的地址值存放到eax
00DA7CB5 mov eax,dword ptr [ebp-18h]
//age = 5;
00DA7CB8 mov dword ptr [eax],5
上面两个通过指针简介修改age的值
Mov是带单位的,取数据是取多少个字节
Lea不要取数据,所以地址值左边不用写单位的如dword
Mov eax,ebp-0Ch
错误,mov不支持运算,但在中括号中可以进行简单的地址运算,所以要使用lea,不使用mov将地址传给某个人
如果要改的话得
Sub ebp,0Ch
Mov eax,ebp
但这样ebp的值就改变了,使用lea的话ebp的值不会发生改变,也能运算
只要将地址给某个就lea,mov是不可以的,就不深究了
引用的汇编代码和指针是一样的,引用的本质就是汇编。
00DA7CAF lea eax,[ebp-0Ch]
00DA7CB2 mov dword ptr [ebp-18h],eax
我们以后看到上面两段代码可以知道
1,ebp-18h是指针的地址值,ebp-0Ch是另一个变量的地址值
C-07-const引用、特点
常引用
引用被const修饰后,就变成了长引用,就不能再被修改了可以访问但不能改
int & const ref = age;
ref = 30;
为什么,这里ref可以被修改,这个表面上看确实不太容易,但按照指针的方式看的话,如下
int * const p = &age;
p=30; 此处不能被修改,const修饰的是右边,修饰的是指针变量(里面存储的是地址),所以指针变量不能被修改,而指针所指向的存储空间时可以修改的
*p=30; 但这可以修改,指针所指向的存储空间时可以修改的,*p就是age
相对于引用来说,ref就是age,与指针相对比就可以看出了
int age = 10;
int height = 120;
//指向变量P1不能修改指向,可以利用指向变量P1间接修改所指向的变量
int * const p1 = &age;
//p1 =&height;
*p1 = 30;
//指向变量P2可以修改指向,不可以利用指向变量P2间接修改所指向的变量
int const *p2 = &age;
p2 = &height;
//*p2=30;
注意:引用是有限制的,它本身不能修改指向
引用的本质就是指针,所以两者写法一样时(两者const所在位置相同时),看指针,指针什么行为,它就什么行为
//ref1不能修改指向,但是可以通过ref1间接修改所指向的变量
int & const ref1 = age; //注意此处引用本身不能修改指向,故此处相当于int &ref1 = age;
ref1 = 30;
//ref2不能修改指向,此处不可以通过ref1间接修改所指向的变量
int const &ref2 = age;
//ref2 = 40; 此处报错,无法修改
Const引用特点,可以指向临时数据
作为函数参数时,可以接受const和非const实参
int sum(const int &v1, const int &v2){
return v1 + v2;
}
int main(){
//非cost实参
int a = 10;
int b = 20;
sum(a,b);
//const实参
const int c = 10;
const int d = 20;
sum(c, d);
很多语法不是很懂没关系,到时候看汇编就能验证了
当常引用指向不同类型的数据时,会产生临时变量,即引用指向的,并不是初始化时的那个变量
此处为int类型本身的
int age = 10;
const int &ref = age;
age = 30;
00FC5F34 mov dword ptr [ebp-3Ch],0Ah
00FC5F3B lea eax,[ebp-3Ch]
00FC5F3E mov dword ptr [ebp-48h],eax
00FC5F41 mov dword ptr [ebp-3Ch],1Eh
cout <<"age is " << age <<endl; #结果过30
cout << "ref is " << ref << endl; #结果30
当const为另一种数据类型long时
int age = 10;
const long &ref = age;
age = 30;
00515F34 mov dword ptr [ebp-3Ch],0Ah
00515F3B mov eax,dword ptr [ebp-3Ch]
00515F3E mov dword ptr [ebp-54h],eax
00515F41 lea ecx,[ebp-54h]
00515F44 mov dword ptr [ebp-48h],ecx
00515F47 mov dword ptr [ebp-3Ch],1Eh
cout <<"age is " << age <<endl; #结果过30
cout << "ref is " << ref << endl; #结果10
我们发现这两个结果是不一样的,因为变为另一种数据类型时,从汇编代码可以看出,产生了一个临时变量,临时产生了一个地址ebp-54h,所以我们指向的是临时变量的地址,所以结果为10
不同编程语言转成的汇编是一样的吗?
Java、C++、OC、swift写代码 --》汇编\机器码(取决于CPU架构)
编译器不同,产生的汇编可能是不同的,如将其生成X86架构或者X64的汇编、机器码
最终生成什么汇编和你最终运行的平台有关,汇编、机器指令是有CPU定好的,CPU支持哪些汇编指令,哪些机器指令由CPU架构决定
CPU决定支持哪些汇编,机器码
Imm 表示立即数,就是直接写出来的值如3等不是变量a等数
m内存,允许立即数赋给内存,在白皮书是找不到,左边内存,右边内存的即mov [],[]不存在