0

I am using an Arm Cortex-M3 processor. I receive binary data in an unsigned char array, which must be cast into a suitable variable to be used for further computation:

unsigned char gps[24] = { 0xFA, 0x05, 0x08, 0x00, 0x10, 0x00,0xA4, 0x15, 0x02, 0x42, 0x4D, 0xDF, 0xEB, 0x3F, 0xF6, 0x1A, 0x36, 0xBE, 0xBF, 0x2D, 0xA4, 0x41,
                          0xAF, 0x1A };
int i = 6;
float f = (float) *(double*)&gps[i];

This code works on a computer to get the correct value of "f" but it fails on the Cortex-M3. I understand that does not have an arithmetic unit on the processor, hence doesn't support 64 bit operations; but is there a work-around to cast as shown above.

Note that the code below works on the processor; only the casting shown above fails:

double d = 9.7;

Also note that 32 bit casts work, as shown below; only double or uint64_t fail.

uint16_t k = *(uint16_t*)&gps[i];

Is there an alternative solution?

artless noise
  • 21,212
  • 6
  • 68
  • 105
Kartik Kanugo
  • 41
  • 2
  • 8
  • 2
    Do you want a C or a C++ solution? They may be (very) different. – Adrian Mole Jul 22 '22 at 09:14
  • and `*(double*)&gps[i]` violates strict aliasing rule in both languages – phuclv Jul 22 '22 at 09:30
  • While it might appear to work it's UB before C++20 and after you still are lacking the right alignment. If you can't make gps an array of `double` then you have to `memcpy` from the array into a `double` for the alignment and `bit_cast` to fix the type before you can convert to `float`. – Goswin von Brederlow Jul 22 '22 at 18:19

1 Answers1

3

Casting the address of an unsigned char to a pointer to a double – and then using it – is violating strict aliasing rules; more importantly (in your case, as discussed below), it also breaks the required alignment rules for accessing multi-byte (i.e. double) data units.

Many compilers will warn about this; clang-cl gives the following for the (double*)&gps[i] expression:

warning : cast from 'unsigned char *' to 'double *' increases required alignment from 1 to 8 [-Wcast-align]

Now, some architectures aren't too fussy about alignment of data types, and the code may (seem to) work on many of those. However, the Cortex-M3 is very fussy about the alignment requirements for multi-byte data types (such as double).

To remove undefined behaviour, you should use the memcpy function to transfer the component bytes of your gps array into a real double variable, then cast that to a float:

    unsigned char gps[24] = {0xFA, 0x05, 0x08, 0x00, 0x10, 0x00,0xA4, 0x15, 0x02, 0x42, 0x4D,
        0xDF, 0xEB, 0x3F, 0xF6, 0x1A, 0x36, 0xBE, 0xBF, 0x2D, 0xA4, 0x41, 0xAF, 0x1A };
    int i = 6;
    double d; // This will be correctly aligned by the compiler
    memcpy(&d, &gps[i], sizeof(double)); // This doesn't need source alignment
    float f = (float)d; // ... so now, this is a 'safe' cast down to single precision

The memcpy call will use (or generate) code that can access unaligned data safely – even if that means a significant slow-down of the access.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • 1
    Strictly spoken, the aliasing is a distinct issue from the alignment. Aliasing affects the compiler's freedom to optimize (and consequently potentially ignore changes to memory accessed by an illegal alias); alignment, by contrast, is the hard requirement to have addresses be a multiple of some n. The two issues are orthogonal to each other -- even if both may be encountered by raw casts like this one. (I'm sure I'm not telling you anything new here; it's more a nudge to improve your answer.) – Peter - Reinstate Monica Jul 22 '22 at 10:30
  • @Peter Very helpful comment. I've made some changes, to be more precise in my terminology. – Adrian Mole Jul 22 '22 at 10:43