6

I have an array each of whose elements could be either uint16_t or a pair of uint8_t.

Its elements are defined as a union of a uint16_t and a sub-array of 2 uint8_t.

Unfortunately, the compiler (MicroChip XC16) allocates twice as much memory as it should for the array.

typedef union {
   uint16_t u16;   // As uint16_t
   uint8_t  u8[2]; // As uint8_t
} my_array_t;

my_array_t my_array[1]; // 1 word array, for testing

my_array[0].u8[0] = 1;
my_array[0].u8[1] = 2;
uint8_t byte_0 = my_array[0].u8[0]; // Gets 0x01
uint8_t byte_1 = my_array[0].u8[1]; // Gets 0x02
uint16_t byte_0 = my_array[0].u16; // Gets 0x0201

The compiler allocates 4 bytes instead of 2 bytes as it should.

Workaround: if I change the struct to:

typedef union {
   uint16_t u16;   // As uint16_t
   uint8_t  u8[1];   // As uint8_t
} my_array_t;

The compiler allocates 2 bytes as it should, but then this is incorrect:

my_array[0].u8[1] = 2;

though it still works:

uint8_t byte_1 = my_array[0].u8[1]; // Gets 0x02

(except for the inconvenience that the debugger doesn't show its value).

Question: should I live with the workaround, or should I use a better solution?

Please refer to a previous discussion on this, where the above solution was suggested.


EDIT.

Per EOF's suggestion (below), I checked sizeof.

Before the workaround:

sizeof(my_array_t) // Is 4
sizeof(my_array[0]) // Is 4
sizeof(my_array[0].u8) // Is 2

After the workaround:

sizeof(my_array_t) // Is 2
sizeof(my_array[0]) // Is 2
sizeof(my_array[0].u8) // Is 2

That would indicate that it's a compiler bug.

Community
  • 1
  • 1
Davide Andrea
  • 1,357
  • 2
  • 15
  • 39
  • 1
    File a bug report with the compiler vendor? – EOF Nov 18 '14 at 17:13
  • You see this as a bug in the compiler? (As opposed to a mistake on my part?) – Davide Andrea Nov 18 '14 at 17:16
  • 1
    If `sizeof(union_x) != sizeof(union_x.largest_member)`, that looks like a compiler bug to me. – EOF Nov 18 '14 at 17:18
  • But check if `CHAR_BIT != 8`. In that case it might not be a compiler bug. – EOF Nov 18 '14 at 17:26
  • 1
    I don't use XC16, but by analogy with other compilers, I would assume that this round-up is done for aligning with even addresses, and as such gain time. Many compilers have an option to 'pack' structures though. It might affect calculating effectiveness in calculating offsets in tables. And, depending if XC16 is commercial or not, there might be a difference in the paid or non-paid versions. – jcoppens Nov 18 '14 at 17:27
  • > But check if CHAR_BIT != 8 --- 'CHAR_BIT' undeclared – Davide Andrea Nov 18 '14 at 17:31
  • 1
    > round-up is done for aligning with even addresses -- It's a 16-bit part. Even addresses match uint16_t – Davide Andrea Nov 18 '14 at 17:32
  • 1
    In order to get the value of `CHAR_BIT`, you need to `#include `. Also, what is `sizeof(uint8_t)`? If it's not `1`, we have a problem. – EOF Nov 18 '14 at 17:33
  • sizeof(uint8_t) = 1, CHAR_BIT = 8 – Davide Andrea Nov 18 '14 at 17:35
  • 1
    You could find out if it's a bug in `sizeof` or in the storage allocated, by declaring a union array of 2 elements, writing to `my_array[0].u8[2]` which is out of range, and then looking at `my_array[1].u16` – Weather Vane Nov 18 '14 at 17:48
  • 2
    It still *looks* like a compiler bug to me. What confuses me right now is `sizeof(my_array[0].u8) == 2` in the workaround. That's just a `uint8_t`, so it should have a `sizeof(uint8_t) == 1`. – EOF Nov 18 '14 at 17:48
  • > You could find out if it's a bug in sizeof or in the storage allocated -- Oh, I already know the bug is in the storage allocated: that's why I stared looking into it. – Davide Andrea Nov 18 '14 at 17:59
  • @EOF: A friend pointed me to a solution: use a struct of 2 bytes instead of an array of 2 bytes. I posted it as an answer. – Davide Andrea Nov 18 '14 at 19:17

2 Answers2

5

Instead of an array of 2 bytes, use a structure of 2 bytes:

// Two bytes in a 16-bit word
typedef struct{
    uint8_t     lsb;    // As uint8_t, LSB
    uint8_t     msb;    // As uint8_t. MSB
} two_bytes_t;

typedef union {
   uint16_t u16;   // As uint16_t
   two_bytes_t  u8x2; // As 2 each of uint8_t
} my_array_t;


my_array_t my_array[1]; // 1 word array, for testing

my_array[0].u8x2.msb = 1;
my_array[0].u8x2.lsb = 2;

The XC16 compiler correctly allocates only 2 bytes for each element, and the debugger correctly shows the individual bytes.

Davide Andrea
  • 1,357
  • 2
  • 15
  • 39
1

It looks like this issue was fixed in the compiler. I checked it in XC16 1.26 and got these results from my code (optimization 0):

#include "mcc_generated_files/mcc.h"
#include "stddef.h"

typedef union
{
    uint16_t u16;
    uint8_t u8[2];
} example_1_t;

typedef union
{
    uint16_t u16;

    struct
    {
        uint8_t lsb;
        uint8_t msb;
    };
} example_2_t;

int main(void)
{
    SYSTEM_Initialize();

    size_t typeSize1 = sizeof (example_1_t); // debugger shows 2
    size_t typeSize2 = sizeof (example_2_t); // debugger shows 2

    example_1_t ex1; // Can see all values in debugger
    ex1.u16 = 0x4321; // u8[0] = 0x21, u8[1] = 0x43
    example_2_t ex2; // Can see all values in debugger
    ex2.u16 = 0x4321; // lsb = 0x21, msb = 0x43

    size_t objSize1 = sizeof (ex1); // debugger shows 2
    size_t objSize2 = sizeof (ex2); // debugger shows 2

    while (1)
    {
    }

    return -1;
}
Ryan B
  • 527
  • 1
  • 6
  • 17