news 2026/7/24 20:41:57

【Rust中级教程】1.13. 内存中的类型 Pt.1:对齐(Alignment)、布局(Layout)、`repr`属性

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Rust中级教程】1.13. 内存中的类型 Pt.1:对齐(Alignment)、布局(Layout)、`repr`属性

1.13 内存中的类型 Pt.1:对齐(Alignment)、布局(Layout)、repr属性

1.13.1. 类型的基本职责

每个Rust值都有类型,而类型的职责在于告诉你如何解释内存中的比特位(bits)。

例如:0b10111101这串比特(bits)本身并没有意义,但是:
- 用u8类型来解释就会得到数字189
- 用i8类型来解释就会得到数字-67

当自定义类型时:编译器决定该类型的各部分在内存表示中的位置

1.13.2. 对齐(Alignment)

对齐(Alignment)决定了类型的字节可以被存储在哪里。

而一旦类型的表示被确定之后,你可能想在内存上随便找个地方存进去就行,这在理论上是可行的。但实际上计算机硬件对给定的类型可以存放的位置是有约束的。

最典型的一个例子是指针,它指向的是字节(bytes),而不是位(bits),1个字节等于8比特。换言之,它并不指向具体的比特。所以如果将某类型的值放在计算机内存中索引为4的位(bits)上,那就无法引用它的地址,因为指针指向的是字节,而不是具体的比特,所以就必须对齐字节,也就是对齐到8比特。

出于这个原因,所有的值(无论什么类型),都必须开始于字节的边界。所有类型必须至少是字节对齐的(byte-aligned)。换言之,存放的地址必须是8bits的整数倍。

1.13.3. 更严格的对齐规则

有一些类型的对齐规则比字节的对齐规则还要严格:在CPU和内存系统里,内存经常按大于单个byte的块进行访问。

例如:在64位的CPU上,大部分的值是按照8bytes的块进行访问的,每个操作都开始于“8bytes对齐”的地址上。(这也叫做CPU的字长,英文是word size)

CPU当然也有办法处理更小值的读写,以及跨越块边界的值。但是我们作为开发者应该尽可能地保证硬件可以操作于它的原生(native)对齐。

举个例子:如果想读取的i64值它开始于8bytes块的中间,那这个时候要读取它就至少需要两次读取。因为i64是8字节,而它开始于两个8字节块中间说明它一定横跨了这两个块。所以在读取时引进就得从这两个块读取数据,第一个块读取i64的前面部分,第二个块读取i64的后面部分,然后再把它们合并到一起。

这种操作是非常低效的,会拖累程序执行的速度,所以我们应该尽可能保证硬件可以操作于它的原生对齐。

1.13.4. 没对齐的操作

CPU访问内存时,数据的地址没有按照架构要求的对齐方式进行访问叫做"misaligned access"。这会导致性能低下和并发问题。

很多CPU操作多要求/强烈建议它们的参数是自然对齐的(naturally aligned)。自然对齐值的对齐是匹配他们值的大小的。

例如我想加载8字节,那么提供的地址就需要8字节对齐。

1.13.5. 编译器会尽可能利用对齐

基于类型包含的内容,编译器通过计算为类型分配一个对齐(或者叫给它分配一个对齐方案):
- 对于内值的值,通常对齐到它们的大小。比如说u8按1字节对齐,u16按2字节对齐,u32按4字节对齐,u64按8字节对齐。

  • 而复杂类型(包含其它类型的类型),通常被赋予所含类型的最大对齐。例如某类型含有u8u16u32这三个类型的字段,那么类型就应该是4字节对齐(u32是最大对齐,为4字节)

1.13.6. 布局(Layout)

类型的布局(Layout)指的是编译器决定这个类型在内存上如何表示。

Rust编译器对于类型如何布局,并没有给出多少保证。

Rust提供了repr属性(attribute):它可以添加到你类型的定义上,来请求特定的类型表示。

1.13.7.repr(C)

repr属性(attribute)最常见的一个是repr(C)。名字里带个C说明跟C语言有关系。

repr(C)布局方式与C/C++编译器对同类型的布局兼容。这对于使用FFI(外部函数接口,英文是Foreign Function Interface)与其它语言交互的Rust代码很有用。

使用FFI与其它语言交互的时候,Rust会生成一个匹配其它语言编译器期望的布局。因为C语言的布局是可预测且不易改变的,所以repr(C)unsafe(unsafe Rust详见 【Rust自学】19.1. 摆脱安全性限制的unsafe Rust)的上下文是非常有用的。

比如说你使用指向该类型的原始指针时,或者在两个具有相同字段的类型间进行转换时,都可以使用到repr(C)

1.13.8.repr(transparent)

repr(transparent)中的transparent是透明的意思,它用于 newtype 风格的包装类型,并保证外层类型与其唯一的非零大小字段具有相同的布局。(若还有其它字段,它们必须是零大小类型,例如()PhantomData。)

这与newtype模式(详见【Rust自学】19.5. 高级类型)结合起来很好用。

我们在这里回顾一下newtype模式:利用元组结构体来构建一个新的类型放在本地,相当于是薄封装。

举个例子:你想操作struct Astruct NewA(A)的内存表示,使用了repr(transparent)之后两者的内存表示就应该是一样的。不使用的话Rust编译器就没发保证了。

1.13.9. 使用repr属性的例子

我们来看一个例子:

代码字段类型的大小默认内存表示填充最终对齐
#[repr(C)]
struct Foo {
tiny: bool,1 bit1 byte 对齐3 bytes
normal: u32,4 bytes4 bytes 对齐(tiny+normal)8 bytes
small: u8,1 byte1 byte 对齐7 bytes8 bytes
long: u64,8 bytes8 bytes 对齐8 bytes
short: u16,2 bytes2 bytes 对齐6 bytes8 bytes
}
共 32 bytes

这个表展现了 Rust 结构体在#[repr(C)]下的内存对齐和填充:
- 代码是最左边的这列,使用了repr(C)注解。结构体里面有好几个字段

  • Rust编译器首先看tiny字段是bool类型的,占1bit内存,就会对齐到1字节

  • 编译器接着看normal字段是u32类型的,占4字节,所以对齐到4字节即可。这时候Rust发现tiny字段对齐到的是1字节,所以编译器就会填充3字节让tiny字段占4字节

  • 由于这个字段刚好占了8字节,是4字节的整数倍,所以已经对齐了

  • small字段是u8类型,占1字节,对齐到1字节。由于上面的两个字段已经对齐了,所以Rust编译器会根据下文的字节来判断给它填充多少字节。判断到这里Rust编译器还得观望一下。

  • longu64类型,占8字节,自然就是8字节对齐。它的字段是8字节及以上。此时我们看tinynormal组成了8字节对齐,long也是8字节对齐,Rust明白了现在的情况是应该按8字节对齐。那就只能给small字段填充7个字节补成一个8字节对齐了。

  • shortu16类型,占2字节,由于现在的情况是应该按8字节对齐,所以编译器会给它补6字节合成8字节对齐。

其过程用表格表述就是:

字段类型大小需要的对齐填充情况备注
tiny: bool1 bit1 byte3 bytes 填充为了对齐下一个u32
normal: u324 bytes4 bytes无填充u32对齐
small: u81 byte1 byte7 bytes 填充为了对齐下一个u64
long: u648 bytes8 bytes无填充8 字节对齐
short: u162 bytes2 bytes6 bytes 填充8字节对齐结构体
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/24 20:41:36

Locale-Emulator终极指南:三步让日文游戏告别乱码困扰

Locale-Emulator终极指南:三步让日文游戏告别乱码困扰 【免费下载链接】Locale-Emulator Yet Another System Region and Language Simulator 项目地址: https://gitcode.com/gh_mirrors/lo/Locale-Emulator 你是否曾经满怀期待地下载了一款心仪的日文游戏&a…

作者头像 李华
网站建设 2026/7/24 20:41:03

AssetRipper终极指南:轻松提取Unity游戏资源的免费开源神器

AssetRipper终极指南:轻松提取Unity游戏资源的免费开源神器 【免费下载链接】AssetRipper GUI application to analyze game files 项目地址: https://gitcode.com/GitHub_Trending/as/AssetRipper 你是否曾经遇到过这样的困境?想要分析一个Unity…

作者头像 李华
网站建设 2026/7/24 20:38:43

鸿蒙多功能工具箱开发实战(十六)-实时行情数据获取与展示

鸿蒙多功能工具箱开发实战(十六)-实时行情数据获取与展示 前言 实时行情数据展示需要网络请求和数据刷新机制。本文将讲解HarmonyOS网络请求API和行情数据展示的实现。 一、网络请求封装 1.1 HTTP服务封装 import http from ohos.net.httpexport class HttpService {private st…

作者头像 李华
网站建设 2026/7/24 20:37:30

高精度减法计算

类型 范围 int -10的9次方到+10的9次方 char 无限 long long -10的18次方到+10的18次方 long int -10的12次方到+10的2次方 如果一个数超过了long long的范围,我们就可以先用char读入,然后存入int数组,再进行计算。 1 777 2 777 * …

作者头像 李华
网站建设 2026/7/24 20:37:27

高精度加法计算

类型 范围 int char long long long int -10的9次方到+10的18次方 无限 -10的18次方到+10的18次方 -10的12次方到+10的12次方

作者头像 李华
网站建设 2026/7/24 20:34:22

BetterNCM安装器:3分钟解锁网易云音乐的无限可能

BetterNCM安装器:3分钟解锁网易云音乐的无限可能 【免费下载链接】BetterNCM-Installer 一键安装 Better 系软件 项目地址: https://gitcode.com/gh_mirrors/be/BetterNCM-Installer 你是否曾想过让网易云音乐变得更强大、更个性化?BetterNCM安装…

作者头像 李华