当前位置: 首页 > news >正文

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. 枚举类型构成的数组中,每个枚举类型占用的内存大小相同,可能导致内存空间浪费。

相关文章:

Rust逆向学习 (5)

文章目录 Reverse for Vecvec! 与 添加元素元素访问元素遍历枚举数组弹出最后一个元素——pop 总结 本文将对Rust中的通用集合类型——动态数组 Vec进行学习&#xff0c;对应参考书中的第8章。 Reverse for Vec Vec是Rust中的动态数据结构&#xff0c;与C中的vector功能类似。…...

89.STL-函数对象的使用(仿函数)

目录 1.什么是函数对象 2.仿函数示例 3.代码示例 1.什么是函数对象 函数对象是C中的一种编程概念&#xff0c;也称为函数符或仿函数。其实就是重载“()”操作符&#xff0c;使得类对象可以像函数那样调用。 分类:假定某个类有一个重载的operator()&#xff0c;而且重载的oper…...

文件管理技巧:按文件容量大小分类,自动移动至目标文件夹的方法

按文件容量大小分类可以帮助快速识别和筛选出不同大小的文件。这样做有很多好处。首先&#xff0c;可以轻松地查找和访问特定大小的文件&#xff0c;提高工作效率。其次&#xff0c;通过将不同大小的文件分类&#xff0c;可以更好地了解和掌控文件的使用情况&#xff0c;避免存…...

[架构之路-246]:目标系统 - 设计方法 - 软件工程 - 需求工程- 需求开发:获取、分析、定义、验证

目录 前言&#xff1a; 架构师为什么需要了解需求分析 一、需求工程概述 1.1 概述 1.2 需求工程的两大部分 &#xff08;1&#xff09;需求开发&#xff1a;系统工程师的职责、目标系统开发角度 &#xff08;2&#xff09;需求管理&#xff1a;项目管理者的职责、项目管…...

轻量日志管理方案-[EFK]

使用FileBeat进行日志文件的数据收集&#xff0c;并发送到ES进行存储&#xff0c;最后Kibana进行查看展示&#xff1b; 这个应该是最简单&#xff0c;轻量的日志收集方案了。 最总方案为&#xff1a;FileBeatESKibana ; 【Kibana过于强大&#xff0c;感觉可以无限扩展】 文章目…...

Halcon WPF 开发学习笔记:HSmartWindowControlWPF正常加载

文章目录 加载问题相关文章彻底解决 加载问题 我们在WPF中使用Halcon的时候&#xff0c;会出现图片被拉伸的问题&#xff0c;需要拖动才可以解决&#xff0c;我网上找了好久&#xff0c;终于找到了如何成功解决这个问题。 相关文章 3.7 Halcon 窗体显示对象消失问题 【halcon】…...

mybatis的简单教程

整体就是mysql里存了一张表&#xff0c;然后在java程序里用mybatis把数据读出来的一个简单示例。 库 blog里有一张表 article 整个项目就是增加了这3个文件 首先是mybatis-config.xml文件 <?xml version"1.0" encoding"UTF-8" ?> <!DOCTYPE c…...

数据结构 队列(C语言实现)

目录 1.队列的概念及结构2.队列的代码实现 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的 人工智能学习网站&#xff0c; 通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。 点击跳转到网站。 1.队列的概念及结构 队列&#xff1a;只允许在…...

Android---屏幕适配的处理技巧

在几年前&#xff0c;屏幕适配一直是困扰 Android 开发工程师的一大问题。但是随着近几年各种屏幕适配方案的诞生&#xff0c;以及谷歌各种适配控件的推出&#xff0c;屏幕适配也显得越来越容易。下面&#xff0c;我们就来总结一下关于屏幕适配的那些技巧。 ConstraintLayout …...

vmware workstation 与 device/credential guard 不兼容

VM虚拟机报错 vmware虚拟机启动时报错&#xff1a;vmware workstation 与 device/credential guard 不兼容&#xff1a; 系统是win10专业版&#xff0c;导致报错原因最终发现是安装了docker&#xff0c;docker自带下载虚拟机Hyper-V&#xff0c;而导致vmware workstation 与 …...

第7章-使用统计方法进行变量有效性测试-7.2.1-单因素方差分析

目录 7.2 方差分析 7.2.1 单因素方差分析 组内变异 组间变异 总变异 随机误差...

黑客技术-小白学习手册

一、黑客是什么 原是指热心于计算机技术&#xff0c;水平高超的电脑专家&#xff0c;尤其是程序设计人员。但后来&#xff0c;黑客一词已被用于泛指那些专门利用电脑网络搞破坏或者恶作剧的家伙。 二、学习黑客技术的原因 其实&#xff0c;网络信息空间安全已经成为海陆空之…...

用示波器测量高压电

示波器本身是不可以测试几千v的高压电电路的&#xff0c;一般自带的探头衰减倍数不够&#xff0c;需要使用高压差分探头或者高压探棒&#xff0c;将测试信号衰减到合适的范围再接入示波器。 普通探头能测差分电压吗&#xff1f;差分探头和普通探头有什么区别&#xff1f;全网最…...

AQS中Node状态

在AQS&#xff08;AbstractQueuedSynchronizer&#xff09;中&#xff0c;Node 是一个用于构建等待队列的节点类&#xff0c;用于表示等待获取锁的线程。Node 的状态在不同的同步器中有不同的含义&#xff0c;但一般来说&#xff0c;Node 的状态可以分为以下几种&#xff1a; C…...

STM32 寄存器配置笔记——GPIO配置输出

一、概述 本文主要介绍GPIO 作为输出时的寄存器配置。包括时钟配置&#xff0c;输出模式配置。以STM32F10xxx系列为例&#xff0c;配置PA8、PD2端口作为输出&#xff0c;输出高/低电平。 二、配置流程 1&#xff09;GPIO外设时钟 通过查找STM32F10xxx中文参考手册得知&#xf…...

Spring boot 整合grpc 运用

文章目录 GRPC基础概念&#xff1a;Protocol Buffers&#xff1a;proto 基础语法&#xff1a;调用类型&#xff1a; Spring boot 整合 grpc项目结构&#xff1a;整合代码&#xff1a;父 pomproto 模块服务端&#xff1a;客户端&#xff1a;实际调用&#xff1a; 原生集成 GRPC基…...

C++ 模板保姆级详解——template<class T>(什么是模板?模板分哪几类?模板如何应用?)

目录 一、前言 二、 什么是C模板 &#x1f4a6;泛型编程的思想 &#x1f4a6;C模板的分类 三、函数模板 &#x1f4a6;函数模板概念 &#x1f4a6;函数模板格式 &#x1f4a6;函数模板的原理 &#x1f4a6;函数模板的实例化 &#x1f34e;隐式实例化 &#x1f349;显式实…...

uni.getLocation() 微信小程序 线上获取失败

开发版,体验版,用此方法都可以正确获取定位,但是在小程序的线上,总是获取失败 参考:uni-app微信小程序uni.getLocation获取位置&#xff1b;authorize scope.userLocation需要在app.json中声明permission&#xff1b;小程序用户拒绝授权后重新授权-CSDN博客 uniapp 中的 uni.…...

Pytorch损失函数、反向传播和优化器、Sequential使用

Pytorch_Sequential使用、损失函数、反向传播和优化器 文章目录 nn.Sequential搭建小实战损失函数与反向传播优化器 nn.Sequential nn.Sequential是一个有序的容器&#xff0c;用于搭建神经网络的模块被按照被传入构造器的顺序添加到nn.Sequential()容器中。 import torch.nn …...

css:两个行内块元素和图片垂直居中对齐

目录 两个行内块元素垂直居中对齐图片垂直居中问题图片和文字垂直居中对齐参考文章 两个行内块元素垂直居中对齐 先看一段代码&#xff1a; <style> .box {width: 200px;height: 200px;line-height: 200px;font-size: 20px;text-align: center;display: inline-block;b…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

国防科技大学计算机基础课程笔记02信息编码

1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制&#xff0c;因此这个了16进制的数据既可以翻译成为这个机器码&#xff0c;也可以翻译成为这个国标码&#xff0c;所以这个时候很容易会出现这个歧义的情况&#xff1b; 因此&#xff0c;我们的这个国…...

VB.net复制Ntag213卡写入UID

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

质量体系的重要

质量体系是为确保产品、服务或过程质量满足规定要求&#xff0c;由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面&#xff1a; &#x1f3db;️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限&#xff0c;形成层级清晰的管理网络&#xf…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)

宇树机器人多姿态起立控制强化学习框架论文解析 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架&#xff08;一&#xff09; 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...

微信小程序云开发平台MySQL的连接方式

注&#xff1a;微信小程序云开发平台指的是腾讯云开发 先给结论&#xff1a;微信小程序云开发平台的MySQL&#xff0c;无法通过获取数据库连接信息的方式进行连接&#xff0c;连接只能通过云开发的SDK连接&#xff0c;具体要参考官方文档&#xff1a; 为什么&#xff1f; 因为…...

Android15默认授权浮窗权限

我们经常有那种需求&#xff0c;客户需要定制的apk集成在ROM中&#xff0c;并且默认授予其【显示在其他应用的上层】权限&#xff0c;也就是我们常说的浮窗权限&#xff0c;那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...

大学生职业发展与就业创业指导教学评价

这里是引用 作为软工2203/2204班的学生&#xff0c;我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要&#xff0c;而您认真负责的教学态度&#xff0c;让课程的每一部分都充满了实用价值。 尤其让我…...

springboot整合VUE之在线教育管理系统简介

可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生&#xff0c;小白用户&#xff0c;想学习知识的 有点基础&#xff0c;想要通过项…...

面向无人机海岸带生态系统监测的语义分割基准数据集

描述&#xff1a;海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而&#xff0c;目前该领域仍面临一个挑战&#xff0c;即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...