Search code examples
cgccstructcubesat-protocol

Why would casting a from an array to a pointer mangle my data?


A friend of mine posted this problem on Facebook the other day, and for the life of me I can't figure it out. He's writing a client and server using the cubesat protocol. For some reason when he casts the data member of the protocol struct to a pointer, his data appears to be mangled.

Client code snippet:

uint32_t data[3] = { 1234U, 5678U, 9101U };
memcpy(packet->data32, data, sizeof(data));
packet->length = sizeof(data);
csp_send(connection, packet, 1000);

Server code snippet:

uint32_t *data = (uint32_t *)(packet->data32);
printf("Packet received on %i: %u\r\n", PORT, data[0]);
printf("Packet received on %i: %u\r\n", PORT, data[1]);
printf("Packet received on %i: %u\r\n", PORT, data[2]);
printf("Packet received on %i: %u, %u, %u\r\n", PORT, data[0], data[1], data[2]);

Output this code produces:

Packet received on 15: 2182284498
Packet received on 15: 5678
Packet received on 15: 9101
Packet received on 15: 80904723, 372113408, 596443136

Output a casual reader of this code would expect:

Packet received on 15: 1234
Packet received on 15: 5678
Packet received on 15: 9101
Packet received on 15: 1234, 5678, 9101

After some fiddling he's told me that he gets the correct output if he doesn't cast the data32 member of the struct to a uint32_t*.

From my own research, packet is of type csp_packet_t, which is defined as:

typedef struct __attribute__((__packed__)) {
        uint8_t padding[CSP_PADDING_BYTES];     // Interface dependent padding
        uint16_t length;                        // Length field must be just before CSP ID
        csp_id_t id;                            // CSP id must be just before data
        union {
                uint8_t data[0];                // This just points to the rest of the buffer, without a size indication.
                uint16_t data16[0];             // The data 16 and 32 types makes it easy to reference an integer (properly aligned)
                uint32_t data32[0];             // - without the compiler warning about strict aliasing rules.
        };
} csp_packet_t;

The full header file is here.

This is GNU C, so zero-length arrays are allowed.

I do not know the word size or endianness of the architecture on either side.

So, simply put - what's going on here? Why does the cast matter?


Solution

  • I think this is an alignment issue.

    Quoting fritzone:

    2182284498 is 0x821304D2 where 0x04d2 is 1234 and the rest possibly is the packet data

    This is because the union is a member of a packed struct, and it's 16 bits out of alignment. When accessing from the union member in the packed struct, the compiler works some magic to guarantee that the packed (misaligned) data is retrieved correctly. However, when casting to a uint32_t* the compiler loses the packing detail, and I believe that it assumes it's accessing data which is properly aligned.