ARM Unaligned data... - Page 1
Forum section: electronics / general

author: westfw
author_rank: Super Contributor
timestamp: August 17, 2019, 06:34:36 am
content: Is there a well-known trick for reading unaligned data on the ARM chips that don't support it in hardware?Something more clever than N bytes worth of load/add/shift?Code:extern char buffer;static inline unsigned long getlong(unsigned char p[]) {return p + (p<<8) + (p<<16) + (p<<24);}int main() {volatile unsigned int data = *(unsigned long *)&buffer;data = getlong(&buffer);}

author: filssavi
author_rank: Frequent Contributor
timestamp: August 17, 2019, 07:42:08 am
content: I’m not so familiar with arm architecture to be able to completely rule it out, but usually the restrictions on aligned transfers only is placed to greatly simplify addressing related logic, so it is very possible that without explicit support the core might not be physically able to do unaligned transfers (ie. there are not enough address lines, etc.)

author: andersm
author_rank: Super Contributor
timestamp: August 17, 2019, 08:20:34 am
content: I don't think there is, but examine GCC's output to see if it does something smart.

author: Brutte
author_rank: Frequent Contributor
timestamp: August 17, 2019, 10:15:43 am
content: I do not understand the code.You are assigning long to int.then (char data)data = bufferdata = buffertwice.Also, you never use buffer.Then why the data is volatile?Either this is super clever or has more bugs than lines of code.

author: nctnico
author_rank: Super Contributor
timestamp: August 17, 2019, 11:46:21 am
content: Quote from: westfw on August 17, 2019, 06:34:36 amNo. Never ever cast multi-byte types onto buffers. It is a recipe for a dissaster! Always use shifting bytes. The added bonus is that your code is endian agnostic as well.Unaligned access is prohibited on most architectures.

author: rhodges
author_rank: Frequent Contributor
timestamp: August 17, 2019, 12:10:05 pm
content: Quote from: Brutte on August 17, 2019, 10:15:43 amIt is an example. It is not supposed to do anything useful.I am still a novice with ARM, but I think this is the only way to do a 32-bit load/store at an unaligned address. MIPS had two instructions for loading the top and bottom parts, but I don't remember ARM having that. ARM does allow cheap shifts, so it isn't as bad as you might think.Code:8000256: 7894 ldrb r4, [r2, #2]8000258: 7853 ldrb r3, [r2, #1]800025a: 78d1 ldrb r1, [r2, #3]800025c: eb03 2304 add.w r3, r3, r4, lsl #88000260: 7912 ldrb r2, [r2, #4]8000262: eb03 4301 add.w r3, r3, r1, lsl #168000266: eb03 6302 add.w r3, r3, r2, lsl #24

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 17, 2019, 12:54:38 pm
content: Depending on how many accesses to the buffer your code will do and how large the buffer is, it can be much simpler and more efficient to just copy the non-aligned buffer to an aligned one first with memcpy().

author: nctnico
author_rank: Super Contributor
timestamp: August 17, 2019, 12:59:44 pm
content: Quote from: SiliconWizard on August 17, 2019, 12:54:38 pmThat will only work if all the elements are truly aligned and the compiler is using packed arrays (*). I've seen this trick go wrong and it is extremely hard to debug. Things work well on a PC but an ARM may work with wrong values without giving an exception.The best and most portable solution is to copy the buffer into a struct using byte shifts and then use the struct internally. Vice versa to create buffer from the struct. The point of mapping a buffer onto a struct is usually moot anyway. Most protocols use big endian byte ordering so you have to byte shift anyway.(*) When working packed arrays some compilers may decide to access data on a byte level and start to emit code which uses byte shifting no matter whether the data is aligned or not. Needless to say this results in bulky & slow code.

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 17, 2019, 01:37:53 pm
content: Well, obviously there are portability considerations. If you are guaranteed to work with only little-endian stuff though all the way with a target that is little-endian, that works. It can go wrong when people don't know what they are doing.QuoteUh. Yeah. If you say so. Some existing ones do indeed, in which case you don't have a choice indeed. I've never made any of my own custom protocols big-endian though.Obviously for a structure, you must make sure the relative alignment of members will be the same in the defined structure and in the unaligned buffer. Compiler attributes exist for that, and of course some care must be taken. Likewise, individual members must be encoded in the same way in both "views" of a given buffer. Not hard to guarantee if for instance you're enforcing little-endian all the way, and use IEEE754 FP numbers (which the huge majority of CPUs support).And whatever you mean by "array", obviously same thing. Data inside your unaligned buffer must be laid out the same way as an aligned one.So of course again some care must be taken, and of course this is not 100% portable. But whenever it can be used (which is quite often in practice, especially if you control the whole design), it's again pretty simple and much more efficient.Note that (again with proper care) it's something relatively easy to do in C, but a lot less (or not at all even) in some other languages.You're right in pointing out that one must be careful all conditions for using this are met though.

author: legacy
author_rank: Super Contributor
timestamp: August 17, 2019, 03:05:09 pm
content: Remember, processors with two-byte memory access granularity will throw an exception upon encountering an odd address. On the SONY Playstatio1 and Playstation2, If you were to read from or write to field b, you’d attempt to access an odd address, and the CPU would throw up a System Error, inviting you to press the one only button "Restart. Yikes!"So, instead of laying out your fields just the way you wrote them, the compiler padded the structure so that things would reside at even addresses.-> tell gcc (and the linker) how to do the job for you!

author: legacy
author_rank: Super Contributor
timestamp: August 17, 2019, 03:17:16 pm
content: Quote from: nctnico on August 17, 2019, 11:46:21 amOn chips like PPC74xx, you have the Altivec engine embedded in the core, and ... soon or later you will learn that Altivec doesn’t support unaligned memory access and it works with sixteen-byte chunks at a time, and all addresses passed to Altivec must be sixteen-byte aligned, but if your address is not aligned ... the hardware won’t throw an exception to warn you about the unaligned address, but rather it will simply ignore the lower four bits of the address and charges ahead, operating on the wrong address.Are you scared now? The first time I understood what was the reason why my programs silently corrupted memory and returned incorrect results if you don’t explicitly make sure all your data is aligned, as I did on XINU ... when well, I was really shocked that it only happened *when* the Altivec was enabled, whereas it didn't happen on PPC405GP where there is only a software emulation (soft-float in kernel space) for the Math Coprocessor

author: legacy
author_rank: Super Contributor
timestamp: August 17, 2019, 03:34:27 pm
content: p.s.The behavior of Arm v5 on unaligned accesses is different from Arm v7's one. Arm v6 has a "legacy bit" in its config to force the behavior to be the same as the one on Arm v5. Arm v7 does not have an option like this.so

author: amyk
author_rank: Super Contributor
timestamp: August 17, 2019, 07:00:54 pm
content: If the hardware doesn't support it you'll need to do (what it should've done) manually, i.e. two reads and shifts.

author: andersm
author_rank: Super Contributor
timestamp: August 17, 2019, 07:14:10 pm
content: Quote from: rhodges on August 17, 2019, 12:10:05 pmNo-one else has the LWL/LWR instructions (AFAIK), as they were patented by MIPS, and used as a weapon against unlicensed implementations. When the patent expired, supporting unaligned memory accesses was already cheap enough that no-one bothered to copy the scheme.

author: nctnico
author_rank: Super Contributor
timestamp: August 17, 2019, 07:47:54 pm
content: Quote from: legacy on August 17, 2019, 03:17:16 pmTell me about it. Last year I spend 3 or 4 days on chasing a bug caused by unaligned access in a project I inherited.

author: westfw
author_rank: Super Contributor
timestamp: August 18, 2019, 12:37:04 am
content: QuoteThe 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.)QuoteQuoteQuoteHeh. 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.)QuoteBut... If the HW supports it, it's probably the fastest way! Certainly you ought to wrap up such accesses, for portability to "other" architectures...QuoteNope. Or rather, gcc is smart enough to recognize an "obviously unaligned" pointer, and produce icky-looking code (here, for M0):Code:volatile unsigned int data = *(uint32_t *)&buffer;4: 4b0f ldr r3, [pc, #60] ; (44 <main+0x44>)6: 1c5c adds r4, r3, #18: 785a ldrb r2, [r3, #1]a: 789b ldrb r3, [r3, #2]c: 021b lsls r3, r3, #8e: 4313 orrs r3, r210: 78a2 ldrb r2, [r4, #2]12: 0412 lsls r2, r2, #1614: 4313 orrs r3, r216: 78e2 ldrb r2, [r4, #3]18: 0612 lsls r2, r2, #241a: 4313 orrs r3, r21c: 9301 str r3, [sp, #4](or for m4, "knowing" that it can:)Code:volatile unsigned int data = *(uint32_t *)&buffer;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:Code:ATTR uint32_t getlongbasic(uint8_t p[]) {return *(uint32_t*)p;0: 6800 ldr r0, [r0, #0]2: 4770 bx lrAnd you need to take special steps (annoyingly kludgey...):Code: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, #86: 4313 orrs r3, r28: 7882 ldrb r2, [r0, #2]a: 0412 lsls r2, r2, #16c: 4313 orrs r3, r2e: 78c0 ldrb r0, [r0, #3]10: 0600 lsls r0, r0, #2412: 4318 orrs r0, r314: 4770 bx lr16: 46c0 nop ; (mov r8, r8)The CM0+ code is about the same as I get with explicit or/shift logic:Code:ATTR uint32_t getlong(uint8_t p[]) {return p + (p<<8) + (p<<16) + (p<<24);0: 7803 ldrb r3, [r0, #0]2: 7842 ldrb r2, [r0, #1]4: 0212 lsls r2, r2, #86: 189a adds r2, r3, r28: 7883 ldrb r3, [r0, #2]a: 041b lsls r3, r3, #16c: 18d2 adds r2, r2, r3e: 78c3 ldrb r3, [r0, #3]10: 061b lsls r3, r3, #2412: 18d0 adds r0, r2, r314: 4770 bx lr16: 46c0 nop ; (mov r8, r8)CM4 makes use of the 32bit thumb2 instructions, but takes the same number of bytes overall:Code:ATTR uint32_t getlong(uint8_t p[]) {return p + (p<<8) + (p<<16) + (p<<24);0: 7803 ldrb r3, [r0, #0]2: 7842 ldrb r2, [r0, #1]4: eb03 2302 add.w r3, r3, r2, lsl #88: 7882 ldrb r2, [r0, #2]a: eb03 4302 add.w r3, r3, r2, lsl #16e: 78c0 ldrb r0, [r0, #3]10: eb03 6000 add.w r0, r3, r0, lsl #2414: 4770 bx lr16: bf00 nopIn 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:Code:#include <stdint.h>#define ATTR __attribute__((noinline))extern char buffer;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 + (p<<8) + (p<<16) + (p<<24);}ATTR uint32_t getlong1(uint8_t p[]) {return p | (p<<8) | (p<<16) | (p<<24);}int main() {volatile uint32_t data = *(uint32_t *)&buffer;data = *(uint32_t *)&buffer;data = getlongbasic(&buffer);data = getlongunaligned(&buffer);data = getlong(&buffer);data = getlong1(&buffer);}

author: andersm
author_rank: Super Contributor
timestamp: August 18, 2019, 09:16:44 am
content: Quote from: westfw on August 18, 2019, 12:37:04 amSlightly cleaner:Code:typedef uint32_t unaligned_uint32_t __attribute__((aligned(1)));ATTR uint32_t getlongunaligned(uint8_t p[]) {return *(unaligned_uint32_t*)p;}

author: cv007
author_rank: Frequent Contributor
timestamp: August 18, 2019, 01:06:49 pm
content: https://godbolt.org/z/l3OLnp(cortex-m0 version)So where is the advantage. It all looks the same except for the longbasic version, which has to be a called function (noinline) or else it reverts to a byte version like the rest. I don't know what clock cycles are involved, but I would guess even the called longbasic version would end up being at least close to the icky versions (and only be correct 25% of the time of course).It seems the gcc arm compiler has a resistance to unaligned access, and will be hard to 'trick' it to do something bad. On top of that, you have a bunch of flavors of arm mcu's, and it seems like its best to let the compiler handle all the various flavors and let it figure out what it takes to get you what you want for a particular mcu. I imagine the compiler authors would already be doing something clever if the resulting code required less cycles to do and was always correct.I think you are just exploring the 'what if's' and 'why's', but maybe tell us when this comes into play. If a byte buffer has lots of word data to access, wouldn't the buffer be designed in a way to make word aligned access the norm? If its an occasional word access in a byte buffer, the icky code probably is a rare event and the mcu will not care as at 40MHz+ it has no time to think about it, and the extra few bytes of code will not be noticed inside the 32k+ flash.Maybe I'm missing something, and fail to see it.

author: nctnico
author_rank: Super Contributor
timestamp: August 18, 2019, 01:14:34 pm
content: I rather handle it myself so I know the compiler isn't screwing up. I've seen unaligned accesses going wrong with GCC for ARM so it is better not to bet on whether the compiler does it right or not.

author: mfro
author_rank: Regular Contributor
timestamp: August 18, 2019, 01:23:33 pm
content: Quote from: andersm on August 18, 2019, 09:16:44 amYou might get away with such constructs many times, but strictly according to the standard, it invokes undefined behaviour.

author: andersm
author_rank: Super Contributor
timestamp: August 18, 2019, 02:58:07 pm
content: Quote from: mfro on August 18, 2019, 01:23:33 pmThe __attribute__ syntax is a GCC extension, the standard does not apply.

author: mfro
author_rank: Regular Contributor
timestamp: August 18, 2019, 03:15:35 pm
content: Quote from: andersm on August 18, 2019, 02:58:07 pmI'm not talking about __attribute__, I'm talking about 'upcasting'/aliasing

author: andersm
author_rank: Super Contributor
timestamp: August 18, 2019, 03:20:18 pm
content: Quote from: mfro on August 18, 2019, 03:15:35 pmMaybe try understanding what's going on before posting.

author: mfro
author_rank: Regular Contributor
timestamp: August 18, 2019, 04:01:54 pm
content: Quote from: andersm on August 18, 2019, 03:20:18 pmI guess I do understand what this is about.I also do know about the use of __attribute__(aligned(xx)), but - to my understanding - you appear to use it to tell the compiler it might need to access a variable through a pointer with a smaller than 'naturally' required alignment for its data type.Not only I am unable to find any mention of such usage in the gcc documentation (but will be happy if you could point me to such), but I also can't see that this usage would suddenly make upcasting a char pointer (that has no proper alignment for uint32_t) to an uint32_t pointer legal.My understanding is, that this is still UB.

author: andersm
author_rank: Super Contributor
timestamp: August 18, 2019, 04:28:20 pm
content: Quote from: mfro on August 18, 2019, 04:01:54 pmI am not accessing the array via an uint32_t pointer, I am accessing it through an unaligned_uint32_t pointer. The unaligned_uint32_t type has a minimum alignment requirement of 1 byte. The documentation can be found athttps://gcc.gnu.org/onlinedocs/gcc-9.2.0/gcc/Common-Type-Attributes.html#index-aligned-type-attributeQuoteIt is an extension to the standard. A compiler can extend the standard in any way it sees fit.

author: nctnico
author_rank: Super Contributor
timestamp: August 18, 2019, 05:00:40 pm
content: Quote from: andersm on August 18, 2019, 04:28:20 pmThat is one of the reasons why it is bad. Also the behaviour isn't guaranteed by GCC so mfro is right; you shouldn't count on how the compiler deals with the situation.In general casting one type into another is asking for trouble down the road and should be avoided in code used for production software.

author: mfro
author_rank: Regular Contributor
timestamp: August 18, 2019, 05:25:30 pm
content: Quote from: andersm on August 18, 2019, 04:28:20 pmThat's the document I was reading.To me, it just says the compiler would ensure that a variable has the specified minimum alignment (to, e.g. make sure a char is aligned to the requirements of an int), but it doesn't say it would allow lowering the alignment requirements of a type and generate (special) code to enable dereferencing such a variable through a pointer to that type.Did some tests with an m68k toolchain and gcc always generated the exact same (working) code no matter if I accessed the variable through an uint32_t pointer or an unaligned_uint32_t pointer.

author: Nominal Animal
author_rank: Super Contributor
timestamp: August 18, 2019, 06:12:02 pm
content: Quote from: westfw on August 17, 2019, 06:34:36 amNo, there isn't. (If such a trick exists, it is as obscure as secret undocumented instructions, and probably involves those.)The load-shift-add method takes 8 or 10 instructions to read a 32-byte value: four byte loads, three shifts, and three adds. Anything cleverer, like reading the two aligned 32-bit words covering the desired value, leads to longer machine code. The simple method is just too simple to beat with anything more complex.Personally, I do prefer explicit casting and notation to ensure both us humans and future versions of GCC parse the intent correctly, i.e.Code:#include <stdint.h>/* Read 32-bit unsigned integer from a possibly unaligned pointer.get_u32_native(ptr): Native byte order.get_u32_swapped(ptr): Swapped byte order.get_u32_le(ptr): Little-endian byte order (least significant byte first).get_u32_be(ptr): Big-endian byte order (most significant byte first).*/static inline uint32_t get_u32_le(const void *const ptr){const uint8_t *const byte = ptr;return (uint32_t)byte+ ((uint32_t)byte << 8)+ ((uint32_t)byte << 16)+ ((uint32_t)byte << 24);}static inline uint32_t get_u32_be(const void *const ptr){const uint8_t *const byte = ptr;return (uint32_t)byte+ ((uint32_t)byte << 8)+ ((uint32_t)byte << 16)+ ((uint32_t)byte << 24);}#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__#define get_u32_native get_u32_le#define get_u32_swapped get_u32_be#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__#define get_u32_native get_u32_be#define get_u32_swapped get_u32_le#else#error Unsupported byte order.#endifThese compile to 20 bytes on Cortex-M0, M0+, and M4, when using-O2or-Os(and I always do, and recommend you do too).The other way, sayCode:#include <stdint.h>#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__static inline uint32_t get_u32_le(const void *const ptr){const uintptr_t address = (uintptr_t)ptr;const uint32_t *base = (const uint32_t *)(address & (~(uintptr_t)3));const uint32_t word = { base, base };const unsigned char shift = (address & 3) << 3;return (word >> shift) + (word << (32 - shift));}#else#error Not implemented#endifcompiles to nine instructions (26 bytes) on Cortex-M4, 13 instructions (also 26 bytes) on Cortex-M0 and M0+; compared to 20 bytes for each of the earlier two functions. Furthermore, it accesses the following 32-bit integer ifptrhappens to be aligned, and that can be quite problematic in some cases.If you look at the generated code, you'll see that there are just too many individual operations needed to be done to achieve this, to get under the 10 instructions and 20 bytes of code limit, even with trickery.The only situation when I find myself needing to access possibly unaligned pointers, is when processing binary data (or data stream) with a specific protocol. In those cases, I've always found the overhead of using such accessors warranted.(When dealing with massive amounts of binary data, I've crafted my own protocol which ensures data alignment. I have done this, BTW, for reading and writing molecular dynamic data from both Fortran 95 and C code (although the Fortran code requires one nonstandard feature, reading raw binary data without record length words, but all Fortran 90/95 compilers I had access to, did support that). This was to allow a distributed simulation to save oodles of data to node-local storage without slowing down the simulation; data combining and slicing was done afterwards using a helper utility, when collecting the resulting data.)

author: andersm
author_rank: Super Contributor
timestamp: August 18, 2019, 06:39:06 pm
content: Quote from: nctnico on August 18, 2019, 05:00:40 pmIt's guaranteed as much as any of its other documented behavior.Quote from: mfro on August 18, 2019, 05:25:30 pmDon't be daft. There would be absolutely no point in having the compiler generate data layouts it can't access. It's no different than using the packed attribute on structs.QuoteHow did you test? This is with GCC 9.1:Code:typedef uint32_t uuint32_t __attribute__((aligned(1)));extern char buffer[];uint32_t getuuint32(uint8_t p[]) {return *(uuint32_t*)p;}uint32_t getuint32(uint8_t p[]) {return *(uint32_t*)p;}00000090 <getuuint32>:90:206f 0004moveal %sp@(4),%a094:7200moveq #0,%d196:1210moveb %a0@,%d198:e149lslw #8,%d19a:4841swap %d19c:4241clrw %d19e:7000moveq #0,%d0a0:1028 0001moveb %a0@(1),%d0a4:4840swap %d0a6:4240clrw %d0a8:8280orl %d0,%d1aa:7000moveq #0,%d0ac:1028 0002moveb %a0@(2),%d0b0:e188lsll #8,%d0b2:8081orl %d1,%d0b4:8028 0003orb %a0@(3),%d0b8:4e75rts000000ba <getuint32>:ba:206f 0004moveal %sp@(4),%a0be:2010movel %a0@,%d0c0:4e75rts

author: nctnico
author_rank: Super Contributor
timestamp: August 18, 2019, 08:15:38 pm
content: Quote from: andersm on August 18, 2019, 06:39:06 pmThen explain why I have seen it go wrong using the code presented in this thread.

author: andersm
author_rank: Super Contributor
timestamp: August 18, 2019, 08:41:22 pm
content: Quote from: nctnico on August 18, 2019, 08:15:38 pmWhere?

author: nctnico
author_rank: Super Contributor
timestamp: August 18, 2019, 08:50:58 pm
content: Quote from: andersm on August 18, 2019, 08:41:22 pmOn an ARM cpu using GCC.

author: andersm
author_rank: Super Contributor
timestamp: August 18, 2019, 09:42:15 pm
content: I was asking for an example.

author: mfro
author_rank: Regular Contributor
timestamp: August 19, 2019, 02:27:26 am
content: Quote from: andersm on August 18, 2019, 06:39:06 pmThe documentation - at least according to my interpretation - only talks about placement of variables (so my interpretation is that it was originally meant toincreasealignment), not about generating specific access code when youloweralignment (moreover, you obviously can't change placement through a pointer) whereas it explicitly talks about special code for struct field access for the packed attribute.Quote from: andersm on August 18, 2019, 06:39:06 pmI basically did the same with GCC 4.6.4 (which is - admitted - quite old, but the documentation for the aligned() attribute did not change since as far as I can tell). The generated code is exactly the same in both cases.The fact that your (newer) compiler appears to generate different code depending on the (apparently reduced) alignment of the type indeed supports your claim, but frankly, I can't see this documented. As long as it isn't explicitly documented, your code still appears dubious to me as it is violating strict aliasing rules. It might be that what you see is intended behaviour, but then the docs should be more explicit. As long as they aren't (it's still possible that I've overseen something, however, and would be glad if you could prove me wrong as I would agree this would be an elegant solution if it really was legal), it might as well be a side effect and pure coincidence that it works for you (potentially subject to change with next release).Does the code change in any way when you use -O3 as opposed to -O2?[P.S.: nvm, just tried with gcc 6.2 and that one indeed behaves like yours (regardless of optimisation level). That does not invalidate anything I said above, however]

author: legacy
author_rank: Super Contributor
timestamp: August 19, 2019, 05:50:39 am
content: GCC-addicted code is no good.

author: mfro
author_rank: Regular Contributor
timestamp: August 19, 2019, 03:39:02 pm
content: Today I asked on the gcc mailing list whether your approach is a valid use case for __atribute:__((aligned()))Guess what, the first few opinions that flew in are even farther apart than ours

author: magic
author_rank: Super Contributor
timestamp: August 20, 2019, 06:48:07 am
content: LOL. I guess it's some "users" list filled with armchair language lawyers who participated neither in writing the compiler nor its specificationStrictly speaking, casting from char* to __attribute(aligned(1))__ uint32_t*does notincrease alignment requirements of the pointer, unlike casting to plain uint32_t*

author: andersm
author_rank: Super Contributor
timestamp: August 20, 2019, 11:27:28 pm
content: Quote from: mfro on August 19, 2019, 03:39:02 pmI saw your post. The documentation was changed to match the behavior (ie. allowing decreasing alignment via typedefs)in 2018, though the ticket was created in 2016 and mentions the behavior going back to at least GCC 4.5. GCC's testsuite has tested the functionalitysince at least 2012. Older versions of GCC did indeedexplicitly statethat the attribute could only be used to increase alignment, but that has not been true for some time.In any case, GCC is smart enough that the code produced using the attribute is almost identical to the manual load-and-shift version, so it's really just syntactic sugar.