Most protocols use big endian byte ordering
Uh. Yeah. If you say so.
The TCP/IP protocols are all big-endian. That may be "most protocols" :-)
(but anyway, this is about alignment, not endianness. Though you can maybe deal with both at the same time via getlong/putlong macros (but you have to be really careful about keeping to your API definitions.)
Unaligned access is prohibited on most architectures.
supporting unaligned memory accesses was already cheap enough that no-one bothered [with the MIPS hack]
processors with two-byte memory access granularity will throw an exception upon encountering an odd address.
Heh. I love these discussions.
SOME architectures support unaligned accesses in hardware, in which case that's probably what you want to do. Some architectures fault (which you can write a handler for, though that will be incredibly slow) or otherwise to NOT allow unaligned accesses.
ARM is interesting, because there are several different ARM "architectures" - ARMv7m (Cortex M3, M4, and M7) support unaligned accesses "most of the time", but ARMV6m (Cortex M0, M0+) and ARMv8m does "sometimes" (CM33, but not CM23.)
Never ever cast multi-byte types onto buffers. It is a recipe for a dissaster! Always use shifting bytes.
But... If the HW supports it, it's probably the fastest way! Certainly you ought to wrap up such accesses, for portability to "other" architectures...
examine GCC's output to see if it does something smart.
Nope. Or rather, gcc is smart enough to recognize an "obviously unaligned" pointer, and produce icky-looking code (here, for M0):
volatile unsigned int data = *(uint32_t *)&buffer[1];
4: 4b0f ldr r3, [pc, #60] ; (44 <main+0x44>)
6: 1c5c adds r4, r3, #1
8: 785a ldrb r2, [r3, #1]
a: 789b ldrb r3, [r3, #2]
c: 021b lsls r3, r3, #8
e: 4313 orrs r3, r2
10: 78a2 ldrb r2, [r4, #2]
12: 0412 lsls r2, r2, #16
14: 4313 orrs r3, r2
16: 78e2 ldrb r2, [r4, #3]
18: 0612 lsls r2, r2, #24
1a: 4313 orrs r3, r2
1c: 9301 str r3, [sp, #4]
(or for m4, "knowing" that it can:)
volatile unsigned int data = *(uint32_t *)&buffer[1];
4: 4c0a ldr r4, [pc, #40] ; (30 <main+0x30>)
6: f854 3f01 ldr.w r3, [r4, #1]
a: 9301 str r3, [sp, #4]
But it's not smart enough to know that a char* pointer passed to a function might be unaligned:
ATTR uint32_t getlongbasic(uint8_t p[]) {
return *(uint32_t*)p;
0: 6800 ldr r0, [r0, #0]
2: 4770 bx lr
And you need to take special steps (annoyingly kludgey...):
ATTR uint32_t getlongunaligned(uint8_t p[]) {
return ((unaligned_uint32_t *)p)->i;
0: 7802 ldrb r2, [r0, #0]
2: 7843 ldrb r3, [r0, #1]
4: 021b lsls r3, r3, #8
6: 4313 orrs r3, r2
8: 7882 ldrb r2, [r0, #2]
a: 0412 lsls r2, r2, #16
c: 4313 orrs r3, r2
e: 78c0 ldrb r0, [r0, #3]
10: 0600 lsls r0, r0, #24
12: 4318 orrs r0, r3
14: 4770 bx lr
16: 46c0 nop ; (mov r8, r8)
The CM0+ code is about the same as I get with explicit or/shift logic:
ATTR uint32_t getlong(uint8_t p[]) {
return p[0] + (p[1]<<8) + (p[2]<<16) + (p[3]<<24);
0: 7803 ldrb r3, [r0, #0]
2: 7842 ldrb r2, [r0, #1]
4: 0212 lsls r2, r2, #8
6: 189a adds r2, r3, r2
8: 7883 ldrb r3, [r0, #2]
a: 041b lsls r3, r3, #16
c: 18d2 adds r2, r2, r3
e: 78c3 ldrb r3, [r0, #3]
10: 061b lsls r3, r3, #24
12: 18d0 adds r0, r2, r3
14: 4770 bx lr
16: 46c0 nop ; (mov r8, r8)
CM4 makes use of the 32bit thumb2 instructions, but takes the same number of bytes overall:
ATTR uint32_t getlong(uint8_t p[]) {
return p[0] + (p[1]<<8) + (p[2]<<16) + (p[3]<<24);
0: 7803 ldrb r3, [r0, #0]
2: 7842 ldrb r2, [r0, #1]
4: eb03 2302 add.w r3, r3, r2, lsl #8
8: 7882 ldrb r2, [r0, #2]
a: eb03 4302 add.w r3, r3, r2, lsl #16
e: 78c0 ldrb r0, [r0, #3]
10: eb03 6000 add.w r0, r3, r0, lsl #24
14: 4770 bx lr
16: bf00 nop
In terms of "clever" I was thinking of things like double-word loads (LDRD, LDM) and shift, but it looks like the ARMs without HW unaligned access also lack LDRD, and none have 64bit shifts or divides, so ... probably not. (or maybe checking for halfword-alignment and doing halfwords at a time...)
Current source code:
#include <stdint.h>
#define ATTR __attribute__((noinline))
extern char buffer[20];
typedef struct {
uint32_t i;
} __attribute((__packed__)) unaligned_uint32_t;
ATTR uint32_t getlongbasic(uint8_t p[]) {
return *(uint32_t*)p;
}
ATTR uint32_t getlongunaligned(uint8_t p[]) {
return ((unaligned_uint32_t *)p)->i;
}
ATTR uint32_t getlong(uint8_t p[]) {
return p[0] + (p[1]<<8) + (p[2]<<16) + (p[3]<<24);
}
ATTR uint32_t getlong1(uint8_t p[]) {
return p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24);
}
int main() {
volatile uint32_t data = *(uint32_t *)&buffer[1];
data = *(uint32_t *)&buffer[2];
data = getlongbasic(&buffer[1]);
data = getlongunaligned(&buffer[1]);
data = getlong(&buffer[1]);
data = getlong1(&buffer[1]);
}