4

I have a union in a project i'm working on that represents a USB joystick report:

union {
    struct{
        uint8_t xaxis;
        uint8_t yaxis;
        uint8_t xraxis;
        uint8_t yraxis;
        uint8_t zraxis;
        uint8_t slider;

        uint8_t buttons8;
        uint8_t buttons16;
    };
    uchar buffer[8];
} report;

this way i can easily put the values into the struct members and use the buffer member to send that data over USB, however I end up modifying this union for different projects (different resolutions on the axis, more/less buttons etc) so then i have to work out what the new size of the struct is to declare the array with the same size.

is there a way to automatically make the array the same size? i thought of using sizeof which requires me to change the struct like this:

union {
    struct _data{ // either this
        uint8_t xaxis;
        uint8_t yaxis;
        uint8_t xraxis;
        uint8_t yraxis;
        uint8_t zraxis;
        uint8_t slider;

        uint8_t buttons8;
        uint8_t buttons16;
    } data; // or this
    uchar buffer[8];
} report;

but then i can no longer put data into the struct like this:

report.xaxis = value;

but instead:

report.data.xaxis = value;

which i don't want.

is there a way to do this cleanly?

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
James Kent
  • 5,763
  • 26
  • 50
  • Don’t use an untagged structure. Then use `sizeof(structure tag)` for the dimension. – Jonathan Leffler Jan 18 '18 at 09:33
  • 2
    @JonathanLeffler can you elaborate? i can't see how that lets me avoid having to do something like `report.data.xaxis = value` – James Kent Jan 18 '18 at 09:43
  • Like Lundin shows in his answer. Distinguish between a structure tag which allows you to refer to the structure type and a member name. You should be able to use an anonymous structure, but you need the tag so you can refer to the type and get its size. – Jonathan Leffler Jan 18 '18 at 09:45
  • @JonathanLeffler - If it's tagged, it's not an anonymous structure. At least not according to [n1570](https://port70.net/~nsz/c/c11/n1570.html#6.7.2.1p13). – StoryTeller - Unslander Monica Jan 18 '18 at 10:17
  • @StoryTeller: a tagged structure can be an anonymous member of the union, just as an untagged structure can be too. I am out of here for a few hours — I need to sleep sometime. But while untagged structures are one sort of anonymous, you can have anonymous elements of a structure or union, and those anonymous elements can be tagged or untagged structures (or various other types). – Jonathan Leffler Jan 18 '18 at 10:22
  • @JonathanLeffler - I can't find any mention of anonymous members other than structures or unions. And the grammar production seems to indicate the declarator of a member is only optional for bit-field. I do not wish to deprive you of sleep, but when you are back, I'll appreciate if you can point me in the right direction with regard to this being standard or not. – StoryTeller - Unslander Monica Jan 18 '18 at 10:25
  • @StoryTeller: The standard uses the terms 'unnamed member' and 'anonymous struct' or 'anonymous union'. I was misremembering and don't have a copy of the standard on my iPhone (nor do I have eidetic memory), so I got the terminology wrong. (The 'or various other types' is comment is wrong; I put the mistakes down to sleep deprivation at the time.) – Jonathan Leffler Jan 18 '18 at 16:42
  • I think your requirements are conflicting — one of them is going to have to give. Either you treasure the abbreviated access notation `report.rzaxis` (what happened to the `zaxis` member?), or you value the ability to size the array automatically — AFAICS, you can't have both at the same time (without resorting to abominable macro trickery — so abominable I'm not even going to discuss it, though I work with systems where it is widely (ab)used). – Jonathan Leffler Jan 18 '18 at 17:43
  • @JonathanLeffler I'd rather avoid ugly macro hackery, I'll probably lose the abbreviation, as for the `zaxis` i was restructuring some example code that came with the digispark arduino framework I'm working with, youd have to ask the origional author, however they had written it so the calling code had to do all the byte alignment into the buffer the hard way, meaning many parts of the code needed rework if the structure changed – James Kent Jan 18 '18 at 19:36

3 Answers3

2

Step 1 is to get a standard C compiler. Then you can use an anonymous struct. To get the appropriate size for the array, use a struct tag, which is only needed internally by the union.

typedef union {
    struct data {
        uint8_t xaxis;
        uint8_t yaxis;
        uint8_t xraxis;
        uint8_t yraxis;
        uint8_t zraxis;
        uint8_t slider;
        uint8_t buttons8;
        uint8_t buttons16;
    }; 
    uint8_t buffer[sizeof(struct data)];
} report_t;

...

  report_t r; 
  r.xaxis = 123;
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • 2
    I do get the warning "does not declare anything" with pedantic – Antti Haapala -- Слава Україні Jan 18 '18 at 09:50
  • @JamesKent Which compiler are you using and how is it configured? This compiles cleanly on an older version of gcc but with C11 support. – Lundin Jan 18 '18 at 09:51
  • @AnttiHaapala gcc 4.9.1 `-std=c11 -pedantic-errors -Wall -Wextra` compiles cleanly. – Lundin Jan 18 '18 at 09:51
  • This really being an anonymous [structure, standard-wise, is kinda debatable](https://stackoverflow.com/a/44524573/817643). – StoryTeller - Unslander Monica Jan 18 '18 at 09:53
  • @JamesKent https://stackoverflow.com/questions/8932707/what-are-anonymous-structs-and-unions-useful-for-in-c11 they need C11 support. – Bob__ Jan 18 '18 at 09:53
  • @StoryTeller gcc seems to believe it is, I can't compile it with -std=c99. Anyway it doesn't matter what it is called, as long as it compiles and can be used as intended. – Lundin Jan 18 '18 at 09:55
  • i'm this is on arduino 1.6.9 which according to versions file is using 4.8.1-arduino5 – James Kent Jan 18 '18 at 09:55
  • @JamesKent Then it should have C11 support. But older versions than gcc 5.x has to be told explicitly not to be trash, with `-std=c11 -pedantic-errors`. If you don't include that, you aren't using a standard compiler, but a "gnu90" compiler. Isn't Arduino actually using some sort of simplified C++ btw? – Lundin Jan 18 '18 at 09:57
  • *"Step 1 is to get a standard C compiler"* - If there's a contention point in the standard about this, I don't see why we need to bash the OP's compiler for it, that's my point. Of course it won't work at all in C99 mode, since there isn't *any* notion of an anonymous struct in C99. – StoryTeller - Unslander Monica Jan 18 '18 at 09:58
  • @StoryTeller C11 has been out for 7 years now. There is no excuse not to use a standard ISO C compiler, particularly not if you are using gcc. The OP's compiler _is_ modern enough, it is just incorrectly configured. – Lundin Jan 18 '18 at 09:59
  • According to [this](https://gcc.gnu.org/wiki/C11Status), C11 anonymous struct was included in gcc 4.6. – Lundin Jan 18 '18 at 09:59
  • *"There is no excuse not to use a standard ISO C compiler, particularly not if you are using gcc"* - What does that have to do with anything? I never argued against it. But if the standard itself isn't clear on the validity of this (as I mentioned before, it's debatable), then that's unlikely to be of help. – StoryTeller - Unslander Monica Jan 18 '18 at 10:14
  • @StoryTeller Isn't `typedef struct { foo; } bar;` another issue, though? I actually don't believe the struct in my code counts as an anonymous struct, because it does have a tag. But gcc seems to think it is anonymous still. – Lundin Jan 18 '18 at 11:51
  • @Lundin - In retrospect, it is. Truth be told, I'm kinda baffled by this. I've seen and used code like you presented. But then I hit a brick wall when trying to prove it is pure standard C. – StoryTeller - Unslander Monica Jan 18 '18 at 11:56
  • @StoryTeller Proving that it is standard C is the easy part, simply read chapter 6.7.2.1. – Lundin Jan 18 '18 at 13:00
  • @Lundin - I did, hence my complaints. p13 says *"An unnamed member whose type specifier is a structure specifier with **no tag** is called an anonymous structure"*. So it's not an anonymous structure. And it doesn't *have* to be, like you said. But the problem is that I don't see anything that would suggest it's okay to omit the declarator otherwise. – StoryTeller - Unslander Monica Jan 18 '18 at 13:03
  • Using GCC 7.2.0 and `gcc -O3 -g -std=c11 -Wall -Wextra -Werror -c union71.c`, I get the "does not declare anything" (error because of `-Werror`, but a warning otherwise). Written with the tag, this defines a structure type (that can be referred to as `struct data`), but it doesn't define a structure member. I got confused; it's also subtle as a snake. It's basically saying "you could move the `struct data { … };` before the `union` and it wouldn't change anything. – Jonathan Leffler Jan 18 '18 at 17:17
  • @JonathanLeffler Do you get the same if you include `-pedantic-errors`? – Lundin Jan 19 '18 at 07:33
  • @Lundin: Succinctly, yes — no change with `-pedantic-errors` added. I'm compiling on a Mac with a home-built (but not specially modified) GCC 7.2.0. – Jonathan Leffler Jan 19 '18 at 07:39
1

You could use offsetof() for this with the last element in the struct

union
{
    struct data
    {
        uint8_t xaxis;
        uint8_t yaxis;
        uint8_t xraxis;
        uint8_t yraxis;
        uint8_t zraxis;
        uint8_t slider;

        uint8_t buttons8;
        uint8_t buttons16;
    };
    uint8_t buffer[offsetof(struct data, buttons16)+1];
} report;
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Serve Laurijssen
  • 9,266
  • 5
  • 45
  • 98
  • i see where you're going with this, but it still requires some fiddling if the last member changes or changes type (could be a uint16_t) – James Kent Jan 18 '18 at 09:50
  • Please note that there may be padding at the end of the struct. Not taking this in account might cause problems if you declare an array of unions. For example code such as: `report arr[n]; memcpy(x, arr, 5*sizeof(arr[0].buffer)); // copy 5 items` might fail horribly. – Lundin Jan 18 '18 at 10:13
  • This code runs into the same problem that the code in (the current edition of) [Lundin](https://stackoverflow.com/users/584518/lundin)'s [answer](https://stackoverflow.com/a/48317949/15168) runs into. See my [comment](https://stackoverflow.com/questions/48317843/array-take-size-of-struct-in-union/48318186?noredirect=1#comment83639148_48317949) for some information. – Jonathan Leffler Jan 18 '18 at 17:22
0

The solutions other people have proposed here will work. However, they are all hacks, due to your use of a union for ease of conversion to bytes. There is a cleaner solution, which is to do away with the union. Simply make your struct the top-level type, and have your function that sends data over USB take parameters of (void * data, size_t length) . Then, you can easily send any data type over USB.

Gold Dragon
  • 480
  • 2
  • 9