Rust逆向学习 (5)

文章目录

  • Reverse for Vec
    • vec! 与 添加元素
    • 元素访问
    • 元素遍历
    • 枚举数组
    • 弹出最后一个元素——pop
  • 总结

本文将对Rust中的通用集合类型——动态数组 Vec进行学习,对应参考书中的第8章。

Reverse for Vec

Vec是Rust中的动态数据结构,与C++中的vector功能类似。实际上Rust中的String就是一个特殊的Vec,这可以通过查看Rust的内核代码证实。

vec! 与 添加元素

vec!是一个宏,用于快速初始化数组元素。

pub fn main() {let mut x = vec![1, 2, 3];x.push(4);println!("{}", x.len());
}
example::main:sub     rsp, 168mov     edi, 12mov     esi, 4call    alloc::alloc::exchange_mallocmov     qword ptr [rsp + 32], raxand     rax, 3cmp     rax, 0sete    altest    al, 1jne     .LBB31_1jmp     .LBB31_2
.LBB31_1:mov     rsi, qword ptr [rsp + 32]mov     dword ptr [rsi], 1mov     dword ptr [rsi + 4], 2mov     dword ptr [rsi + 8], 3mov     rax, qword ptr [rip + alloc::slice::<impl [T]>::into_vec@GOTPCREL]lea     rdi, [rsp + 40]mov     qword ptr [rsp + 24], rdimov     edx, 3call    raxmov     rdi, qword ptr [rsp + 24]mov     rax, qword ptr [rip + alloc::vec::Vec<T,A>::push@GOTPCREL]mov     esi, 4call    raxjmp     .LBB31_5

第一段中,我们可以发现vec!宏执行时,汇编实际上执行的是什么操作。首先调用了一个exchange_malloc函数,传入第一个参数为12,第二个参数为4,根据源码可以判断出,第一个参数应该是总的内存分配字节数量,第二个参数为每个元素的字节数量。这个函数的返回值是Box<[i32]>,这是Rust中的一个智能指针类型,能够在堆分配内存并管理生命周期,指针保存在栈中。后面对返回值进行了判断,如果内存分配失败则会输出错误信息。Box的特性如下,参考资料:传送门

在栈上存储指针,指向堆上的数据。
在转移所有权时负责释放堆上的内存。
大小固定,适用于已知大小的类型。
只能有一个所有者,不可共享引用。

随后,代码中以rsi作为指针,初始化了3个数组元素。初始化完成后调用into_vecBox转换为Vec类型。可以说,上面源码中的vec!宏基本等同于:

let mut b: Box<[i32]> = Box::new([1, 2, 3]);
let mut x = b.into_vec();

经过调试发现,调用into_vec后,Vec实例中的指针与Box的指针相同,但现在Box类型已经不复存在了,其所有权已经被转移到Vec中。

随后,程序调用了push方法扩充了Vec的空间,但原先的地址空间不足以容纳新的元素,因此需要将原先的内存空间释放掉再重新分配。考虑到Rust在汇编层调用的是libc,所以堆管理那套本质上还是mallocfree那些函数,与C/C++相同,方便进行分析。

在动态数组大小发生改变时,如果存在一个已有的对某个元素的引用,那么大小改变后该引用可能会指向被释放的空间,这是Rust所不能允许的,这就要回到所有权规则的定义。考虑存在不可变引用的情况,如果此时需要增加数组的长度,那么首先在增加前必然需要获取该动态数组的可变引用,而所有权规则不允许一个实例同时存在可变引用和不可变引用,因此导致编译失败。

元素访问

Rust中有两种方式访问动态数组中的元素,第一种是直接通过下标访问:

pub fn main() {let mut x = vec![1, 2, 3];x.push(4);let y = &x[2];println!("{}", y);
}
.LBB33_5:lea     rdx, [rip + .L__unnamed_6]mov     rax, qword ptr [rip + <alloc::vec::Vec<T,A> as core::ops::index::Index<I>>::index@GOTPCREL]lea     rdi, [rsp + 40]mov     esi, 2call    raxmov     qword ptr [rsp + 16], raxjmp     .LBB33_6

这是加&的汇编代码,第一个参数就是Vec实例地址,第二个参数是索引值,第三个参数疑似指向工程名的字符串切片,推测是在索引越界后输出错误信息用的。这里实际上是调用了index方法进行索引。这个index函数的返回值是一个地址,如果加了&,则直接对指针进行操作,如果不加则会直接解引用。

; 不加&
.LBB32_6:mov     rax, qword ptr [rsp + 16]mov     eax, dword ptr [rax]mov     dword ptr [rsp + 68], eaxlea     rax, [rsp + 68]; 加&
.LBB33_6:mov     rax, qword ptr [rsp + 16]mov     qword ptr [rsp + 64], raxlea     rax, [rsp + 64]

第二种元素访问的方法是使用get方法:

pub fn main() {let mut x = vec![1, 2, 3];x.push(4);let y = x.get(2).unwrap();println!("{}", y);
}
.LBB35_5:mov     rax, qword ptr [rip + <alloc::vec::Vec<T,A> as core::ops::deref::Deref>::deref@GOTPCREL]lea     rdi, [rsp + 72]call    raxmov     qword ptr [rsp + 40], rdxmov     qword ptr [rsp + 48], raxjmp     .LBB35_6
.LBB35_6:mov     rsi, qword ptr [rsp + 40]mov     rdi, qword ptr [rsp + 48]mov     rax, qword ptr [rip + core::slice::<impl [T]>::get@GOTPCREL]mov     edx, 2call    raxmov     qword ptr [rsp + 32], raxjmp     .LBB35_7
.LBB35_7:mov     rdi, qword ptr [rsp + 32]lea     rsi, [rip + .L__unnamed_7]mov     rax, qword ptr [rip + core::option::Option<T>::unwrap@GOTPCREL]call    raxmov     qword ptr [rsp + 24], raxjmp     .LBB35_8

使用get函数前,会首先调用deref方法解引用获取动态数组类型中保存的定长数组实例,随后对这个实例使用get方法获取Option<T>实例。可见如果使用get方法进行数组的越界访问,那么get方法返回后不会立即panic!退出。

元素遍历

对于动态数组,要遍历数组中的元素,只需要使用for循环即可完成。但Rust源码看着简单,实际在汇编层完成的工作可不少。

pub fn main() {let mut x = vec![1, 2, 3];x.push(4);for i in x {println!("{}", i);}
}
.LBB46_5:mov     byte ptr [rsp + 247], 0mov     rax, qword ptr [rsp + 56]mov     qword ptr [rsp + 112], raxmovups  xmm0, xmmword ptr [rsp + 40]movaps  xmmword ptr [rsp + 96], xmm0mov     rax, qword ptr [rip + <alloc::vec::Vec<T,A> as core::iter::traits::collect::IntoIterator>::into_iter@GOTPCREL]lea     rdi, [rsp + 64]lea     rsi, [rsp + 96]call    raxjmp     .LBB46_6
.LBB46_6:mov     rax, qword ptr [rsp + 64]mov     qword ptr [rsp + 128], raxmov     rax, qword ptr [rsp + 72]mov     qword ptr [rsp + 136], raxmov     rax, qword ptr [rsp + 80]mov     qword ptr [rsp + 144], raxmov     rax, qword ptr [rsp + 88]mov     qword ptr [rsp + 152], rax
.LBB46_7:mov     rax, qword ptr [rip + <alloc::vec::into_iter::IntoIter<T,A> as core::iter::traits::iterator::Iterator>::next@GOTPCREL]lea     rdi, [rsp + 128]call    raxmov     dword ptr [rsp + 16], edxmov     dword ptr [rsp + 20], eaxjmp     .LBB46_10

上面即为for循环的其中一段,其中[rsp+40]Vec实例的地址。首先可以看到程序将Vec实例复制了一份,随后调用了into_iter方法获取了一个迭代器实例,该方法的第一个参数为需要初始化迭代器的地址,第二个参数为复制的Vec的地址。这个方法是可以单独调用的,返回一个迭代器:fn into_iter(self) -> Self::IntoIter。从下面的汇编代码(复制到[rsp+128])可以得知,这个迭代器实例在栈中的大小为0x20。下面是这个迭代器在调试时获取的最初状态:

08:0040│ rax rcx 0x7fffffffd840 —▸ 0x5555555b4ba0 ◂— 0x200000001
09:0048│         0x7fffffffd848 ◂— 0x6
0a:0050│         0x7fffffffd850 —▸ 0x5555555b4ba0 ◂— 0x200000001
0b:0058│         0x7fffffffd858 —▸ 0x5555555b4bb0 ◂— 0x0

其中第1个和第3个字保存的都是数组的起始地址,第4个字保存的是数组的末尾地址,第2个字的6保存的是数组的容量,注意这里的容量与数组长度不同,数组长度为4,但容量为6,只不过后面2个元素暂时还未被创建。

往下,代码调用了next方法,获取迭代器中的下一个元素,下面是调用后迭代器的状态:

10:0080│ rcx rdi 0x7fffffffd880 —▸ 0x5555555b4ba0 ◂— 0x200000001
11:0088│         0x7fffffffd888 ◂— 0x6
12:0090│         0x7fffffffd890 —▸ 0x5555555b4ba4 ◂— 0x300000002
13:0098│         0x7fffffffd898 —▸ 0x5555555b4bb0 ◂— 0x0

可以看到第三个字表示的实际上就是当前的指针。next方法返回的是一个Option<T>实例,索引值和数据分别被保存在raxrdx中。这一点在下面的汇编代码中得以证实。

.LBB46_10:mov     eax, dword ptr [rsp + 16]mov     ecx, dword ptr [rsp + 20]mov     dword ptr [rsp + 164], ecxmov     dword ptr [rsp + 168], eaxmov     eax, dword ptr [rsp + 164]cmp     rax, 0jne     .LBB46_12mov     rax, qword ptr [rip + core::ptr::drop_in_place<alloc::vec::into_iter::IntoIter<i32>>@GOTPCREL]lea     rdi, [rsp + 128]call    raxjmp     .LBB46_13

下面的代码中进行了一个比较,通过数据流分析可以发现这里是将next返回值与0进行比较,在Option<T>中,如果T不是一个枚举类型,那么枚举索引值为1表示有效值,0则表示无效值。随后就是正常的宏展开与输出,输出内容后无条件跳转回next方法调用前,继续调用next方法获取下一个值。

next方法调用失败,即已经到达迭代器的终点时,通过调试发现,返回的rax值为0,rdx值为0x5555。后续则是判断失败后跳出循环。

注意,上面的代码是for i in x,这里的x由于没有使用引用,在for循环一开始就丧失了所有权,其所有权会被转移到迭代器中,当for循环结束后,迭代器被销毁,后续将不能使用变量x

如果使用for i in &x,情况则会有些许的不同,不仔细观察还真的容易忽略

注意看,下面是两个into_iter方法在IDA反汇编界面中的函数名:

_$LT$$RF$alloc..vec..Vec$LT$T$C$A$GT$$u20$as$u20$core..iter..traits..collect..IntoIterator$GT$::into_iter::hed888fce85d317be_$LT$alloc..vec..Vec$LT$T$C$A$GT$$u20$as$u20$core..iter..traits..collect..IntoIterator$GT$::into_iter::he37dcd381eb06c85

可能你会纳闷:这里为啥会有这么多$符号?实际上,这是IDA用于表示某些标点符号的转义字符,这个转义的规则与Javascript类似。$LT$表示<$GT$表示>$RF$表示&$C$表示,$u??$表示\x??。因此上面的函数名就等同于:

<&alloc::vec::Vec<T,A> as core::iter::traits::collect::IntoIterator>::into_iter::hed888fce85d317be<alloc::vec::Vec<T,A> as core::iter::traits::collect::IntoIterator>::into_iter::he37dcd381eb06c85

上面那个是for i in &x调用的方法,下面是for i in x调用的方法,除了后面的哈希值之外,函数名真的只有一个&的差别。也即上面的方法是针对&Vec,下面的是针对Vec。二者的参数不同,上面那个只有1个参数:

.LBB33_5:mov     rax, qword ptr [rip + <&alloc::vec::Vec<T,A> as core::iter::traits::collect::IntoIterator>::into_iter@GOTPCREL]lea     rdi, [rsp + 64]call    raxmov     qword ptr [rsp + 32], rdxmov     qword ptr [rsp + 40], raxjmp     .LBB33_6

Vec实例的地址。

且二者的返回值也不同,对于<&alloc::vec::Vec<T,A> as core::iter::traits::collect::IntoIterator>::into_iter,其返回值保存在raxrdx中,其中rax为数组的开始地址,rdx为数组的结束地址。实际返回的迭代器的大小也只有16个字节。

for i in &x后面的汇编代码段如下:

.LBB33_6:mov     rax, qword ptr [rsp + 32]mov     rcx, qword ptr [rsp + 40]mov     qword ptr [rsp + 88], rcxmov     qword ptr [rsp + 96], rax
.LBB33_7:mov     rax, qword ptr [rip + <core::slice::iter::Iter<T> as core::iter::traits::iterator::Iterator>::next@GOTPCREL]lea     rdi, [rsp + 88]call    raxmov     qword ptr [rsp + 24], raxjmp     .LBB33_8
.LBB33_8:mov     rax, qword ptr [rsp + 24]mov     qword ptr [rsp + 104], raxmov     rdx, qword ptr [rsp + 104]mov     eax, 1xor     ecx, ecxcmp     rdx, 0cmove   rax, rcxcmp     rax, 0jne     .LBB33_10

可以看到这里调用的next方法也和不加&的不一样,参数只有1个,即数组的开始地址,返回值只有1个,即下一个元素的地址,该函数调用后,迭代器中的指针位置向前移动。可见对于引用类型的迭代器结构更为简单,只需要一个动态指针和一个结束指针即可,什么时候动态指针等于结束指针,迭代也就结束。

枚举数组

对于元素类型是枚举类型的数组,目前只有一个疑问:当枚举类型中不同枚举项所跟的数据类型不同,占用内存大小不同时,Rust将如何进行处理。

#[derive(Debug)]
enum Shapes {Round(f64),Rectangle(f64, f64),Triangle(f64, f64, f64),
}pub fn main() {let mut x = vec![Shapes::Round(3.5),Shapes::Rectangle(7.5, 9.6),Shapes::Triangle(114.514, 19.1981, 1.57)];
}
example::main:sub     rsp, 136mov     edi, 96mov     esi, 8call    alloc::alloc::exchange_mallocmov     qword ptr [rsp + 8], raxmovsd   xmm0, qword ptr [rip + .LCPI10_5]movsd   qword ptr [rsp + 48], xmm0mov     qword ptr [rsp + 40], 0movsd   xmm0, qword ptr [rip + .LCPI10_4]movsd   qword ptr [rsp + 80], xmm0movsd   xmm0, qword ptr [rip + .LCPI10_3]movsd   qword ptr [rsp + 88], xmm0mov     qword ptr [rsp + 72], 1movsd   xmm0, qword ptr [rip + .LCPI10_2]movsd   qword ptr [rsp + 112], xmm0movsd   xmm0, qword ptr [rip + .LCPI10_1]movsd   qword ptr [rsp + 120], xmm0movsd   xmm0, qword ptr [rip + .LCPI10_0]movsd   qword ptr [rsp + 128], xmm0mov     qword ptr [rsp + 104], 2and     rax, 7cmp     rax, 0sete    altest    al, 1jne     .LBB10_1jmp     .LBB10_2
.LBB10_1:mov     rsi, qword ptr [rsp + 8]mov     rax, qword ptr [rsp + 40]mov     qword ptr [rsi], raxmov     rax, qword ptr [rsp + 48]mov     qword ptr [rsi + 8], raxmov     rax, qword ptr [rsp + 56]mov     qword ptr [rsi + 16], raxmov     rax, qword ptr [rsp + 64]mov     qword ptr [rsi + 24], raxmov     rax, qword ptr [rsp + 72]mov     qword ptr [rsi + 32], raxmov     rax, qword ptr [rsp + 80]mov     qword ptr [rsi + 40], raxmov     rax, qword ptr [rsp + 88]mov     qword ptr [rsi + 48], raxmov     rax, qword ptr [rsp + 96]mov     qword ptr [rsi + 56], raxmov     rax, qword ptr [rsp + 104]mov     qword ptr [rsi + 64], raxmov     rax, qword ptr [rsp + 112]mov     qword ptr [rsi + 72], raxmov     rax, qword ptr [rsp + 120]mov     qword ptr [rsi + 80], raxmov     rax, qword ptr [rsp + 128]mov     qword ptr [rsi + 88], raxlea     rdi, [rsp + 16]mov     edx, 3call    qword ptr [rip + alloc::slice::<impl [T]>::into_vec@GOTPCREL]

可以看到,Rust编译器似乎很喜欢通过大量的mov系列指令完成内存复制操作,在上面的示例中可以发现,Rust是将枚举类型可能占用的最大内存大小作为数组一个元素的大小进行存储,在下面的内存拷贝操作中甚至还拷贝了未被初始化的内存区域。我们可以将每一个枚举类型后面跟的值视作一个大的union结构,一个枚举类型的不同实例占用的内存大小相同,即使其中一个实例只保存了8字节而另一个实例保存了80字节,前者也需要80个字节的空间保存数据。这会造成一定的内存浪费,但便于数组索引寻址。

弹出最后一个元素——pop

Vecpop方法能够弹出数组中最后一个元素,并在数组中将其删除。

pub fn main() {let mut x = vec![1, 2, 3];x.push(4);let y = x.pop().unwrap();
}
.LBB31_5:mov     rax, qword ptr [rip + alloc::vec::Vec<T,A>::pop@GOTPCREL]lea     rdi, [rsp + 32]call    raxmov     dword ptr [rsp + 8], edxmov     dword ptr [rsp + 12], eaxjmp     .LBB31_6
.LBB31_6:mov     esi, dword ptr [rsp + 8]mov     edi, dword ptr [rsp + 12]lea     rdx, [rip + .L__unnamed_5]mov     rax, qword ptr [rip + core::option::Option<T>::unwrap@GOTPCREL]call    raxjmp     .LBB31_7

pop的参数只有一个,即Vec实例地址,返回值是Option<T>rdx为有效值,rax为是否有效的索引值,1为有效。该方法调用后,数组的大小会变化,但容量不变,真正保存值的静态数组指针中的值也不变,而且也不需要改变,因为数组大小变小,所以后面的值在正常情况下无法访问。

在参考书中只给出了插入元素、获取元素、遍历元素等几个为数不多的Vec操作方法,但实际上Vec能完成的功能远不止于此,考虑到Vec的方法实在太多,这里无法全部完成分析,就先到这里了。不过我们已经掌握了Vec的基本结构,对于其他方法的分析也就万变不离其宗。

总结

本文我们学习了:

  1. Vec动态数组结构在内存中的结构。
  2. Vec在最后添加、删除元素、遍历、访问值的相关方法分析。
  3. IDA中对一些含有特殊字符的Rust方法的转义方式与Javascript类似。
  4. 枚举类型构成的数组中,每个枚举类型占用的内存大小相同,可能导致内存空间浪费。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/139420.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

4.HTML网页开发的工具

4. 网页开发的工具 4.1 快捷键 4.1.1 快速复制一行 快捷键&#xff1a;shiftalt下箭头&#xff08;上箭头&#xff09; 或者ctrlc 然后 ctrlv 4.1.2 选定多个相同的单词 快捷键&#xff1a; ctrld 4.1.3 添加多个光标 快捷键&#xff1a;ctrlalt上箭头&#xff08;下箭头&…

CS224W6.2——深度学习基础

在本文中&#xff0c;我们回顾了深度学习的概念和技术&#xff0c;这些概念和技术对理解图神经网络至关重要。从将机器学习表述为优化问题开始&#xff0c;介绍了目标函数、梯度下降、非线性和反向传播的概念。 文章目录 1. 大纲2. 优化问题2.1 举例损失函数 3. 如何优化目标函…

MySQL on duplicate key update用法

基本使用方法 public static final String SQL_TQI_SINK "insert into " ConfigureContext.get(ConfigKeyConstants.MYSQL_TABLE_TQI) " \n" "(mile_km, mile_start_km, mile_start_m, is_out, tqi_alig_l, \n" "tqi_alig_r, tqi_surf_l…

Redis系列-Redis性能优化与安全【9】

目录 Redis系列-Redis性能优化与安全【9】Redis性能优化策略Redis安全设置与防护措施Redis监控与诊断工具介绍 七、Redis应用案例与实战八、Redis未来发展与趋势 个人主页: 【⭐️个人主页】 需要您的【&#x1f496; 点赞关注】支持 &#x1f4af; Redis系列-Redis性能优化与安…

2023年第十六届山东省职业院校技能大赛高职组“信息安全管理与评估”赛项规程

第十六届山东省职业院校技能大赛 高职组“信息安全管理与评估”赛项规程 一、赛项名称 赛项名称&#xff1a;信息安全管理与评估 英文名称&#xff1a;Information Security Management and Evaluation 赛项组别&#xff1a;高职组 赛项归属&#xff1a;电子与信息大类 二…

运行npm install卡住不动的几种解决方案

在前端开发经常会遇到运行npm install 来安装工具包一直卡住不动&#xff0c;为此这里提供几种解决方案&#xff0c;供大家参考学习&#xff0c;不足之处还请指正。 第一种方案、首先检查npm代理&#xff0c;是否已经使用国内镜像 // 执行以下命令查看是否为国内镜像 npm con…

入门指南:Docker的基本命令

入门指南&#xff1a;Docker的基本命令 Docker是一个功能强大的容器化平台&#xff0c;可以帮助您轻松构建、打包和部署应用程序。要充分利用Docker&#xff0c;您需要了解一些基本命令。本文将介绍并示范Docker的一些最重要的基本命令&#xff0c;以帮助您快速上手。 1. doc…

linux 查看进程及端口

linux 查看进程及端口 使用 ps 命令查看当前运行的进程 命令&#xff1a; ps -aux | grep 进程名 进程ID用户CPU占用率内存占用率命令1234user10.51.2/usr/bin/example15678user21.00.8/usr/bin/example2 使用 netstat 命令查看当前打开的端口和与之相关的进程 命令&#…

虚假内容检测,谣言检测,不实信息检测,事实核查;纯文本,多模态,多语言;数据集整理

本博客系博主个人理解和整理所得&#xff0c;包含内容无法详尽&#xff0c;如有补充&#xff0c;欢迎讨论。 这里只提供数据集相关介绍和来源出处&#xff0c;或者下载地址等&#xff0c;因版权原因不提供数据集所含的元数据。如有需要&#xff0c;请自行下载。 “Complete d…

深度学习之基于Django+Tensorflow商品识别管理系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 项目简介 本系统是一个基于DjangoTensorflow的商品识别管理系统。通过深度学习技术&#xff0c;实现商品的自动识别…

arduino 简易智能花盆

编辑器&#xff1a;arduino IDE 主板&#xff1a;arduino uno 传感器&#xff1a; 0.96寸的OLED屏&#xff08;四脚&#xff09; 声音模块 土壤温湿度模块 DS18B20温度模块&#xff08;这里用到防水的&#xff09; 光敏电阻模块&#xff08;买成三脚的了只能显示高低&#x…

AR导览小程序开发方案

一、背景介绍 随着科技的不断发展&#xff0c;虚拟现实&#xff08;VR&#xff09;和增强现实&#xff08;AR&#xff09;技术逐渐被应用于各个领域。其中&#xff0c;AR导览小程序作为一种新兴的导览方式&#xff0c;以其独特的视觉体验和互动性受到了广泛的关注。AR导览小程…

el-table实现展开当前行时收起上一行的功能

<el-tableref"tableRef":data"tableData":expand-row-keys"expandRowKeys":row-key"handleRowKey" // 必须指定 row-keyexpand-change"handleExpandChange" // 当用户对某一行展开或者关闭的时候会触发该事件> <…

实验结果啊

所提出的框架已应用于智利一家银行的两个信用评分数据集。本节的组织结构如下&#xff1a;第4.1节对实施的项目进行了说明。实验设置如所示第4.2节&#xff0c;包括用于基准测试的替代方法的说明。随后&#xff0c;第4.3节总结了主要结果&#xff0c;包括从这些实验中获得的主要…

【算法专题】双指针—三数之和

力扣题目链接&#xff1a;三数之和 一、题目解析 二、算法原理 解法一&#xff1a;排序暴力枚举利用set去重 代码就不写了&#xff0c;你们可以试着写一下 解法二&#xff1a;排序双指针 这题和上一篇文章的两数字和方法类似 排序固定一个数a在这个数的后面区间&#xff0…

《詩經别解》——國風·周南·雎鳩​​​​​​​

一、关于古文的一个认识 目前可以阅读的古文经典&#xff0c;大多是经历了几千年的传承。期间的武力战争、文化纷争、宗教侵袭、官僚介入及文人的私人恩怨与流派桎梏&#xff0c;印刷与制作技术&#xff0c;导致这些古文全部都已经面目全非。简单地说&#xff0c;你读到的都是…

2022最新版-李宏毅机器学习深度学习课程-P46 自监督学习Self-supervised Learning(BERT)

一、概述&#xff1a;自监督学习模型与芝麻街 参数量 ELMO&#xff1a;94MBERT&#xff1a;340MGPT-2&#xff1a;1542MMegatron&#xff1a;8BT5&#xff1a;11BTuring NLG&#xff1a;17BGPT-3&#xff1a;175BSwitch Transformer&#xff1a;1.6T 二、Self-supervised Lear…

算法--- 每日温度

题目 给定一个整数数组 temperatures &#xff0c;表示每天的温度&#xff0c;返回一个数组 answer &#xff0c;其中 answer[i] 是指对于第 i 天&#xff0c;下一个更高温度出现在几天后。如果气温在这之后都不会升高&#xff0c;请在该位置用 0 来代替。 示例 1: 输入: te…

[HXPCTF 2021]includer‘s revenge

文章目录 方法一前置知识Nginx 在后端 Fastcgi 响应过大产生临时文件竞争包含绕过include_once限制 解题过程 方法二前置知识Base64 Filter 宽松解析iconv filter 解题过程 方法一 NginxFastCGI临时文件 前置知识 Nginx 在后端 Fastcgi 响应过大产生临时文件 www-data用户在n…

SharePoint 页面中插入自定义代码

我们都知道 SharePoint 是对页面进行编辑的。 对于一些有编程基础的人来说&#xff0c;可能需要对页面中插入代码&#xff0c;这样才能更好的对页面进行配置。 但是在新版本的 SharePoint modern 页面来说&#xff0c;虽然我们可以插入 Embed 组件。 但是 Embed 组件中是不允…