Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 73 additions & 30 deletions src/type-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ r[layout.repr.c.struct]
r[layout.repr.c.struct.align]
The alignment of the struct is the alignment of the most-aligned field in it.

r[layout.repr.c.struct.align-empty]
Structs with no fields are guaranteed to have alignment of 1.

r[layout.repr.c.struct.size-field-offset]
The size and offset of fields is determined by the following algorithm.

Expand All @@ -209,43 +212,83 @@ 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.

<!-- ignore: 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();

let current_offset = 0;
Here is the algorithm:

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
struct Field {
alignment: usize,
size: usize,
}
# /// User-defined structs
struct UserStruct {
# /// Fields stored in declaration order
fields: Vec<Field>,
# /// Offset of each field from the start of the struct
field_offsets: Vec<usize>,
# /// Overall alignment
alignment: usize,
# /// Overall size
size: usize,
}

struct[field].offset = current_offset;
impl UserStruct {
/// 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<Field>) -> Self {
// "The alignment of the struct is the alignment of the most-aligned
// field in it."
let max_alignment = fields.iter().map(|field| field.alignment).max();
// "Structs with no fields are guaranteed to have alignment of 1."
let alignment = max_alignment.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
);

UserStruct { fields, field_offsets, alignment, size }
}
}

struct.size = current_offset + padding_needed_for(current_offset, struct.alignment);
```

> [!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.
Expand Down
Loading