struct — Native struct and union helpers

This module, designed for use in conjunction with the native module, allows C structs and unions to be declared dynamically, to calculate the offset of struct fields and the size of structs and unions. This is useful for reading and writing struct fields, as well as indexing into arrays of structs and unions.

The major advantage to using this module is that it attempts to get field alignment right, inserting padding bytes where necessary to match the C compiler.

Note

We say “attempts to get alignment right” because alignment is hard. It varies across different architectures, platforms and compilers, and there are a lot of corner cases. If you find that the alignment is broken in a particular case, please file a bug report.

For example, consider the following C struct definition:

struct person
{
    char gender;        // Male or female.
    short country;      // Country ID number.
    double age;         // Years.
    int height;         // Centimetres.
};

Accessing the fields of this struct from Mars is non-trivial, due to padding. On 64-bit Linux, double has a size and alignment of 8, and int has a size and alignment of 4, so the following constraints apply:

  • gender will always be at offset 0, since it is first.
  • country will be at offset 2, since it must be 2-byte aligned. So there will be a padding byte after gender.
  • age will be at offset 8, since it must be 8-byte aligned. So there will be 4 padding bytes after country.
  • height will be at offset 16, right after age.
  • There will be 4 padding bytes after height, to round the struct up to a multiple of 8 (due to the presence of the double earlier on). So the whole struct will have a size of 24 bytes.

However, on 32-bit Linux, double has 4-byte alignment, so you cannot assume these offsets. This module helps you calculate the correct offsets of struct fields with the correct alignment. To do so, simply call build_struct_prims with a string that denotes the primitive types of the struct fields, and store it in a global constant:

def struct_person :: StructDef = build_struct_prims("bhdi")

Now, the sizeof_struct and struct_fieldoffset functions can provide information about how to access struct fields:

?> struct_fieldoffset(struct_person, 0)
0
?> struct_fieldoffset(struct_person, 1)
2
?> struct_fieldoffset(struct_person, 2)
8
?> struct_fieldoffset(struct_person, 3)
16
?> sizeof_struct(struct_person)
24

If you have a pointer to such a struct, you can access a field using cptr_add, and adding one of the field offsets. Or, you can use the convenience function struct_fieldptr to do just that. If you are accessing an array of structs, you should use sizeof_struct to find an individual struct element of the array.

The same concept applies for unions, but they are much simpler. Because all elements of a union are found at offset 0, there is no union_fieldoffset or union_fieldptr function. However, you can still use this to compute the size of a union (simply the maximum size of all of the union’s fields).

type AlignInfo
AlignInfo(size :: Num, alignment :: Num)

Stores information about the size and alignment of a native type (either a primitive type, or a struct or union).

  • size: The size of the type in bytes. Must be a multiple of alignment.
  • alignment: Elements of this type must be stored at an address which is a multiple of this number. Must be >0.
aligninfo_cchar :: AlignInfo

The alignment info for CChar, for use with build_struct.

aligninfo_cshort :: AlignInfo

The alignment info for CShort, for use with build_struct.

aligninfo_cint :: AlignInfo

The alignment info for CInt, for use with build_struct.

aligninfo_cfloat :: AlignInfo

The alignment info for CFloat, for use with build_struct.

aligninfo_cdouble :: AlignInfo

The alignment info for CDouble, for use with build_struct.

aligninfo_cptr :: AlignInfo

The alignment info for CPtr, for use with build_struct.

type StructDef

Stores the computed field offsets and size of a struct.

build_struct_prims(format :: Array(Num)) :: StructDef

Builds a StructDef for a struct with only primitive fields from a format string describing the fields of the native struct.

Note

This function does all the expensive work of computing the size and offsets, storing the result in the StructDef. The subsequent operations are cheap lookups. Therefore, it is recommended that you call build_struct_prims once for each C struct and store the result in a global constant for quick recall.

Warning

Do not use this to describe structs with structs inside them (flattening out the fields); this may produce incorrect alignment. Instead, use build_struct.

format is a string containing a character for each struct field. The character indicates the primitive type of the field, as shown in this table.

Format character C type Mars type
b char CChar
h short CShort
i int CInt
f float CFloat
d double CDouble
p void* CPtr
build_struct(fields :: Array(AlignInfo)) :: StructDef

Builds a StructDef for a struct with arbitrary fields. This allows structs to be specified with fields of struct or union types.

fields is a list of alignment infos, each describing a field of primitive, struct, or union type. For a primitive type, pass the aligninfo_* constant for that type. For example, build_struct([aligninfo_int, aligninfo_char]) is equivalent to build_struct_prims("ib"). For a struct type, pass the result of aligninfo_struct. For a union type, pass the result of aligninfo_union.

For example, consider the following C struct definitions:

struct inner
{
    char a;
    int b;
};

struct outer
{
    char x;
    char y;
    struct inner z;
};

This can be described in Mars as follows:

def struct_inner :: StructDef = build_struct_prims("bi")
def struct_outer :: StructDef = build_struct([aligninfo_cchar, aligninfo_cchar, aligninfo_struct(struct_inner)])

Note that you cannot simply write build_struct_prims("bbbi") because the inner struct must be aligned to a 4-byte boundary, even though it starts with a char.

sizeof_struct(struct :: StructDef) :: Num

Returns the size of a native struct, including any padding bytes. This is equivalent to the C expression applying sizeof to the struct type.

aligninfo_struct(struct :: StructDef) :: AlignInfo

Returns the alignment info for a native struct, for use with build_struct.

struct_fieldoffset(struct :: StructDef, fieldnum :: Num) :: Num

Returns the offset of struct field number fieldnum within a native struct, in bytes. If p is a pointer to a value of the struct type, this is the number of bytes after p where the given field may be found.

struct_fieldptr(struct :: StructDef, pointer :: CPtr, fieldnum :: Num) :: CPtr

Helper method for finding the pointer to a struct field fieldnum. If pointer is a pointer to a value of the struct type, this returns a pointer to the given field.

This is equivalent to cptr_add(pointer, struct_fieldoffset(struct, fieldnum)).

type UnionDef

Stores the computed size of a union.

build_union_prims(format :: Array(Num)) :: UnionDef

Builds a UnionDef for a union with only primitive fields from a format string describing the fields of the native union.

Note

This function does all the expensive work of computing the size, storing the result in the UnionDef. The subsequent operations are cheap lookups. Therefore, it is recommended that you call build_union_prims once for each C union and store the result in a global constant for quick recall.

format is a string containing a character for each union field. See build_struct_prims for an explanation of this string’s syntax.

build_union(fields :: Array(AlignInfo)) :: UnionDef

Builds a UnionDef for a union with arbitrary fields. This allows unions to be specified with fields of struct or union types.

fields is a list of alignment infos, each describing a field of primitive, struct, or union type. See build_struct for more information.

sizeof_union(union :: UnionDef) :: Num

Returns the size of a native union. This is equivalent to the C expression applying sizeof to the union type.

aligninfo_union(union :: UnionDef) :: AlignInfo

Returns the alignment info for a union, for use with build_struct.

Previous topic

set — Unordered set type

Next topic

unittest — Testing user code

This Page