Is this a bug, or a feature?

BlitzMax Forums/BlitzMax Programming/Is this a bug, or a feature?

Scaremonger(Posted 2014) [#1]
Take the following code:
Type MTYPE
Field size:Int = SizeOf(MTYPE)	'# 4 Byte
Field hInstance:Int		'# 4 Byte
Field param:Short		'# 2 Byte
				'# --------
End Type			'# 10 BYTES

Type HTYPE
Field size:Int = SizeOf(HTYPE)	'# 4 Byte
Field hInstance:Int		'# 4 Byte
Field param:Short		'# 2 Byte
Field line:Int			'# 4 Byte
				'# --------
End Type			'# 14 BYTES
 
Print "MTYPE SIZE=" + SizeOf( MTYPE )
Print "HTYPE SIZE=" + SizeOf( HTYPE )


Given that an INT is 4 bytes and a SHORT is 2 bytes, the types should be 10 bytes and 14 bytes, but run it and you get this:
Building untitled1
Compiling:untitled1.bmx
flat assembler  version 1.69.14  (984971 kilobytes memory)
3 passes, 4483 bytes.
Linking:untitled1.debug.exe
Executing:untitled1.debug.exe
MTYPE SIZE=10
HTYPE SIZE=16

Process complete


BlitzMax says they are 10 and 16 bytes in size!

This causes problems it you are overlapping a C STRUC onto a BlitzMax type.


GfK(Posted 2014) [#2]
Odd... I just tried swapping the declaration order around so that all the Ints are first, and the Shorts last.

What do you make of that??!


Yasha(Posted 2014) [#3]
This is caused by "alignment". BlitzMax is aligning the fields to a memory address boundary where they can be most efficiently accessed by the CPU. In general, this means that they have to sit on an address that is a whole multiple of their size. So ints will always be put on a 4x address. If "line" had been a short instead of an int, it would likely be put in the final two bytes, because the size of the fields up to the end of "param" is a clean multiple of 2 (I assume Max will do this, anyway), but because it's a 4-byte type, it has to go on the 4-byte boundary and thus the intermediate two bytes are useless.

A C compiler would actually almost certainly do exactly the same thing (and I think in C11, the latest language version, it is mandatory) unless you used a compiler-specific extension like the GCC "packed" attribute.

Alignment is essential for good performance on x86, and on some platforms (such as ARM) it's essential for the program to work at all (there was a bug in iMiniB3D a while back that caused it to crash when loading models on iOS for this very reason), which is why C does this too. The latest C standard includes an _Alignof operator to help you work with this requirement.

A non-aligned data type can still be accessed, but usually it means the CPU has to break it into component bytes and read them individually, then recombine them afterwards, making it very inefficient by comparison. x86 does this automatically behind the scenes; fixing iMiniB3D needed a manual function to be added to do this explicitly (mobile processors don't have the same kind of backing power to waste on detecting your mistakes at runtime).


GfK(Posted 2014) [#4]
So in laymen's terms, don't rely on an accurate response from SizeOf()?


Yasha(Posted 2014) [#5]
Pretty much... I think SizeOf only really tells you the number of bytes you would hypothetically need to loop over from start to finish to cover the whole object's useful content.

It also doesn't include the 8-byte object header (used internally by Max), or the memory allocator's minimum block size (which I think is 16 bytes), so if you wanted to work out memory use you'd need to add the header and then round up to the block boundary... and of course would be completely different in BCC-NG which has different internals.


GW(Posted 2014) [#6]
So in laymen's terms, don't rely on an accurate response from SizeOf()?

No, sizeof is giving the correct result.


Scaremonger(Posted 2014) [#7]
Its one to watch out for when converting libraries from C. I bumped into it whilst messing around with a STRUCT defined in the Windows API. Took me hours to figure out why it didn't work.


Brucey(Posted 2014) [#8]
This causes problems it you are overlapping a C STRUC onto a BlitzMax type.

BCC-NG produces this in 32-bit :

MTYPE SIZE=12
HTYPE SIZE=16

struct _MTYPE_obj {
	struct BBClass__MTYPE* clas;
	BBINT __mtype_size;
	BBINT __mtype_hinstance;
	BBSHORT __mtype_param;
};

12 = 16 - (4 byte clas pointer)

struct _HTYPE_obj {
	struct BBClass__HTYPE* clas;
	BBINT __htype_size;
	BBINT __htype_hinstance;
	BBSHORT __htype_param;
	BBINT __htype_line;
};

16 = 20 - (4 byte clas pointer)


All that gets thrown out of the window when you move to 64-bit too...

MTYPE SIZE=16
HTYPE SIZE=16

I haven't been able to reliably do any of the memcopy "hacks" that Mark uses in some of the modules for overlaying C structs - like in the FreeType module.
So I've had to change the code to work off some glue code instead.
This is what makes the Win32 modules code a pain to port for 64-bit as it will need a rewrite to work on 64-bit...

It also doesn't include the 8-byte object header

Casting an Object to a Byte Ptr will return the pointer after the header. On BMX-NG, I only use a 4-byte header for 32-bit, and an 8-byte header for 64-bit - since I don't need the ref counter.