Type member alignment; unions

Structures

A structure is a type that has member variables. Most of these types are user-defined types.

 

Only these types don't have member variables:

byte - 1 byte.

word - 2 bytes.

int, lpstr, pointer, interface pointer - 4 bytes.

long, double - 8 bytes.

Default alignment

Structure members are placed in memory in the order they are defined. In the following example, offset of a is 0, offset of b is 4 (because size of a is 4), and size of structure is 8.

 

type T1 int'a int'b
T1 t
out "%i %i   %i" &t.a-&t &t.b-&t sizeof(t)

 

Assuming that all members are not structures, these rules are applied:

 

For the above reason, in some cases there may be some padding (1-7 unused bytes) between members and at the end. In the following example, offset of a is 0, offset of b is 4 (3-byte padding after a), and size of structure is 12 (2-byte padding at the end).

 

type T2 byte'a int'b word'c
T2 t
out "%i %i %i   %i" &t.a-&t &t.b-&t &t.c-&t sizeof(t)

Nonstandard alignment

Some types use nonstandard member alignment. In C/C++ languages, to set it is used #pragma pack. It can set alignment 1, 2, 4 or 8 (default). Structure members are aligned so that their offsets are divisible by their sizes or by the alignment value, whichever is smaller.

 

QM 2.3.2. To set alignment, add [pack1], [pack2], [pack4] or [pack8] (default) at the end of type definition. Example:

 

type T3 byte'a int'b word'c [pack1]
T3 t
out "%i %i %i   %i" &t.a-&t &t.b-&t &t.c-&t sizeof(t)

Explicit alignment; unions

You can explicitly specify offsets of some members. It allows you to define unions (structures with overlapped members of different types) and structures with nonstandard member alignment. The member declaration must be preceded by the offset (integer number) in square brackets. Example:

 

type LHWORD int'i [0]word'lo [2]word'hi
LHWORD lh.i=0x00050007
out "LOWORD is %i, HIWORD is %i" lh.lo lh.hi
 Output: LOWORD is 7, HIWORD is 5

 

If offset begins with +, it is interpreted as offset from offset of previous member. Empty brackets means same offset as of previous member. Example:

 

type LHWORD int'i []word'lo [+2]word'hi

 

If offsets of all members are explicitly set, the size of the type also is explicitly set. In the following example it is 6 (normally it would be 8, because i would be at 4-byte offset):

 

type TYPE [0]word'w [2]int'i

 

QM 2.2.0. Implicit and explicit constructor/destructor/copy functions are not called for union members (except of the VARIANT type). A warning is shown. QM cannot know which union member is valid at the time. Clearing wrong member may be catastrophic. These members should be cleared explicitly. For example, if an union member is an interface pointer, and you know it is valid at the time, call its hidden function Release to clear it. For BSTR, call SysFreeString. For ARRAY - SafeArrayDestroy. Also, there are special functions for some types, for example ReleaseStgMedium clears variables of STGMEDIUM type. All this is documented in MSDN Library.

Anonymous types within types

You probably already know that members of user-defined types can be other user-defined types. Example:

 

type T1 int'a int'b
type T2 int'x T1'y
T2 t
t.y.a=5

 

It is also possible to define types within types (QM 2.2.0). It can be used to simplify conversion between C++ and QM declarations, because in C++ it is used to define complex unions. These nested types don't have a type name and a member name, but can have specified offset. Their members are accessed directly. To define such nested types, enclose their members in {}. Examples:

 

type T2 int'x {int'a int'b}
T2 t
t.a=5

type LHWORD int'i []{word'lo word'hi}
LHWORD lh.i=0x00050007
out "LOWORD is %i, HIWORD is %i" lh.lo lh.hi