Type layout
Size, alignment and field offsets are determined by the selected target ABI.
Size
sizeof(T) returns the ABI allocation size of a type as nuint.
nuint integerSize = sizeof(int);
nuint characterSize = sizeof(char); // always 4
nuint vectorSize = sizeof(Vector3);
Alignment
alignof(T) returns the ABI alignment required for values of a type.
nuint alignment = alignof(Vector3);
Field offset
offsetof(T, Field) returns the byte offset of a struct field from the beginning of its containing value.
nuint yOffset = offsetof(Vector3, Y);
sizeof(Vector3)alignof(Vector3)offsetof(Vector3, Y)Every result describes the selected compilation target, including cross-target object generation.
Target-dependent constants
Layout queries can appear in module, struct and local const declarations, enum initializers and case labels. Their values are resolved for the selected target ABI before output is emitted.
struct Header
{
public byte Tag;
public int Count;
}
enum HeaderLayout : nuint
{
Size = sizeof(Header),
Alignment = alignof(Header),
CountOffset = offsetof(Header, Count)
}
bool IsPointerSized(nuint bytes)
{
const nuint PointerBytes = sizeof(nint);
switch (bytes)
{
case PointerBytes:
return true;
default:
return false;
}
}
Sizes and offsets include ABI padding, inherited fields and virtual dispatch storage. nint and nuint follow pointer width; clong and culong follow the target C ABI. For example, C long is 32 bits on x64 Windows and 64 bits on x64 Linux.
A struct carries runtime dispatch storage when its own declaration requires it (interfaces, virtual or abstract members, or a virtual destructor), or when it inherits that storage from a polymorphic base. Polymorphic descendants never add storage to their ancestors. See stable inheritance layout.
Native integer casts, arithmetic and automatic enum successors use those target values. Overflow, invalid constant operations and duplicate switch values are checked again for that target, with diagnostics at the source location. A failed target check prevents emission.
case sizeof(nint): and case cast<nuint>(4): are duplicate labels on a 32-bit target, but distinct on a 64-bit target. Neither label is a runtime calculation.
Stable inheritance layout
A type's physical layout depends on its declaration, its base and field layouts, and the selected ABI. Adding a derived type never changes an existing base's size, alignment, field offsets, array stride or function ABI. The same invariant applies when the descendant is declared in another source file; computing a base layout does not require discovering its descendants.
struct Base { public int Value; }
interface IFoo { int Foo(); }
struct Derived : Base, IFoo
{
public int Foo() { return Value; }
}
const nuint BaseSize = sizeof(Base); // 4, with or without Derived
const nuint BaseAlignment = alignof(Base); // 4
const nuint ValueOffset = offsetof(Base, Value); // 0
The complete base subobject occupies offset zero, including its ABI tail padding. A descendant does not reuse that padding for its fields or metadata. Its own fields follow the base subobject and any newly introduced dispatch storage, with target-required alignment.
The first polymorphic type in a chain introduces one dispatch pointer: at offset zero if it has no base, or after the complete non-polymorphic base otherwise. Later descendants reuse that same storage location and initialize it with their own runtime table. Virtual calls, interface conversion and virtual destruction all locate it through the type that introduced it; they do not assume it is always the first word.
Derived* to Base* and derived-to-base reference binding preserve the numeric address. Inherited field offsets match those of the standalone base. See pointer and array constraints and runtime interface dispatch.
Selecting the target
The CLI uses the host ABI unless a target is selected explicitly. Target selection also applies when emitting LLVM IR:
xenon build main.xe --emit-llvm --target i686-pc-windows-msvc
This validates and emits for the selected target; it does not execute the program on that target. Constant evaluation supports the language's constant expressions, not arbitrary user function execution.