网站开发算互联网公司吗/推荐就业的培训机构
三、基础语法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 =20p4 = &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就是使用ageint &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,src6: 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 == 1122Hmov eax,dword ptr [1122H]
1122H地址对应的存储空间取出4字节的数据(假设为4)赋值给eaxeax == 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; #结果过30cout << "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; #结果过30cout << "ref is " << ref << endl; #结果10
我们发现这两个结果是不一样的,因为变为另一种数据类型时,从汇编代码可以看出,产生了一个临时变量,临时产生了一个地址ebp-54h,所以我们指向的是临时变量的地址,所以结果为10
不同编程语言转成的汇编是一样的吗?
Java、C++、OC、swift写代码 --》汇编\机器码(取决于CPU架构)
编译器不同,产生的汇编可能是不同的,如将其生成X86架构或者X64的汇编、机器码
最终生成什么汇编和你最终运行的平台有关,汇编、机器指令是有CPU定好的,CPU支持哪些汇编指令,哪些机器指令由CPU架构决定
CPU决定支持哪些汇编,机器码
Imm 表示立即数,就是直接写出来的值如3等不是变量a等数
m内存,允许立即数赋给内存,在白皮书是找不到,左边内存,右边内存的即mov [],[]不存在
相关文章:

三、基础语法2(30小时精通C++和外挂实战)
三、基础语法2(30小时精通C和外挂实战) B-02内联函数B-04内联函数与宏B-05_constB-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内联函数 …...

gitee设置ssh公钥密码频繁密码验证
gitee中可以创建私有项目,但是在clone或者push都需要输入密码, 比较繁琐。 公钥则可以解决该问题,将私钥放在本地,公钥放在gitee上,当对项目进行操作时带有的私钥会在gitee和公钥进行验证,避免了手动输入密…...

BGP选路之Next Hop
原理概述 当一台BGP路由器中存在多条去往同一目标网络的BGP路由时,BGP协议会对这些BGP路由的属性进行比较,以确定出去往该目标网络的最优BGP路由,然后将该最优BGP路由与去往同一目标网络的其他协议路由进行比较,从而决定是否将该最优BGP路由放进P路由表中…...

牛客14666(优先屏障) + 牛客14847(Masha与老鼠)
文章目录 写在前面14666-优先屏障思路编程 14847-Masha与老鼠思路编程 写在前面 昨天刷的这两道题写了很久,特别是Masha与老鼠这道题,写了都快3个小时,主要还是理解代码逻辑有点难,不过写完之后感觉收获挺大的,给我以…...

Git下载与安装
下载网址:https://git-scm.com/downloads 下载之后开始安装 选择安装路径,next 选择需要安装的组件,这里默认即可,next 选择菜单文件夹,这里默认即可,next 选择默认编辑器,默认推荐的即可&…...

创建vue2/vue3项目
目录 创建一个Vue2项目创建一个Vue3项目 创建一个Vue2项目 ## 安装Vue-Cli : npm install -g vue/cli // Vue CLI 4.x 需要 Node.js v8.9 或更高版本 (推荐 v10 以上)vue --version // 检测版本是否正确## 创建一个项目: vue create hello-world // hel…...

IOS七层模型对应的网络协议和物理设备
以下是网络模型、对应的协议以及对应的物理设备的表格总结: 网络模型层次主要功能对应协议对应物理设备物理层透明的传输比特流,确定机械及电气规范RS-232、V.35、RJ-45、FDDI等中继器、集线器、网线、调制解调器、网卡数据链路层将比特组装成帧和点到点…...

论文复现:Predictive Control of Networked Multiagent Systems via Cloud Computing
Predictive Control of Networked Multiagent Systems via Cloud Computing论文复现 文章目录 Predictive Control of Networked Multiagent Systems via Cloud Computing论文复现论文摘要系统参数初始化系统模型观测器预测过程控制器设计系统的整体框图仿真结果 论文摘要 翻译…...

JSON 文件存储
JSON 全称为: JavaScript Object Notation 也就是 javaScript 对象标记,通过对象和数组的组合来表示数据, 虽然构造简洁,但是结构化程度非常高, 是一种轻量级的数据交换格式 对象和数组 在 JavaScript 语言中&#…...

python——pynput
pynput 是一个 Python 库,用于控制和监听键盘与鼠标输入。它在 Windows、macOS 和 Linux 上都可以工作,为用户提供了一个跨平台的输入事件处理方式。pynput 包含两个主要模块:keyboard 和 mouse,分别用于处理键盘和鼠标事件。 主…...

[用AI日进斗金系列]用码上飞在企微接单开发一个项目管理系统!
今天是【日进斗金】系列的第二期文章。 先给不了解这个系列的朋友们介绍一下,在这个系列的文章中,我们将会在企微的工作台的“需求发布页面”中寻找有软件开发需求的用户 并通过自研的L4级自动化智能软件开发平台「码上飞CodeFlying」让AI生成应用以解…...

《JavaEE篇》--多线程(2)
《JavaEE篇》--多线程(1) 线程安全 线程不安全 我们先来观察一个线程不安全的案例: public class Demo {private static int count 0;public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(() -> {//让count自增5W次…...

防爆智能手机如何助力电气行业保驾护航?
在电气行业的智能化转型浪潮中,防爆智能手机以其强大的数据处理能力、实时通讯功能及高度集成的安全特性,正成为保障电力网络稳定运行、预防安全隐患的得力助手。 防爆智能手机在电气行业中发挥着重要的保驾护航作用,主要体现在以下几个方面&…...

24.7.24数组|那几个课后得做的题
1、对长整形数据进行反转 2、对字符串进行反转 一、题目地址: 1. 实现一个函数atoi,使其能够将字符串转换整数 (Leetcode 8/中等). - 力扣(LeetCode) 2. 颠倒给定的32位无符号整数的二进制位(Leetcode 190/简单&…...

03Spring底层架构核心概念解析
为了感谢罕哥对我工作的帮助,特此记录下学习过程,期待成为和罕哥一样优秀的人 时间:2024.7.13 内容:spring源码课程3学习记录 一、BeanDefinition BeanDefinition表示Bean的定义,BeanDefinition中存在很多属性用来…...

Vue学习---vue 防抖处理函数,是处理什么场景
Vue防抖处理函数是用来处理在快速连续操作中,只执行最后一次操作的情况。 例如,在输入框输入时,我们可能希望只在用户完成输入后进行处理,而不是在每次键入时都处理。(n秒后触发一次) 以下是一个简单的Vue防抖处理函数的例子&am…...

力扣爆刷第166天之TOP100五连刷96-100(单词拆分、回溯、旋转数组)
力扣爆刷第166天之TOP100五连刷96-100(单词拆分、回溯、旋转数组) 文章目录 力扣爆刷第166天之TOP100五连刷96-100(单词拆分、回溯、旋转数组)一、24. 两两交换链表中的节点二、139. 单词拆分三、560. 和为 K 的子数组四、209. 长…...

2024在线PHP加密网站源码
源码介绍 2024在线PHP加密网站源码 更新内容: 1.加强算法强度 2.优化模版UI 加密后的代码示例截图 源码下载 https://download.csdn.net/download/huayula/89568335...

网络驱动移植(RTL8189)
1、把驱动放到内核文件夹中(linux/drivers/net/wireless),对应的驱动可以在网上下载 2、修改该目录下的Kconfig和Makefile文件 3、配置内核(make menuconfig) 配置支持IEEE 802.11,选中8189模块࿰…...

go语言中map学习
在 Go 语言中,map 是一种引用类型,这意味着它有以下特点: 内存结构: map 实际上是一个指向底层数据结构的指针。这个底层数据结构包含键值对的集合。 赋值与传参: 当你给一个变量赋值一个 map 时,或者将 map 作为函数参数传递时,实际上传递的是指针,而不是完整的数据结构副本。…...
【C#】| 与 及其相关例子
按位或(|) 按位或运算符 | 对两个数的每一位进行比较,如果两个数中至少有一个为 1,则结果位为 1;否则,结果位为0。 1010 (10 in decimal) | 1100 (12 in decimal) ------1110 (14 in decimal) 力扣相关…...

【数据结构 | 哈希表】一文了解哈希表(散列表)
😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀 🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C、数据结构、音视频🍭 🤣本文内容🤣&a…...

go创建对象数组
在 Go 语言中,可以使用字面量的方式创建结构体对象数组。以下是一个示例代码,展示了如何使用字面量创建一个结构体对象数组: package mainimport "fmt"// 定义一个结构体 type Person struct {Name stringAge intAddress Address…...

Golang | Leetcode Golang题解之第278题第一个错误的版本
题目: 题解: func firstBadVersion(n int) int {return sort.Search(n, func(version int) bool { return isBadVersion(version) }) }...

自动化网络爬虫:如何它成为提升数据收集效率的终极武器?
摘要 本文深入探讨了自动化网络爬虫技术如何彻底改变数据收集领域的游戏规则,揭示其作为提升工作效率的终极工具的奥秘。通过分析其工作原理、优势及实际应用案例,我们向读者展示了如何利用这一强大工具加速业务决策过程,同时保持数据收集的…...

软件测试---测试需求分析
课程目标 什么是软件测试需求 软件测试需求的必要性 如何对软件测试需求进行分析(重点) 课程补充 灰度测试(基于功能):先发布部分功能,然后看用户的反馈,再去发布另外一部分的功能更新。 A/B测…...

Android11 framework 禁止三方应用通过广播开机自启动-独立方案
之前的文章Android11 framework 禁止三方应用开机自启动记录了我调试Android11应用自启动限制的全过程,但是之前的方案感觉还能再研究,所以有了这一篇文章。 这一篇文章主要探讨Android11上,以广播来进行自启动的应用的限制,极个别…...

Node:解决Error: error:0308010C:digital envelope routines::unsupported的解决方法
问题描述 在使用vuepress搭建博客的时候,运行项目发现报错了,检查了node的版本是18,之前用的是16或14的版本,现在报:Error: error:0308010C:digital envelope routines::unsupported错误。 查找了一些资料࿰…...

spring boot(学习笔记第十四课)
spring boot(学习笔记第十四课) Spring Security的密码加密,基于数据库认证 学习内容: Spring Security的密码加密基于数据库认证 1. Spring Security的密码加密 如果用户的密码保存在数据库中是以明文保存,对于公司的安全将是灾难性的&…...

Android 11 Unable to start/bind service
今天在Android11上发现了一个的问题,如果目标Service的进程没有启动,那么无论是bindService还是startService都没有办法拉起指定的Service。 网上查了很多资料如下: 1.目标Service 设置 android:exported"true" 2.目标Service需要声明自定义权…...