1

Previous similar questions which didn't answer my question:

bit cast uint64_t to double and back in a macro

Converting uint64_t to Double Value

I need to save a double to the flash memory on an STM32L476 microcontroller. The flash controller works in 64 bit chunks and the HAL APIs from ST take a uint64_t parameter for the data to write. To this end, I need to convert the bits from the double variable into one or more uint64_ts.

I tried the following, which is UB:

uint64_t flash_write_val = *(uint64_t*)&double_value;

However, I get a compiler warning about breaking strict aliasing rules. How would I do this without invoking UB? It doesn't need to be super portable. It can be valid only on a Cortex M4F core.

I'm thinking about this:

union uint64_double {
    uint64_t flash_friendly;
    double math_friendly;
};

Is this a good approach, or am I still shooting myself in the foot?

Louis Cloete
  • 421
  • 3
  • 16
  • 4
    Type punning using unions is OK in C but *not* in C++: [Unions and type-punning](https://stackoverflow.com/q/25664848/10871073). – Adrian Mole Aug 27 '21 at 13:12
  • What is it about the two posts you linked that doesn't answer your question? – Adrian Mole Aug 27 '21 at 13:12
  • Is it required that the data is `uint64_t` or could you use e.g. `uint8_t buffer[8]`? Do you need specific alignment for the data? – Bodo Aug 27 '21 at 13:15
  • Seems strange that they would design a 64 bit API for a 32 bit MCU. The HAL bloatware has a nasty reputation, but maybe they offer an alternative way to call the functions? – Lundin Aug 27 '21 at 14:14
  • 1
    @Lundin Not that strange; the STM32F476 on-chip flash has a 64 bit data path (actually 72 - including the 8-bit ECC) between the memory and the cache/accelerator and can only be programmed in 64bit aligned "double words". Well, perhaps it is strange, but not inexplicable and in this case not a HAL design issue. I guess it is hardware optimisation to cram in as much flash memory as possible by making it less flexible thus requiring less logic (and therefore die space). The flash implementations on STM32 vary across the series, and flash code for one part does not necessarily work on another! – Clifford Aug 28 '21 at 15:00
  • @Clifford Larger chunks of aligned flash segments isn't uncommon, but that has nothing to do with the function parameter type. I wonder why they made the API 64 bit because the core can't pass along 64 bits in a single instruction. So it's just a cumbersome API and an array of 8 bytes would have been better. If they wished to burn directly from the function parameter's location, then wrap the 8 byte array in a struct so it gets aligned. – Lundin Aug 29 '21 at 10:27
  • @Lundin : Perhaps; I was simply explaining the "strangeness" - not justifying it - even bad design decisions can have rational explanations. More naive that "strange". We are in complete agreement that the STM32 HAL API sucks. Here they have clearly written the API with little useful abstraction. The simple solution is to either write your own and avoid the HAL or to write a facade over the HAL to give it a more straightforward interface. – Clifford Aug 29 '21 at 15:58
  • @Lundin it's worse: the API internally writes 2 32-bit values onto the data bus to the flash controller anyway, so it masks and shifts the 64 bit value internally. – Louis Cloete Aug 30 '21 at 11:11
  • 1
    @LouisCloete That's pretty bad programming... I wouldn't use the ST HAL. – Lundin Aug 30 '21 at 13:38
  • HAL with CubeMX is nice if you don't care about efficiency or speed. But goodness, they managed to make even run-of-the-mill tasks slow without scheduler overhead with all their locking and checking in ways that take 200x longer than necessary – Louis Cloete Aug 30 '21 at 17:57

2 Answers2

7

Just use memcpy to copy the bytes where you want.

memcpy(&flash_write_val, &double_val, sizeof(double_val));
mediocrevegetable1
  • 4,086
  • 1
  • 11
  • 33
dbush
  • 205,898
  • 23
  • 218
  • 273
  • 3
    This is the correct answer. Though I'd probably advise to also add `static_assert(sizeof(double)==sizeof(uint64_t), "oops");` somewhere for defensive purposes. – Lundin Aug 27 '21 at 14:11
  • 1
    This is the correct answer _for C++_. For C (with which the question is tagged) the union is fine. – cooperised Aug 27 '21 at 15:31
  • I went with the memcpy anyway, since I realized the overhead is negligible compared to the string handling going with the save (I'm using existing SCPI parsing infrastructure to save the values). It is also not worth it to change every occurrence of the previous `double` to a new union type. Good to know that it is valid in C to pun types in unions though. – Louis Cloete Aug 27 '21 at 16:51
-1

It is only Cortex-M3+ answer!!

  1. Pointer punning.
uint64_t flash_write_val = *(uint64_t*)&double_value;

As Cortex-M3 and newer support not-aligned access the above is 100% safe. I personally prefer memcpy way, but in FLASH write functions I usually use pointer punning.

  1. Union punning - safe and portable:
union uint64_double {
    uint64_t flash_friendly;
    double math_friendly;
};
  1. The memcpy function The most portable and and probably most efficient method as modern compilers know very well what memcpy does and in many cases inline it.
uint64_t bar(uint64_t *);

uint64_t foo(double double_val)
{
    uint64_t flash_write_val;
    memcpy(&flash_write_val, &double_val, sizeof(flash_write_val));
    return bar(&flash_write_val);
}
foo:
        push    {lr}
        sub     sp, sp, #12
        mov     r2, r0
        mov     r3, r1
        mov     r0, sp
        strd    r2, [sp]
        bl      bar
        add     sp, sp, #12
        ldr     pc, [sp], #4

https://godbolt.org/z/Ws6Yr15x8

0___________
  • 60,014
  • 4
  • 34
  • 74
  • I didn't down vote but 1) is undefined behavior. The other two are fine. – Lundin Aug 29 '21 at 10:28
  • Cortex-m support unaligned access only for word and half-word. Not for doubleword. Already happened to my that a compiler optimization broke a code for this reason optimizing two word copy in one doubleword copy. So to make your code portable and robust imho you should disable the unaligned support in non-release version if you don't have any valid reason to enable it. Unions and memcpy are the ways to go. – Damiano Sep 02 '21 at 05:53