做外链的博客网站,高端模板建站,小网站关键词,c2c平台是洗钱吗Reverse for Struct
Rust中的结构体是一个重要的内容#xff0c;由于Rust中没有类的概念#xff0c;因此其他编程语言中的封装、继承、多态与Rust中的表现都有较大差异。
我们使用参考书中的一个示例开始进行分析。
Struct 初始化
struct User {username: String,email: …Reverse for Struct
Rust中的结构体是一个重要的内容由于Rust中没有类的概念因此其他编程语言中的封装、继承、多态与Rust中的表现都有较大差异。
我们使用参考书中的一个示例开始进行分析。
Struct 初始化
struct User {username: String,email: String,sign_in_count: u64,active: bool,
}pub fn main() {let mut user1 User {email: String::from(someoneexample.com),username: String::from(someusername123),active: true,sign_in_count: 1};println!({}, {}, user1.email, user1.active);
}上面这段在汇编层是如何处理的呢
第一段
example::main:sub rsp, 296lea rsi, [rip .L__unnamed_5]lea rdi, [rsp 120]mov edx, 19call alloc::string::String as core::convert::Fromstr::fromlea rsi, [rip .L__unnamed_6]lea rdi, [rsp 144]mov edx, 15call alloc::string::String as core::convert::Fromstr::fromjmp .LBB17_3....L__unnamed_5:.ascii someoneexample.com.L__unnamed_6:.ascii someusername123上面是第一段汇编内容在源码中我们是首先对email进行了初始化在汇编中也是如此。这里分别将两个字符串实例保存到了[rsp120]和[rsp144]处。我们之前分析过String实例在栈中的大小应该为0x18可见这两个String实例是完全相邻的中间没有其他的数据。
第二段
.LBB17_3:mov rax, qword ptr [rsp 160]mov qword ptr [rsp 64], raxmovups xmm0, xmmword ptr [rsp 144]movaps xmmword ptr [rsp 48], xmm0lea rax, [rsp 72]mov rcx, qword ptr [rsp 136]mov qword ptr [rsp 88], rcxmovups xmm0, xmmword ptr [rsp 120]movups xmmword ptr [rsp 72], xmm0mov qword ptr [rsp 96], 1mov byte ptr [rsp 104], 1mov qword ptr [rsp 280], raxlea rax, [rip alloc::string::String as core::fmt::Display::fmt]mov qword ptr [rsp 288], raxmov rax, qword ptr [rsp 280]mov qword ptr [rsp 32], raxmov rax, qword ptr [rsp 288]mov qword ptr [rsp 40], raxjmp .LBB17_6随后是第二段这里有一个Rust 1.73与Rust 1.69的不同之处在老版本中对于宏将会调用core::fmt::ArgumentV1::new_display将中括号对应的内容转为字符串而在新版本中则只会将core::fmt::Display函数地址保存到栈而并不调用。并且结构体中各个元素的内存排列顺序也不相同通过IDA分析可见在1.73版本中元素排列与元素定义的顺序相同但老版本中则不是。这里是因为String实例实现了Display这个Trait所以能够直接输出。输出时调用的实际上也是Display的Trait。
需要注意的是第一段中的字符串初始化并不是对结构体的字符串直接进行初始化而是在栈中另外开辟了0x30大小的空间用于初始化这两个字符串随后将这段内存的内容复制到结构体中。真正的结构体应该位于[rsp48]。四个元素的保存地址分别为[rsp48][rsp72][rsp96][rsp104]因此中间的两条指令mov qword ptr [rsp 96], 1、mov byte ptr [rsp 104], 1就是在对sign_in_count和active进行初始化因为二者一个是整数类型一个是布尔值都是不需要通过new进行初始化的因此可以直接赋值。
00000000 revlab::User struc ; (sizeof0x40, align0x8, copyof_91)
00000000 ; XREF: _ZN6revlab4main17h1e5ad0972ab6a820E/r
00000000 ; _ZN6revlab4main17h1e5ad0972ab6a820E/r
00000000 username alloc::string::String ? ; XREF: revlab::main::h1e5ad0972ab6a82065/w
00000000 ; revlab::main::h1e5ad0972ab6a82072/w
00000018 email alloc::string::String ? ; XREF: revlab::main::h1e5ad0972ab6a82077/o
00000018 ; revlab::main::h1e5ad0972ab6a82084/w ...
00000030 sign_in_count dq ? ; XREF: revlab::main::h1e5ad0972ab6a82093/w
00000038 active db ? ; XREF: revlab::main::h1e5ad0972ab6a8209C/w
00000038 ; revlab::main::h1e5ad0972ab6a82011C/o
00000039 db ? ; undefined
0000003A db ? ; undefined
0000003B db ? ; undefined
0000003C db ? ; undefined
0000003D db ? ; undefined
0000003E db ? ; undefined
0000003F db ? ; undefined第三段
.LBB17_6:mov rax, qword ptr [rsp 40]mov rcx, qword ptr [rsp 32]mov qword ptr [rsp], rcxmov qword ptr [rsp 8], raxlea rax, [rsp 104]mov qword ptr [rsp 264], raxmov rax, qword ptr [rip bool as core::fmt::Display::fmtGOTPCREL]mov qword ptr [rsp 272], raxmov rax, qword ptr [rsp 264]mov qword ptr [rsp 16], raxmov rax, qword ptr [rsp 272]mov qword ptr [rsp 24], raxmov rax, qword ptr [rsp 24]mov rcx, qword ptr [rsp 16]mov rdx, qword ptr [rsp 8]mov rsi, qword ptr [rsp]mov qword ptr [rsp 216], rsimov qword ptr [rsp 224], rdxmov qword ptr [rsp 232], rcxmov qword ptr [rsp 240], raxlea rsi, [rip .L__unnamed_7]lea rdi, [rsp 168]mov edx, 3lea rcx, [rsp 216]mov r8d, 2call core::fmt::Arguments::new_v1jmp .LBB17_8.L__unnamed_7:.quad .L__unnamed_2.zero 8.quad .L__unnamed_11.asciz \002\000\000\000\000\000\000.quad .L__unnamed_12.asciz \001\000\000\000\000\000\000.L__unnamed_11:.ascii , .L__unnamed_12:.ascii \n这一段的工作主要就是输出通过调试发现新版rustc在使用println!宏时将不再将临时字符串切片参数保存在栈中但通过IDA依然可以较为容易地辨别。
Struct 作为返回值
下面书中给出一个通过函数初始化结构体的实例
struct User {username: String,email: String,sign_in_count: u64,active: bool,
}fn build_user(email: String, username: String) - User {User {email,username,active: true,sign_in_count: 1}
}pub fn main() {let mut user1 build_user(String::from(someoneexample.com), String::from(someusername123));println!({}, {}, user1.email, user1.active);
}example::build_user:mov rax, rdimov rcx, qword ptr [rdx]mov qword ptr [rdi], rcxmov rcx, qword ptr [rdx 8]mov qword ptr [rdi 8], rcxmov rcx, qword ptr [rdx 16]mov qword ptr [rdi 16], rcxmov rcx, qword ptr [rsi]mov qword ptr [rdi 24], rcxmov rcx, qword ptr [rsi 8]mov qword ptr [rdi 32], rcxmov rcx, qword ptr [rsi 16]mov qword ptr [rdi 40], rcxmov qword ptr [rdi 48], 1mov byte ptr [rdi 56], 1ret从函数的汇编可以看到这个函数实际上是将第一个参数作为指针完成初始化的可以将第一个指针理解为this这与C类方法的函数调用规则类似。
实现 Debug Trait
一个结构体可以通过#[derive(Debug)]完成对Debug Trait的默认实现
#[derive(Debug)]
struct Rect {width: u32,height: u32,
}pub fn main() {let rect1 Rect {width: 30, height: 50};println!(rect1 {:?}, rect1);
}example::main:sub rsp, 88mov dword ptr [rsp], 30mov dword ptr [rsp 4], 50mov rax, rspmov qword ptr [rsp 72], raxmov rax, qword ptr [rip example::Rect as core::fmt::Debug::fmtGOTPCREL]mov qword ptr [rsp 80], raxmov rcx, qword ptr [rsp 72]mov rax, qword ptr [rsp 80]mov qword ptr [rsp 56], rcxmov qword ptr [rsp 64], raxlea rdi, [rsp 8]lea rsi, [rip .L__unnamed_4]mov edx, 2lea rcx, [rsp 56]mov r8d, 1call core::fmt::Arguments::new_v1lea rdi, [rsp 8]call qword ptr [rip std::io::stdio::_printGOTPCREL]add rsp, 88ret可以看到汇编代码中获取的就是Debug这个Trait的函数指针说明不同的宏实际上调用的函数也不同。如果将{:?}修改为{:#?}则原先调用的core::fmt::Arguments::new_v1将会改为调用core::fmt::Arguments::new_v1_formatted。考虑到Rust的格式化字符串非常强大与灵活有多种输出形式后面将通过专门的分析对宏展开进行分析这里不深入探讨。
Reverse for Methods
在Rust中结构体充当了其他语言中类的功能可以在结构体下定义方法使这个方法专属于该结构体。
struct Rect {width: u32,height: u32,
}impl Rect {fn area(self) - u32 {self.width * self.height}
}pub fn main() {let rect1 Rect {width: 30, height: 50};println!(area {}, rect1.area());
}example::Rect::area:push raxmov eax, dword ptr [rdi]mul dword ptr [rdi 4]mov dword ptr [rsp 4], eaxseto altest al, 1jne .LBB1_2mov eax, dword ptr [rsp 4]pop rcxret
.LBB1_2:lea rdi, [rip str.0]lea rdx, [rip .L__unnamed_4]mov rax, qword ptr [rip core::panicking::panicGOTPCREL]mov esi, 33call raxud2example::main:sub rsp, 104mov dword ptr [rsp 8], 30mov dword ptr [rsp 12], 50lea rdi, [rsp 8]call example::Rect::areamov dword ptr [rsp 84], eaxlea rax, [rsp 84]mov qword ptr [rsp 88], raxmov rax, qword ptr [rip core::fmt::num::imp::impl core::fmt::Display for u32::fmtGOTPCREL]mov qword ptr [rsp 96], raxmov rcx, qword ptr [rsp 88]mov rax, qword ptr [rsp 96]mov qword ptr [rsp 64], rcxmov qword ptr [rsp 72], raxlea rdi, [rsp 16]lea rsi, [rip .L__unnamed_5]mov edx, 2lea rcx, [rsp 64]mov r8d, 1call core::fmt::Arguments::new_v1lea rdi, [rsp 16]call qword ptr [rip std::io::stdio::_printGOTPCREL]add rsp, 104ret由上述汇编可知这里还是将rdi作为self使用。
#[derive(Debug)]
struct Rect {width: u32,height: u32,
}impl Rect {fn area(self) - u32 {self.width * self.height}fn can_hold(self, other: Rect) - bool {self.width other.width self.height other.height}
}pub fn main() {let rect1 Rect {width: 30, height: 50};let rect2 Rect {width: 10, height: 40};println!({}, rect1.can_hold(rect2));
}对于上面的代码can_hold方法的参数有两个都是指针如果将第二个参数的去掉则参数有三个。经过试验发现当一个结构体中的元素数量较少时不加可能会将结构体的每个元素分别作为参数传递当元素数量较多时则是首先复制然后传递指针。
对于关联函数由于其第一个参数并不是self类似于C中的类静态函数不需要首先获取结构体实例即可调用参数传递与一般的函数相同。
Reverse for enum (Part 2)
对于枚举类型我们在第二篇文章中已经进行了较为详细的解释对于枚举类型的内存排布有了一定的了解。
下面对枚举类型中定义的方法进行测试。
use std::any::Any;pub enum Student {Freshman(String),Sophomore(String),Junior(String),Senior(String),
}pub fn get_student(grade: i32, name: String) - OptionStudent {match grade {1 Some(Student::Freshman(name)),2 Some(Student::Sophomore(name)),3 Some(Student::Junior(name)),4 Some(Student::Senior(name)),_ None}
}impl Student {fn test(self) - String {match self {Student::Freshman(name) format!({}, Calculus).to_string(),Student::Sophomore(name) format!({}, Data Structure).to_string(),Student::Junior(name) format!({}, Computer Network).to_string(),Student::Senior(name) format!({}, Graduation Design).to_string()}}
}pub fn main() {let x get_student(4, CoLin.to_string()).unwrap();println!({}, x.test());
}上面代码中对于test方法的调用如下 mov rax, qword ptr [rip core::option::OptionT::unwrapGOTPCREL]lea rdi, [rsp 40]mov qword ptr [rsp 32], rdicall raxmov rsi, qword ptr [rsp 32]lea rdi, [rsp 192]call example::Student::testjmp .LBB26_3可以看到方法的第一个参数依然是self第二个参数则是等待初始化的String实例地址。在代码中是返回String实例实际上是传入未初始化的指针。
OptionT
针对OptionTRust在汇编层有自己的处理方式。如果将OptionT看做一个普通的枚举类型且Some后面带的是另一个枚举类型那么这样的话就会产生两层枚举对象不太优雅。对于get_student函数下面是部分反编译结果
.text:0000000000009702 48 89 4C 24 18 mov [rsp108hvar_F0], rcx
.text:0000000000009707 83 E8 03 sub eax, 3
.text:000000000000970A 77 15 ja short def_971F ; jumptable 000000000000971F default case
.text:000000000000970A
.text:000000000000970C 48 8B 44 24 18 mov rax, [rsp108hvar_F0]
.text:0000000000009711 48 8D 0D B4 09 04 00 lea rcx, jpt_971F
.text:0000000000009718 48 63 04 81 movsxd rax, ds:(jpt_971F - 4A0CCh)[rcxrax*4]
.text:000000000000971C 48 01 C8 add rax, rcx
.text:000000000000971F FF E0 jmp rax ; switch jump
.text:000000000000971F
.text:0000000000009721 ; ---------------------------------------------------------------------------
.text:0000000000009721
.text:0000000000009721 def_971F: ; CODE XREF: revlab::get_student::h5c77d454e35cea033A↑j
.text:0000000000009721 48 8B 44 24 08 mov rax, [rsp108hvar_100] ; jumptable 000000000000971F default case
.text:0000000000009726 48 C7 00 04 00 00 00 mov qword ptr [rax], 4
.text:000000000000972D E9 43 02 00 00 jmp loc_9975下面的def_971F为默认分支可以看到这里是将枚举类型的索引值赋值为4但上面定义的枚举类型一共只有4个值最大的索引值只能为3。将索引值设置为4实际上也就表示这个枚举类型是一个无效值这样在内存中实际上并不存在二重枚举类型而是只有一个Student枚举类型。由此可见对泛型参数为枚举类型的OptionRust进行了优化。
Reverse for if-let
if let语句是针对只有一个处理条件和一个默认条件的match语句的平替。由于只有一个特殊条件和默认条件因此在实际实现中只需要使用类似于if的逻辑即可完成。
pub fn main() {let x get_student(4, CoLin.to_string());if let Some(Student::Senior(y)) x {println!({}, y);}
}example::main:sub rsp, 216mov byte ptr [rsp 183], 0lea rdi, [rsp 56]lea rsi, [rip .L__unnamed_5]mov edx, 5call str as alloc::string::ToString::to_stringlea rdi, [rsp 24]mov esi, 4lea rdx, [rsp 56]call qword ptr [rip example::get_studentGOTPCREL]mov byte ptr [rsp 183], 1mov eax, 1xor ecx, ecxcmp qword ptr [rsp 24], 4cmove rax, rcxcmp rax, 1jne .LBB18_2cmp qword ptr [rsp 24], 3je .LBB18_3可以发现这里的判断逻辑和match是类似的都是对枚举索引值进行比较。
总结
本文学习了
Rust 结构体的内存排布以及结构体方法的参数传递结构体方法参数传递遵照this参数传递法Rust 枚举类型方法的参数传递与结构体方法的参数传递类似Rust if-let语句的判断逻辑OptionT的内存结构