Weird size of structures with equivalent byte size

Hi! I was writing some code for a file parser, and then I noticed something really weird, I was checking to the size of the structure be equal to the structure we will use, for this I use std::mem::size_of, but after change some part of the structure, which should have the same size (change two u32 for one u64), the first one has a size of 252 bytes, and after change to u64 changes to 256................

I try reduce most I can the code, but any change from here causes to hide this.
Is for a game, you will notice by the fields :slight_smile:

pub struct ItemModel {
        pub r#type: u32,
        pub id: u32,
        pub name_offset: u32,
        pub unknown_01: [u16; 59],
        pub unknown_03: u32,
        pub unknown_04: u32,
        pub unknown_05: u32,
        pub unknown_06: u32,
        pub unknown_07: u32,
        pub unknown_08: u32,
        pub chara_user: u32,
        pub unknown_02: u32,
        pub type_item: u16,
        pub max_count: u16,
        pub buy_price: u32,
        pub sell_price: u32,
        pub stats: Stats,
        pub unknown_44: [u16; 10],
        pub chip_level: u16,
        pub ability: u16,
        pub desc_offset: u32,
}

pub struct Stats {
        pub hit_points: i32,
        pub unknown_01: i32,
        pub skill_points: i32,
        pub strength: i32,
        pub vitality: i32,
        pub intelligence: i32,
        pub mentality: i32,
        pub agility: i32,
        pub technique: i32,
        pub unknown_02: i32,
        pub luck: i32,
        pub movement: i32,
}

fn main() {
        println!("Item1 {}", std::mem::size_of::<ItemModel>());
}

Run the code, the output will be 252.

Change:

       pub unknown_03: u32,
       pub unknown_04: u32,

To:

        pub unknown_03: u64,

Run again the code, and magically, the size now is 256! I tested this on the RustPlayground... no idea why this happens....

Thx!

u64 has an alignment of 8, so the struct's size has to be a multiple of 8. It gets 4 bytes of padding to achieve this.

2 Likes

u64 has a platform-dependent alignment which may be up to 8 bytes. This requires u64 to be placed at addresses which are a multiple of 8. Alignment requirements for a type have to propagate up to the structs that contain that type, so your struct ItemModel gains an alignment of 8, which means its size must be a multiple of 8. Padding (unused, uninitialized memory) is inserted in the struct layout in order to ensure this.

It sounds like you are trying to write a struct that matches the layout of a file on disk. This is a difficult thing to do — even discounting big-endian machines as irrelevant in the modern day, there are a lot of subtleties and ways to get it wrong. You should consider just reading the bytes explicitly and copying them into structs the boring way.

If you want to do it anyway, follow these principles:

  • Add #[repr(C)] to your structs. This tells the compiler not to reorder fields for efficiency; without it, you cannot rely on the struct matching your file at all.
    • Either lay out your structs so that padding cannot appear (e.g. a u64 must start at an offset that is a multiple of 8), or
    • use #[repr(C, packed)] to tell the compiler not to insert any padding. (This will disable the ability to take references to the fields of the structs, so it is not preferable.)
  • DO NOT write your own unsafe code to create the struct from the file data. Instead, use bytemuck or zerocopy to do it, which will check the necessary safety properties (including that you used #[repr(C)]) — you may still get nonsense if the struct is defined wrong, but at least your program won’t have a security vulnerability due to memory corruption.
6 Likes

:open_mouth: I didn't know it, so the used size is different from how much bytes it "contains".

What I was doing is get the size to check the number of bytes read in a file with:

#[br(assert(std::mem::size_of::<T>().eq(&TryInto::<usize>::try_into(row_size).unwrap())))

Is there any similar function for size, which just give us the number of bytes of the structure without take in consideration alignment? The idea is for a known number of bytes to be read, the struct need to store the same number of them.

No, that isn't a concept built into the language. But if you use (or write) a derive-macro library that generates parsing functions from a struct definition, then it would be able to know how many bytes of data to expect. I am sure there are such libraries, but I haven’t written any binary file loaders in Rust so I can't recommend one.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.