Rust: 从内存地址信息看内存布局

内存布局其实有几个:address(地址)、size(大小)、alignment(对齐位数,2 的自然数次幂,2,4,8...)。

今天主要从address来看内存的布局。

说明:下面以Struct,Enum以默认对齐的情况下(不包括C对齐即#[repr( C )]、 紧凑对齐 #[repr(packed)]、自定义对齐 #[repr(align(n))] 等情况)进行分析。

一、代码

bash 复制代码
#[derive(Default)]
struct BaseStruct {
    field1: u8,
    field2: u8,
    field3: i32,
    field4: u8,
    field5: u8,
    field6: u8,
    field7: u8,
}

struct MyStruct{
    field1:u8,
    field2:BaseStruct,
    field3:MyEnum,
    field4:i64,
}
impl MyStruct{
    fn default() -> Self {
        MyStruct {
            field1: 1,
            field2: BaseStruct::default(),
            field3: MyEnum::Variant1(1),
            field4: 2,
        }
    }
}
// Enum的内存大小:最大字段大小size+8个字节,下面最大为i64:8个字节 +8 =16

enum MyEnum {
    Variant1(i64),
    Variant2(i8),
    Variant3(i64),
    Variant4(i64),
}

fn main() {
    println!("Size of u64                   : {}", std::mem::size_of::<u64>());
    println!("Size of i64                   : {}", std::mem::size_of::<i64>());
    println!("Size of MyEnum                : {}", std::mem::size_of::<MyEnum>());
    println!("Align of MyEnum               : {}", std::mem::align_of::<MyEnum>());
    println!("Size of BaseStruct            : {}", std::mem::size_of::<BaseStruct>());
    println!("Align of BaseStruct           : {}", std::mem::align_of::<BaseStruct>());
    println!("Size of MyStruct              : {}", std::mem::size_of::<MyStruct>());
    println!("Align of MyStruct             : {}", std::mem::align_of::<MyStruct>());
    println!("-------------BaseStruct-------------");
    let base_struct = BaseStruct::default();
    println!("address of base_struct        : {}",get_ptr_address(&base_struct));
    println!("address of base_struct_field_1: {}",get_ptr_address(&base_struct.field1));
    println!("address of base_struct_field_2: {}",get_ptr_address(&base_struct.field2));
    println!("address of base_struct_field_3: {}",get_ptr_address(&base_struct.field3));
    println!("address of base_struct_field_4: {}",get_ptr_address(&base_struct.field4));
    println!("address of base_struct_field_5: {}",get_ptr_address(&base_struct.field5));
    println!("address of base_struct_field_6: {}",get_ptr_address(&base_struct.field6));
    println!("address of base_struct_field_7: {}",get_ptr_address(&base_struct.field7));
    
    println!("-------------MyStruct-------------");
    let my_struct = MyStruct::default();
    println!("address of my_struct          : {}",get_ptr_address(&my_struct));
    println!("address of my_struct_field_1  : {}",get_ptr_address(&my_struct.field1));
    println!("address of my_struct_field_2  : {}",get_ptr_address(&my_struct.field2));
    println!("address of my_struct_field_3  : {}",get_ptr_address(&my_struct.field3));
    println!("address of my_struct_field_4  : {}",get_ptr_address(&my_struct.field4));
    println!("-----------get_ptr_address_2-----------");
    println!("address_2 of my_struct          : {}",get_ptr_address_2(&my_struct));
    println!("address_2 of my_struct_field_1  : {}",get_ptr_address_2(&my_struct.field1));
    println!("address_2 of my_struct_field_2  : {}",get_ptr_address_2(&my_struct.field2));
    println!("address_2 of my_struct_field_3  : {}",get_ptr_address_2(&my_struct.field3));
    println!("address_2 of my_struct_field_4  : {}",get_ptr_address_2(&my_struct.field4));


}

fn get_ptr_address<T>(data: &T)->usize{
    data as *const _ as usize //会先创建一个引用
}
// 为什么不能用这个?
fn get_ptr_address_2<T>(data: &T)->usize{
    std::ptr::addr_of!(data) as usize //不需要创建一个引用
}

二、输出

bash 复制代码
Size of u64                   : 8
Size of i64                   : 8
Size of MyEnum                : 16
Align of MyEnum               : 8
Size of BaseStruct            : 12
Align of BaseStruct           : 4
Size of MyStruct              : 40
Align of MyStruct             : 8
-------------BaseStruct-------------
address of base_struct        : 490357126640
address of base_struct_field_1: 490357126644
address of base_struct_field_2: 490357126645
address of base_struct_field_3: 490357126640
address of base_struct_field_4: 490357126646
address of base_struct_field_5: 490357126647
address of base_struct_field_6: 490357126648
address of base_struct_field_7: 490357126649
-------------MyStruct-------------
address of my_struct          : 490357126600
address of my_struct_field_1  : 490357126636
address of my_struct_field_2  : 490357126624  // basestruct
address of my_struct_field_3  : 490357126600  // myenum
address of my_struct_field_4  : 490357126616
-----------get_ptr_address_2-----------
address_2 of my_struct          : 490357126528
address_2 of my_struct_field_1  : 490357126528
address_2 of my_struct_field_2  : 490357126528
address_2 of my_struct_field_3  : 490357126528
address_2 of my_struct_field_4  : 490357126528

三、问题

可以对照一下address的顺序,来看一下各个field以及对应Struct、Enum的大小。

1、为什么BaseStruct的大小并不是field的起始地址到field的最后地址?

BaseStruct的真实size是12个字节。但起始address起始间隔目前只看到9。为什么?

从地址信息可以看出,

(1)field3(i32),重新布局后,已经放在前面,并不是从field1开始。地址是从490357126640->490357126644:占了4个字节。

(2)接下来是field1,field2,field4,field5,依次占了1个字节。

(3)再接下来是field6,field7,各占1个字节。

因为在进行内存布局优化时,已经按4+4+4的格局进行优化,这里面4就是BaseStruct alignment。

在最后4个字节中,field6,field7已经用掉了2个,还有2个空的(alignment padding)。因此是4+4+2+2=12。

其实:BaseStruct如果没有field7,或者说,再加一个field8(u8),其size均是12个字节!

这个在MyStruct中各field的地址信息,可以更真实显示BaseStruct(field2)的大小(即上下间隔)。即490357126624 ->490357126636(注:每次运行不一样)间隔为12。

当然,MyStruct也是一样,不能把field首地址和未地址相减得到其占用大小(计算得到36个字节,并不是40个字节!)。它的alignment是8。

因此需要注意的是:最后一个,是有alignment的。有些是只有部分占用。因此不能简单通过首地址和末地址相减来获得这个大小。

2、为什么add_of!在这儿不能用?

bash 复制代码
fn get_ptr_address_2<T>(data: &T)->usize{
    std::ptr::addr_of!(data) as usize //不需要创建一个引用
}

add_of宏生成了同样的地址。

只需要进行下面的修改就可以了。add_of宏不需要是对象的引用,直接用对象本身,不用担心会消费掉所有权。

bash 复制代码
println!("address_2 of my_struct          : {}",std::ptr::addr_of!(my_struct) as usize);
println!("address_2 of my_struct_field_1  : {}",std::ptr::addr_of!(my_struct.field1) as usize);
println!("address_2 of my_struct_field_2  : {}",std::ptr::addr_of!(my_struct.field2) as usize);
println!("address_2 of my_struct_field_3  : {}",std::ptr::addr_of!(my_struct.field3) as usize);
println!("address_2 of my_struct_field_4  : {}",std::ptr::addr_of!(my_struct.field4) as usize);

上面的修改和

bash 复制代码
fn get_ptr_address<T>(data: &T)->usize{
    data as *const _ as usize //会先创建一个引用
}

效果一样。

相关推荐
张北北.21 分钟前
【深入底层】C++开发简历4+4技能描述6
java·开发语言·c++
程序视点32 分钟前
望言OCR 2025终极评测:免费版VS专业版全方位对比(含免费下载)
前端·后端·github
李永奉43 分钟前
STM32-定时器的基本定时/计数功能实现配置教程(寄存器版)
c语言·开发语言·stm32·单片机·嵌入式硬件
rannn_1111 小时前
Java学习|黑马笔记|Day23】网络编程、反射、动态代理
java·笔记·后端·学习
go54631584651 小时前
中文语音识别与偏误检测系统开发
开发语言·人工智能·学习·生成对抗网络·数学建模·语音识别
NUC_Dodamce1 小时前
Cocos3x 解决同时勾选 适配屏幕宽度和 适配屏幕高度导致Widget组件失效的问题
开发语言·javascript·ecmascript
一杯科技拿铁1 小时前
Go 的时间包:理解单调时间与挂钟时间
开发语言·后端·golang
独泪了无痕1 小时前
Hutool之CollStreamUtil:集合流操作的神器
后端
小白学大数据1 小时前
基于Python的新闻爬虫:实时追踪行业动态
开发语言·爬虫·python
freed_Day1 小时前
python面向对象编程详解
开发语言·python