From e000ac9d35917f36b4a9e2602831b50acb5341c9 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Tue, 28 Apr 2026 13:37:33 -0700 Subject: [PATCH] type-layout: rewrite `#[repr(C)]` struct layout algorithm Make the code actual compile by defining types, not using a variable named `struct`, adding `mut` where needed, etc. Properly handle the `max()` alignment call returning `Option` rather than `usize` by using 1 for the alignment if there are no fields. Add comments with the parts of the algorithm that are represented by the statements. Also include a runnable example hidden by default. --- src/type-layout.md | 125 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 95 insertions(+), 30 deletions(-) diff --git a/src/type-layout.md b/src/type-layout.md index 078a90e442..275da74e99 100644 --- a/src/type-layout.md +++ b/src/type-layout.md @@ -209,43 +209,108 @@ For each field in declaration order in the struct, first determine the size and Finally, the size of the struct is the current offset rounded up to the nearest multiple of the struct's alignment. -Here is this algorithm described in pseudocode. - - -```rust,ignore -/// Returns the amount of padding needed after `offset` to ensure that the -/// following address will be aligned to `alignment`. -fn padding_needed_for(offset: usize, alignment: usize) -> usize { - let misalignment = offset % alignment; - if misalignment > 0 { - // round up to next multiple of `alignment` - alignment - misalignment - } else { - // already a multiple of `alignment` - 0 - } -} - -struct.alignment = struct.fields().map(|field| field.alignment).max(); +Here is the algorithm: -let current_offset = 0; - -for field in struct.fields_in_declaration_order() { - // Increase the current offset so that it's a multiple of the alignment - // of this field. For the first field, this will always be zero. - // The skipped bytes are called padding bytes. - current_offset += padding_needed_for(current_offset, field.alignment); +```rust +# /// A field of a struct. +# #[derive(Debug)] +struct Field { + alignment: usize, + size: usize, +} +# /// Layout of user-defined structs. +# #[derive(Debug)] +struct MockLayout { +# /// Fields stored in declaration order. + fields: Vec, +# /// Offset of each field from the start of the struct. + field_offsets: Vec, +# /// Overall alignment. + alignment: usize, +# /// Overall size. + size: usize, +} - struct[field].offset = current_offset; +impl MockLayout { + /// Returns the amount of padding needed after `offset` to ensure that the + /// following address will be aligned to `alignment`. + fn padding_needed_for(offset: usize, alignment: usize) -> usize { + let misalignment = offset % alignment; + if misalignment > 0 { + // Round up to next multiple of `alignment`. + alignment - misalignment + } else { + // Already a multiple of `alignment`. + 0 + } + } - current_offset += field.size; + /// Fields must be in declaration order. By this point, they have already + /// had their alignments and sizes calculated. + pub fn from_fields(fields: Vec) -> Self { + // "The alignment of the struct is the alignment of the most-aligned + // field in it, or one if there are no fields." + let alignment = fields + .iter() + .map(|field| field.alignment) + .max() + .unwrap_or(1); + + // "Start with a current offset of 0 bytes." + let mut current_offset = 0; + + let mut field_offsets = vec![]; + for field in &fields { + // "If the current offset is not a multiple of the field's + // alignment, then add padding bytes to the current offset until it + // is a multiple of the field's alignment." + current_offset += Self::padding_needed_for( + current_offset, + field.alignment + ); + + // "The offset for the field is what the current offset is now." + field_offsets.push(current_offset); + + // "Then increase the current offset by the size of the field." + current_offset += field.size; + } + + // "Finally, the size of the struct is the current offset rounded up to + // the nearest multiple of the struct's alignment." + let size = current_offset + Self::padding_needed_for( + current_offset, + alignment + ); + + MockLayout { fields, field_offsets, alignment, size } + } } - -struct.size = current_offset + padding_needed_for(current_offset, struct.alignment); +# +# #[repr(C)] +# struct Demo { +# first: u8, +# second: u32, +# third: u64, +# } +# macro_rules! fields { +# ( $( $t:ty ),+ ) => { +# vec![ +# $( Field { +# alignment: std::mem::align_of::<$t>(), +# size: std::mem::size_of::<$t>(), +# }),+ +# ] +# } +# } +# let fields = fields![u8, u32, u64]; +# let demo_layout = MockLayout::from_fields(fields); +# assert_eq!(std::mem::align_of::(), demo_layout.alignment); +# assert_eq!(std::mem::size_of::(), demo_layout.size); ``` > [!WARNING] -> This pseudocode uses a naive algorithm that ignores overflow issues for the sake of clarity. To perform memory layout computations in actual code, use [`Layout`]. +> This mock implementation uses a naive algorithm that ignores overflow issues for the sake of clarity. To perform memory layout computations in actual code, use [`Layout`]. > [!NOTE] > This algorithm can produce [zero-sized] structs. In C, an empty struct declaration like `struct Foo { }` is illegal. However, both gcc and clang support options to enable such structs, and assign them size zero. C++, in contrast, gives empty structs a size of 1, unless they are inherited from or they are fields that have the `[[no_unique_address]]` attribute, in which case they do not increase the overall size of the struct.