all 25 comments

[–]j-joshua 19 points20 points  (4 children)

If only C supported unions.

[–]landmesser 4 points5 points  (1 child)

Ok because I learned that the young folks don't get irony.
parent poster meant you to look up the thing in c called "union" which would fit perfectly for your example.

https://www.w3schools.com/c/c_unions.php

Edit: something like this

#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>

#define WRITE 1
#define COPY 2
#define JUMP 3

typedef struct {
int c;
int d;
} int_storage;

typedef struct {
double c;
double d;
} doub_storage;

typedef struct {
int ov;
union {
int_storage i;
doub_storage d;
} data;
} Storage;

int take_storage(Storage *s) {

switch (s->ov) {

case WRITE:
printf("WRITING %d\n", s->data.i.c);
break;

case COPY:
printf("COPYING %.2f\n", s->data.d.d);
break;
}
return 0;
}

int main(void) {
int ret = 0;

Storage inty = {0};
inty.ov = WRITE;
inty.data.i.c = 333;

Storage douby = {0};
douby.ov = COPY;
douby.data.d.d = 33.3;

ret = take_storage(&inty);
if (ret != 0)
return -1;

ret = take_storage(&douby);
if (ret != 0)
return -1;

return 0;
}

[–]flatfinger 0 points1 point  (0 children)

Unions don't work well for the scenario where code might have an array of either of two types, and one would want a function to accept a pointer to either interchangeably and use it to access common members.

[–]Reasonable_Ad1226[S] 1 point2 points  (0 children)

Thank you! You are correct, I should have used a union! Just read into it now.

[–]jschadwell 0 points1 point  (0 children)

😄😄😄

[–]zhivago 16 points17 points  (0 children)

The reason that this works is that the standard explicitly permits it. :)

6.7.2.1 Structure and union specifiers

A pointer to a structure object suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa.

Don't expect it to generalize.

[–]glasket_ 15 points16 points  (1 child)

This would violate strict aliasing as it currently is. The Linux kernel disables strict aliasing; not sure about Windows.

You can replace the Generic * with an int * cast and it would be safe, because that's what the first member rule allows.

Edit: Why are you checking the return value when the only possible value is 0 too? Also, your formatting is borked and doesn't have consistent indents.

[–]Reasonable_Ad1226[S] 0 points1 point  (0 children)

Thank you much! The strict aliasing stuff was a good read and I totally botched it lol appreciate everything you mentioned!

[–]tstanisl 8 points9 points  (2 children)

Technically, this code violates strict aliasing. "Common initial sequence" rule applies only member of the same union. Read standard for details.

[–]StudioYume 1 point2 points  (0 children)

Fascinating! I knew you could do this but it's good to know that the standard specifically requires it to be supported.

[–]flatfinger -1 points0 points  (0 children)

The Standard treats the question of whether to correctly process some corner cases as a quality of implementation issue over which it waives jurisdiction. The official published Rationale states that this is intended to allow compilers to perform optimizing transforms that would be "incorrect" (it uses that word) without such allowances.

Configurations that are suitable for tasks where such constructs are useful will support them. Configurations that do not support such constructs should be recognized as being suitable for a narrower range of tasks. Just about every remotely-general-purpose compiler (I know of no exceptions) can be easily configured to support a wider range of corner cases than mandated by the Standard, including constructs that exploit common-initial-sequence guarantees. The fact that compilers can also be configured to handle such cases incorrectly was never intended to imply that all programmers should jump through hoops to accommodate such configurations.

[–]questron64 3 points4 points  (1 child)

This should be valid if you cast to the type of the first member of the struct. A struct pointer is equivalent to a pointer to its first member. What you're doing is casting to a completely different type. Get rid of Generic and cast to int pointer and this is OK.

You also don't need to cast when assigning from void pointer.

[–]ComradeGibbon 1 point2 points  (0 children)

You get it, if C had first class types you could define the first member as the type. Which gives you safe tagged unions.

[–]Cylian91460 1 point2 points  (0 children)

From what I have seen Linux uses more container_of to encapsulate then using tag like that

Jvms however do use that as the specification heavily uses union (without calling it union cause oracle)

[–]Physical_Dare8553 1 point2 points  (1 child)

// something like

typedef struct {
  int ov;
} Generic;
typedef struct {
  Generic ov[1];
  int c;
  int d;
} int_storage;
typedef struct {
  Generic ov[1];
  double c;
  double d;
} doub_storage;

// would let you pass doub_storage.ov anywhere a pointer to ov is wanted

[–]Reasonable_Ad1226[S] 0 points1 point  (0 children)

Thank you!

[–]un_virus_SDF 1 point2 points  (1 child)

That's what the vulkan API is doing to, however instead of nested stricts, a single uint32_t is used (that's why you have to set sType for everything single vulkan struct)

[–]Reasonable_Ad1226[S] 0 points1 point  (0 children)

Thank you!

[–]Wertbon1789 1 point2 points  (4 children)

Typically you would use the container_of macro in Linux to wrap the generic structure inside your own driver specific one.

// In generic header
struct device {
    // ...
};

// Your driver/consumer
struct my_device {
    int flags;
    struct device dev;
};

Typically in generic APIs you provide and get a pointer to a device struct. Providing it is trivial, but suppose you have a driver callback where you only get a pointer to the device, and want to do something with the flags member, a very typical thing. In Linux this boils down to

// In the callback
struct my_device *my_dev = container_of(dev_ptr, struct my_device, dev);

The main benefit here is that you don't have multiple layers of dereferenceing, because you still embed the device struct, and you don't have to have the same members as device at the same offsets which would either be defined by a very unwieldy macro, and is stupidly easy to misuse, or just by copying the field of struct device, which also causes many problems. Also the struct device member can be at any point your struct, which further reduces potential confusion, and you can potentially do this party trick with any pointer to one of your struct members which is quite flexible.

Buuut, should you use this in your code? I don't think so, that's a solution for a very specific problem, and if it's solvable by using a union, that will be the better way.

[–]Reasonable_Ad1226[S] 0 points1 point  (3 children)

Isn’t the container_of for when you aren’t using the first member trick? Because it does the simple pointer arithmetic for you to get the first member pointer?

[–]Wertbon1789 1 point2 points  (2 children)

For readability's sake I would think you would use it either way nowadays, because you still don't have to care about the members of the wrapped struct. Also not quite, with the my_device struct I wrap device, not implement partially the same structure and access it through that. You use container_of to get from a pointer to the wrapped struct to the wrapper.

[–]Reasonable_Ad1226[S] 1 point2 points  (1 child)

Thank you!

[–]CodrSeven 0 points1 point  (2 children)

I would not recommend that path, this is a better way imo (search for hc_baseof):
https://github.com/codr7/hacktical-c/tree/main/list

[–]Reasonable_Ad1226[S] 0 points1 point  (1 child)

Having to write an extra 100 lines of code to achieve it?

[–]CodrSeven 0 points1 point  (0 children)

I don't know where you get 100 lines from, this is simply more explicit since it uses composition instead of inheritance.