# 汇编的用途
- 掌握编程语言,代码的本质
- 破解
- ...
# 汇编
汇编语言与机器语言
一一对应
,每一条机器语言指令都有与之对应
的汇编指令
高级语言可以通过编译得到汇编语言 / 机器语言,但汇编语言/机器语言
几乎不
可能还原成高级语言
Visual studio 在调试下右键转汇编
9: int arrray[] = { 1,2,3 }; //C++ 语言 | |
//汇编- 地址 机器指令 汇编代码 | |
00007FF7BA87191C C7 45 08 01 00 00 00 mov dword ptr [arrray],1 | |
00007FF7BA871923 C7 45 0C 02 00 00 00 mov dword ptr [rbp+0Ch],2 | |
00007FF7BA87192A C7 45 10 03 00 00 00 mov dword ptr [rbp+10h],3 |
- 编译型语言 (不依赖虚拟机)
C\C++\OC\Swift
- 脚本语言
Python\JS\PHP
- 编译型语言 (依赖虚拟机)
Java\Ruby
# Visual studio 2022 常用快捷键
# 为什么 C++ 支持函数重载
采用
name mangling
或者name decoration
技术
# 默认参数
- 默认参数必须从右边开始
- 如果同时有声明,实现,默认参数只能放在函数声明中
# 指针存储函数名
void func(int v1,void(*p)(int)) // 函数指针 | |
{ | |
p(v1); | |
} | |
void test(int a) | |
{ | |
cout << "test(int) - "<< a <<endl; | |
} | |
int main(void) | |
{ | |
void(*p)(int) = test; // 指针存储函数名 | |
p(10); | |
func(20,test); | |
} |
# extern "C"
被 extern "C" 修饰的代码会按照
C
语言的方式去编译,用在 C 和 C++ 混合开发
#include <iostream> | |
using namespace std; | |
extern "C" void func() // 使用 C 编译函数重载 | |
{ | |
} | |
extern "C" void func(int v) | |
{ | |
} | |
// 或者 | |
extern "C" // 使用大括号 C 编译内部 | |
{ | |
void func() | |
{ | |
} | |
void func(int v) | |
{ | |
} | |
} | |
int main() | |
{ | |
return 0; | |
} | |
//“func”: 无法重载具有外部 "C" 链接的 |
如果函数同时有声明和实现,要让函数声明被
extern "C"
修饰,函数实现可以不修饰 (声明和实现都放,或者放声明处)
# cpp
使用 C 语言开源库
//math.c 文件 | |
int sum(int v1, int v2) | |
{ | |
return v1 + v2; | |
} | |
int delta(int v1, int v2) | |
{ | |
return v1 - v2; | |
} |
#include <iostream> | |
using namespace std; | |
// 或者 | |
extern "C" // 使用 c 来编译 c 函数 | |
{ | |
int sum(int v1, int v2); | |
int delta(int v1, int v2); | |
} | |
int main() | |
{ | |
cout << sum(10, 20) << endl; | |
cout << delta(30, 20) << endl; | |
return 0; | |
} | |
/* | |
输出 | |
30 | |
10 | |
*/ |
# C
文件写入头文件
//math.h | |
#pragma once | |
int sum(int v1, int v2) | |
{ | |
return v1 + v2; | |
} | |
int delta(int v1, int v2) | |
{ | |
return v1 - v2; | |
} |
#include <iostream> | |
using namespace std; | |
// 或者 | |
extern "C" // 使用 c 来编译头文件 | |
{ | |
#include "math.h" | |
} | |
int main() | |
{ | |
cout << sum(10, 20) << endl; | |
cout << delta(30, 20) << endl; | |
return 0; | |
} | |
/* | |
输出 | |
30 | |
10 | |
*/ |
# 宏定义
- C 默认会定义宏
#define _cplusplus
, 将此应用到都文件中判断是否为 C 环境来决定是否用 C 编译
#pragma once | |
#ifdef _cplusplus | |
extern "C" | |
{ | |
#endif //_cplusplus | |
int sum(int v1, int v2) | |
{ | |
return v1 + v2; | |
} | |
int delta(int v1, int v2) | |
{ | |
return v1 - v2; | |
} | |
#ifdef _cplusplus | |
} | |
#endif //_cplusplus |
# #pragma once
- 我们经常使用
#ifndef,#define,#endif
来防止头文件的内容被重复包含 #pragma once
可以防止整个文件的内容被重复包含
# 内联函数与宏
- 内联函数和宏,都可以减少函数调用的开销
- 对比宏,内联函数多了语法检测和函数特性
# C++ 有些表达式可以被赋值
int a = 1; | |
int b = 2; | |
// 赋值给了 a | |
(a = b) = 3; | |
// 赋值给了 b | |
(a < b ? a : b) = 4; |
# const
//const 的含义,const 修饰的是右边的内容 | |
int age = 10; | |
const int *p1 = &age; | |
// 修饰 * p1 是常量,而 p1 地址可以改 | |
int const *p2 = &age; // 同 p1,const 可以和类型互换位置 | |
int * const p3 = &age; | |
//const 修饰右边的内容 p3 是地址常量,不变,而 * p3 可以修改 | |
const int * const p4 = &age; | |
int const * const p5 = &age; | |
//p4 和 p5 等价,地址和 * p4,*p5 都是常量不能变 |
# 引用
- 引用的本质就是弱化了的指针
- 一个引用占用一个指针的大小
- 在 C 语言中,使用指针 (Pointer) 可以间接获取,修改某个变量的值
- 在 C++ 中,使用引用 (Reference) 可以起到指针类似的功能
注意: - 引用相当于变量的别名 (基本数据类型,枚举,结构体,类,数组等,都可以有引用)
- 对引用做计算,就是对引用所指向的变量做计算
- 在定义的事后就必须初始化,一旦指向了某个变量,就不可以再改变,"从一而终"
- 可以利用引用初始化另一个引用,相当于某个变量的多个别名
- 不存在 (引用的引用,指向引用的指针,引用数组)
- 引用存在的价值:比指针更安全,函数返回值可以被赋值
- const 必须卸载 & 符号的左边,才能算常引用
# const
引用的特点
- 可以指向临时数据 (常量,表达式,函数返回值等)
- 可以指向不同类型的数据
- 作为函数参数时 (此规则也适用于
const指针
)
- 当常引用指向了
不同类型
的数据时,会产生临时变量
,即引用指向的并不是初始化时的那个变量
int age = 10; | |
const long& aAge = age; | |
age = 30; |
00007FF71556184D C7 45 04 0A 00 00 00 mov dword ptr [rbp+4],0Ah | |
00007FF715561854 8B 45 04 mov eax,dword ptr [rbp+4] | |
00007FF715561857 89 45 44 mov dword ptr [rbp+44h],eax | |
00007FF71556185A 48 8D 45 44 lea rax,[rbp+44h] | |
00007FF71556185E 48 89 45 28 mov qword ptr [rbp+28h],rax | |
00007FF715561862 C7 45 04 1E 00 00 00 mov dword ptr [rbp+4],1Eh |
#include <iostream> | |
#include "math.h" | |
using namespace std; | |
/* | |
两者引用:机器码相同,汇编相同 | |
*/ | |
int main() | |
{ | |
int age = 22; | |
int* p = &age; | |
*p = 25; | |
cout << age << endl; | |
int& ref = age; | |
ref = 30; | |
cout << age << endl; | |
return 0; | |
} |
9: int& ref = age; | |
00007FF759091914 48 8D 45 04 lea rax,[rbp+4] | |
00007FF759091918 48 89 45 28 mov qword ptr [rbp+28h],rax | |
10: ref = 30; | |
00007FF75909191C 48 8B 45 28 mov rax,qword ptr [rbp+28h] | |
00007FF759091920 C7 00 1E 00 00 00 mov dword ptr [rax],1Eh | |
11: int* p = &age; | |
00007FF6CD505214 48 8D 45 04 lea rax,[rbp+4] | |
00007FF6CD505218 48 89 45 28 mov qword ptr [rbp+28h],rax | |
12: *p = 25; | |
00007FF6CD50521C 48 8B 45 28 mov rax,qword ptr [rbp+28h] | |
00007FF6CD505220 C7 00 19 00 00 00 mov dword ptr [rax],19h |
# 常引用数组
// 数组名 arr 起始就是数组的地址,也是数组首元素的地址 | |
// 数组名 arr 可以看做是指向数组首元素的指针 (int *) | |
int arr[] = {1,2,3}; | |
// int (&ref)[3] = arr; | |
int * const &ref = arr; // 常量要用常引用 |
# 汇编语言
# 汇编语言的种类
8086
汇编 (16bit
)x86
汇编(32bit
)x64汇编(64bit)
- ARM 回避那 (嵌入式,移动设备)
- ...
x64
汇编根据编译器不同,两种书写格式
Intel
:
AT&T
# AT&T 汇编 VS Intel 汇编
# 寄存器与内存
假设内存中有块红色内存空间的值是 3,现在想把它的值加 1,并将结果存储到蓝色内存空间
cpu
首先会将红色内存空间的值放到EAX
寄存器中:mov eax
, 红色内存空间- 然后让
eax
寄存器与1
相加:add eax,1
- 最后将值赋值给内存空间:
mov
蓝色内存空间,eax
int a = 5; | |
a = a + 1; |
10: int a = 5; | |
00007FF6E7DA520D C7 45 04 05 00 00 00 mov dword ptr [rbp+4],5 | |
11: a = a + 1; | |
00007FF6E7DA5214 8B 45 04 mov eax,dword ptr [rbp+4] | |
00007FF6E7DA5217 FF C0 inc eax | |
00007FF6E7DA5219 89 45 04 mov dword ptr [rbp+4],eax |
# x64 寄存器 Register
RAX(包含EAX)\REX\RCX\RDX
:通用寄存器32bit:EAX\EBX\ECX\EDX
:通用寄存器16bit:AX\BX\CX\DX
: 通用寄存器x64
一个寄存器8
个字节R
开头的寄存器是64bit
的,占8
字节E
开头的寄存器是32bit
的,占4
个字节
# C++ 内联汇编
#include <iostream> | |
using namespace std; | |
int main() | |
{ | |
int a = 10; | |
// 双下划,C++ 嵌入汇编执行 | |
__asm | |
{ | |
mov eax, 10 | |
} | |
} |
# x86
汇编重要指令
mov
dest,src
将 src 的内容赋值给 dest,类似于 dest = src
[地址值]
- 中括号 [] 里面放的都是内存地址
* `word`是2字节,`dword`是4字节(double word),`qword`是八字节(quad word)
call
函数地址
调用函数,并不是真实地址
CPU 大小端模式,大部分都是小端模式
小端模式:高高低低,高字节放高地址,低字节放低地址
一个变量的地址值,是它所在字节地址中的最小值
lea
dest
,[地址值]
将地址赋值给
dest
,类似dest = 地址值
ret
函数返回
xor
op1
,op2
将
op1
和op2``异或
的结果赋值给op1
, 类似于op1
=op1
^op2
add
op1
,op2
类似于
op1 = op1 + op2
sub
op1
,op2
类似于
op1
=op1
-op2
inc
op
自增,类似于 op = op + 1
dec
op
自减,类似于 op = op - 1
jmp
内存地址- 跳转到某个内存地址去执行代码
- j 开头的一般都是跳转,大多是带条件的跳转,一般跟
test
,cmp
等指令配合使用
参考权威:Intel 白皮书[1]
cmp
eax
, b
cmp
是 ``compare的简称,比较
eax和
b` 的值是否相等
# 跳转指令表格
JE, JZ | 结果为零则跳转 (相等时跳转) | ZF=1 | ||
---|---|---|---|---|
equal | zero | |||
JNE, JNZ | 结果不为零则跳转 (不相等时跳转) | ZF=0 | ||
not equal | not zero | |||
JS | 结果为负则跳转 | SF=1 | ||
sign (有符号 \ 有负号) | ||||
JNS | 结果为非负则跳转 | SF=0 | ||
not sign (无符号 \ 无负号) | ||||
JP, JPE | 结果中 1 的个数为偶数则跳转 | PF=1 | ||
parity even | ||||
JNP, JPO | 结果中 1 的个数为偶数则跳转 | PF=0 | ||
parity odd | ||||
JO | 结果溢出了则跳转 | OF=1 | ||
overflow | ||||
JNO | 结果没有溢出则跳转 | OF=0 | ||
not overflow | ||||
JB, JNAE | 小于则跳转 (无符号数) | CF=1 | ||
below | not above equal | < | ||
JNB, JAE | 大于等于则跳转 (无符号数) | CF=0 | ||
not below | above equal | >= | ||
JBE, JNA | 小于等于则跳转 (无符号数) | CF=1 or ZF=1 | ||
below equal | not above | <= | ||
JNBE, JA | 大于则跳转 (无符号数) | CF=0 and ZF=0 | ||
not below equal | above | > | ||
JL, JNGE | 小于则跳转 (有符号数) | SF≠ OF | ||
little | not great equal | < | ||
JNL, JGE | 大于等于则跳转 (有符号数) | SF=OF | ||
not little | great equal | >= | ||
JLE, JNG | 小于等于则跳转 (有符号数) | ZF=1 or SF≠ OF | ||
little equal | not great | <= | ||
JNLE, JG | 大于则跳转 (有符号数) | ZF=0 and SF=OF | ||
not little equal | great | > |
# 汇编符号说明
# 参考的 C++ 变量名规范
- 全局变量:
g_
- 成员变量:
m_
- 静态变量:
s_
- 常量:
c_
# 类补充
struct
默认public
class
默认private
# 如何利用指针间接访问所指向量的成员变量
- 从指针中取出对象的地址
- 利用对象的地址
+
成员变量的偏移量计算出成员变量的地址 - 根据成员变量的地址访问成员变量的存储空间
- 类函数默认会被赋值为
ccccccc
, cc -> int3
: 起到断点的作用- 中断:interrupt
# 内存空间的布局
每个应用都有自己独立的内存空间
- 栈空间
每调用一个函数就会给它分配一段连续的栈空间,等函数调用完毕后会自动回收这段栈空间
自动分配和回收
- 堆空间
需要主动去申请和释放,在程序运行过程,为了能够自由控制内存的声明周期,大小,会经常使用堆空间的内存
- 代码区 (段)
用于存放代码
- 全局区 (数据段)
用于存放全局变量等
# 对初始内存初始化
memset
函数是将较大的数据结构 (比如对象,数组等) 内存清零的比较快的方法
int *p = (int *)malloc(sizeof(int)*10); | |
//*p=0;// 并不能全部清零,仅前四个字节 | |
memset(p,0,40);// 从 p 地址开始,连续个字节,都设置为 0 |
Person person; | |
person.m_id = 1; | |
person.m_age = 20; | |
person.m_height = 180; | |
memset(&person,0,sizeof(person)); // 赋值 person 为零 |
# new
的初始化
int *p1 = new int; // 未被初始化 | |
int *p2 = new int(); // 被初始化为 0 | |
int *p3 = new int(5); // 被初始化为 5 | |
int *p4 = new int[3]; // 数组元素未被初始化 | |
int *p5 = new int[3](); //3 个数组元素都被初始化为 0 | |
int *p6 = new int[3]{}; //3 个数组元素都被初始化为 0 | |
int *p7 = new int[3]{ 5 } // 数组首元素被初始化为 5,其它元素被初始化为零 |
# malloc
不会调用类的构造 & 析构函数
#include <iostream> | |
using namespace std; | |
struct Person | |
{ | |
int m_age; | |
Person() | |
{ | |
cout << "Person()" << endl; | |
} | |
~Person() | |
{ | |
cout << "释放Person()" << endl; | |
} | |
}; | |
int main(void) | |
{ | |
Person person; // 会调用默认构造函数 | |
Person* p = new Person; // 申请空间也会调用默认构造函数 | |
delete p; | |
//malloc 并不调用默认构造函数, | |
Person* q = (Person*)malloc(sizeof(Person)); | |
free(q); // 也不会调用析构函数 | |
return 0; | |
} | |
/* | |
Person () | |
Person () | |
释放 Person () | |
释放 Person () | |
*/ |
# 成员变量的初始化
- 全局区默认初始化为 0
- 如果自定义了构造函数,除了全局区,其他内存空间的成员变量默认都不会被初始化,需要开发人员手动初始化。
#include <iostream> | |
using namespace std; | |
struct Person | |
{ | |
int m_age; | |
}; | |
Person g_person; // 全局变量初始化为 0 | |
int g_age; // 全局变量默认初始化为 0 | |
int main(void) | |
{ | |
//Person person; // 堆空间:没有初始化成员变量 | |
// 堆空间:没有初始化成员变量 | |
Person* p0 = new Person; | |
// 堆空间:成员变量初始化为 0,// 如果有构造函数则不进行初始化 | |
Person* p1 = new Person(); | |
cout << g_age << endl; | |
cout << g_person.m_age << endl; | |
//cout << person.m_age << endl; | |
cout << p0->m_age << endl; | |
cout << p1->m_age << endl; | |
return 0; | |
} | |
/* 输出: | |
0 | |
0 | |
-842150451 | |
0 | |
*/ |
# 构造函数的相互调用
struct Person | |
{ | |
int m_age; | |
int m_height; | |
Person() :Person(0,0) | |
{ | |
//Person (0,0)// 不可,此命令汇编申请临时对象,而非调用 | |
} | |
Person(int age,int height):m_age(age),m_height(height) | |
{ | |
} | |
} |
# call 汇编机械码
- call 汇编机械码
E8
直接调用,写死 - call 汇编机械码
FF
间接调用,可变
# 调用多态性虚表的汇编过程
//调用run | |
Animal *cat = new Cat(); | |
cat->run(); | |
//ebp-8是指针变量cat的地址 | |
//eax存储的是cat对象的地址 | |
move eax,dword ptr [ebp-8] | |
//取出cat对象的最前面4个字节(虚表地址)给edx | |
mov edx, dword ptr [eax] | |
//跳出虚表的最前面4个字节,取出4个字节赋值给eax | |
//取出cat::run的函数调用地址给eax | |
mov eax ,dword ptr [edx+4] | |
//call cat::run | |
call eax |
# 单例模式 (简)
设计模式的一种,保证某个类永远只创建一个对象
- 构造函数私有化
- 定义一个私有的 static 成员变量指向唯一的那个单例对象
- 提供一个公共的访问单例对象的接口
/* 安全位置小火箭 */ | |
class Rocket | |
{ | |
private: | |
Rocket() | |
{ | |
} | |
~Rocket() | |
{ | |
} | |
static Rocket *ms_rocket; | |
public: | |
static Rocket *sharedRocket() | |
{ | |
// 这里要考虑多线程的安全 | |
if(ms_recket == NULL) | |
{ | |
ms_rocket = new Rocket(); | |
} | |
return ms_rocket; | |
} | |
static void deleteRocket() | |
{ | |
if(ms_rocket != NULL) | |
{ | |
delete ms_rocket; | |
ms_rocket = NULL; | |
} | |
} | |
} |
# const 成员
- const 成员:被 const 修饰的成员变量,非静态成员函数
- const 成员变量
- 必须初始化 (类内部初始化) 可以在声明的时候直接初始化赋值
- 非 static 的 const 成员变量还可以再初始化列表中初始化
- const 成员函数 (非静态)
- const 关键字写在参数列表后面,函数的声明和实现必须带 const
- 内部不能修改非 static 成员函数
- 内部只能调用 const 成员函数,static 成员函数
- 非 const 成员函数可以调用 const 成员函数
- const 成员函数和非 const 成员函数构成重载
- 非 const 对象 (指针) 优先调用非 const 成员函数
- const 对象 (指针) 只能调用 const 成员函数,static 成员函数
# 拷贝函数
- 利用已经存在的 car3 对象创建一个 car3 新对象
- car4 初始化会调用拷贝构造函数
class Car | |
{ | |
public: | |
int m_price; | |
int m_length; | |
car(int price = 0, int length = 0):m_price(price),m_length(length) | |
{ | |
cout<< "Car(int price = 0,int length = 0)"<<endl; | |
} | |
// 拷贝构造函数 (会默认生成浅拷贝构造函数) | |
Car(const Car &car):m_price(car.m_price),m_length(car.m_length) | |
{ | |
cout <<"Car(const Car &car)"<<endl; | |
// m_price = car.m_price; | |
// m_length = car.m_length; | |
} | |
void display() | |
{ | |
cout<< "price = " << m_price << " , length="<<m_length << endl; | |
} | |
} | |
Car car3(100,5); | |
Car car4(car3); | |
car4.display(); |
浅拷贝 (shallow copy): 指针类型的变量只会拷贝地址值
深拷贝 (deep copy): 将指针指向的内容拷贝到新的存储空间
class Car | |
{ | |
int m_price; | |
char *m_name; | |
void copy(const char*name = NULL) | |
{ | |
if(name == NULL) | |
return ; | |
} | |
// 申请新的存储空间 | |
m_name = new char[strlen(name)+1] {}; | |
strcpy(m_name,name); | |
} |
# 浅,深拷贝
- 编译器默认的提供的拷贝是
浅拷贝
(shallow copy) - 将一个对象中所有成员变量的值拷贝到另一个对象
- 如果某个成员变量是个指针,只会拷贝指针中存储的地址值,并不会拷贝指针指向的内存空间
- 可能会导致堆空间多次 free 的问题
- 如果需要实现
深拷贝
(deep copy),就需要自定义拷贝构造函数 - 将指针类型的成员变量所指向的内存空间,拷贝到新的内存空间
# 对象类型的参数和返回值
使用对象类型作为函数的参数或者返回值,可能会产生一些不必要的中间对象,返回引用可避免中间值
# 匿名对象
匿名对象:没有变量名,没有被指针指向的对象,用完后马上调用析构
car(); | |
// 一次性对象,调用完之后立即释放空间 |
# 隐式构造 / 转换构造
Person p1 = 20; | |
// 等同于 | |
Person p2(20); | |
// 都会调用单构造函数 |
// 隐式构造 | |
Person test2() | |
{ | |
return 40; // 隐式构造,会将 40 传递给 Person 单函数对象 | |
} |
可以通过关键字 explicit 在定义构造函数时禁止掉隐式构造
# 编译器何时自动生成构造函数
virtual function table 在汇编显示:vftable
- 成员变量在声明的同时进行了初始化
- 有定义虚函数
- 虚继承其他类
- 包含了对象类型的成员,且这个成员有构造函数 (编译器生成或自定义)
- 父类有构造函数 (编译器生成或自定义)
- 对象创建后,需要做一些额外操作时 (比如内存操作,函数调用),编译器一般都会为期生成构造函数
# 内部类
如果将类 A 定义在类 C 的内部,那么类 A 就是一个内部类 (嵌套类)
- 内部类的特点
- 支持 public,protected,private 权限
- 成员函数可以直接访问其外部类对象的所有成员 (反过来不行)
- 成员函数可以直接不带类名,对象名访问其外部类的 static 成员
- 不会影响外部类的内存布局
# 局部类
在一个
函数内部
定义的类,称为局部类
- 局部域仅限于所在的函数内部
- 其所有的成员必须定义在类内部,不允许定义 static 成员变量
- 成员函数不能直接访问函数的局部变量 (static 变量除外)
- 在函数内部定义的类如果未调用类和函数,并不会每次调用函数都调用类
# 类型转换
- C 语言风格类型转换符
- (type)expression
- type(expression)
int a = 10; | |
double d = (double) a; | |
double d2 = double(a); |
- C++ 中有
4
个类型转换符- static_cast
- dynamic_cast
- reinterpret_cast
- const_cast
- 使用格式:xx_cast<type>(expression)
int a = 10; | |
double d = static_cast<double>(a); |
# const_cast
一般用于
去
除const
属性,将const
转换为非const
#include <iostream> | |
using namespace std; | |
class Person | |
{ | |
}; | |
int main(void) | |
{ | |
const Person* p1 = new Person(); | |
// 实现去除 const 属性的强制类型转换 | |
Person* p2 = const_cast<Person*>(p1); | |
Person* p3 = (Person*)p1; | |
// 汇编语言相同 | |
return 0; | |
} | |
/* | |
12: Person* p2 = const_cast<Person*>(p1); | |
00007FF773E2197C 48 8B 45 08 mov rax,qword ptr [rbp+8] | |
00007FF773E21980 48 89 45 28 mov qword ptr [rbp+28h],rax | |
13: Person* p3 = (Person*)p1; | |
00007FF773E21984 48 8B 45 08 mov rax,qword ptr [rbp+8] | |
00007FF773E21988 48 89 45 48 mov qword ptr [rbp+48h],rax | |
*/ |
# dynamic_cast
一般用于
多态
类型的转换,有运行
时安全检测
#include <iostream> | |
using namespace std; | |
class Person | |
{ | |
virtual void run() | |
{ | |
} | |
}; | |
class Student : public Person | |
{ | |
}; | |
class Car | |
{ | |
}; | |
int main(void) | |
{ | |
Person* p1 = new Person(); | |
Person* p2 = new Student(); | |
cout << "P1 = " << p1 << endl; | |
cout << "P2 = " << p2 << endl; | |
// 用于多态类型转换 student 和 Person 没有多态没有虚函数会报错 | |
// 不太安全,Person 赋值给 student,子类赋值给父类,不安全 | |
Student* stu1 = dynamic_cast<Student *>(p1); // 不安全 | |
Student* stu2 = dynamic_cast<Student *>(p2); // 安全 | |
// 其它方式会把地址完全转过来 | |
cout << "stu1 = " << stu1 << endl; // 不安全会清空,安全检测 | |
cout << "stu2 = " << stu2 << endl; | |
return 0; | |
} | |
/* | |
P1 = 0000019F7CD6C8B0 | |
P2 = 0000019F7CD6C2C0 | |
stu1 = 0000000000000000 //p1 赋值失败 | |
stu2 = 0000019F7CD6C2C0 //p2 成功 | |
*/ |
# static_cast
- 对比 dynamic_cast,缺乏运行时安全检测
不能交叉转换
(不是同一继承体系的,无法转换)- 常用语
基本数据类型
的转换,非const转成const
- 适用较广
Person* p1 = new Person(); | |
Car* c1 = dynamic_cast<Car*>(p1); | |
// 报错,交叉转换不允许 car 和 Person 没有任何关系 | |
//Car* c2 = static_cast<Car*>(p1); | |
// 基本类型转换 | |
int a = 10; | |
double d = static_cast<double>(a); | |
return 0; |
# reinterpret_cast
- 属于比较
底层的强制转换
,没有任何类型检查和格式转换,仅仅是简单的二进制数据拷贝
可以交叉转换
可以将指针和整数相互转换
#include <iostream> | |
using namespace std; | |
int main(void) | |
{ | |
int a = 10; | |
// 小端模式从后面开始读 | |
// 二进制 0000 1010 0000 0000 0000 0000 | |
// 十六进制: 0A 00 00 00 | |
//int a = 10; | |
//double 八个字节,double 和 int 并不能简单的二进制 copy | |
// 00 00 00 00 00 00 24 40 | |
double d = reinterpret_cast<double&>(a); | |
// 二进制纯 copy | |
cout << "a= " << a << endl; | |
cout << "d= " << d << endl; | |
return 0; | |
} | |
/* | |
a= 10 | |
d= 8.33713e+80 | |
*/ |
参考资料:《30 小时快速精通 C++ 和外挂》
https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html ↩︎