Memory Alignment

Apr 29th 2018

I was reviewing some code the other day which did the following:

//convert bytes to float
uint8_t data[4]; 

float getFloat(uint8_t *ptrBytes)
{
	uint8_t i;
	for (i=0; i<4; i++) 
	{
		data[i]=*ptrBytes++;

	}

	return *((float *)data);
}

First off the return casting of the uint8_t data[] array to a float is wrong. That is processors like the cortex M series require that at float be on a 4 byte address boundary, where 8bit values can be on any boundary.  Hence the casting of the 8bit value to a float could cause a unaligned memory access failure.  

In general you should never cast to pointers to types that are larger in size. If you absolutely have to then you should make sure you check for the required memory alignment.

Second issue with the code is that the ptrBytes is incremented in the function. So when the function returns the pointer is pointing to a different location.  When someone reads this code they then are forced to ask themselves, was this intentional or a mistake?  What if the next guy sees this nice function to get a float from a pointer and uses it assuming the pointer is not modified? 

Third there is an assumption on if the processor is big endian or little endian. Which again might not be a problem on this processor but it is definitely not portable code. 

A minor thing I dislike is *ptrBytes++, it is difficult for programmers (aka me) to remember the order of operations. That is does the increment happen before or after the dereference.  So why write code that might confuse people? Do we think this is faster or better than:

data[i]=*ptrData;
ptrData++; 

In this case it is clear that the increment happens after and the compilers are generally smart enough to generate the same code. 

Finally the interesting thing is that the code worked. I figured that when I compiled the code with optimizations turned on for size the code would fail because the data[] array would be allocated unaligned to save SRAM space. I was wrong... 

As it turns out compiling for size is for code space savings, and accessing the data array on a 32 bit memory alignment takes less code space than it would if it was unaligned (on the Cortex M processors).   So what if I did the following:

uint8_t x; <br>uint8_t y;<br>uint8_t z;  

Would all three be aligned to 32 bit word boundary?  

Yes

Again it takes less code space to access a 8bit value on a word boundary for the Cortex M, hence each 8bit byte is actually uses the same SRAM space as a 32bit word!  So if you are trying to use 8bit or 16bit data types to save SRAM code space you might want to check that your assumption that it does.  

So more importantly how should you write an improved version of the code?

// assume we want to use the same endian of the machine
float getFloat(uint8_t *const ptrBytes)
{
	union {<br>		float f;<br>		uint8_t c[4];
	} u;

	uint8_t i;
	for (i=0; i<4; i++) 
	{
		u.c[i]=ptrBytes[i];

	}

	return u.f;

}<br>