0

I'm trying to grok what exactly you get from the easement on aligned variables in C99:

Exception to strict aliasing rule in C from 6.5.2.3 Structure and union members

Does it give you carte blanche on casting to that union, if the original write was done through a pointer to one of the aligned structs as below?

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

struct Foo { char t; int i; };

struct Bar { char t; float f; };

union FooBar {
    struct Foo foo;
    struct Bar bar;
};

void detector(union FooBar *foobar) {
    if (((struct Foo*)foobar)->t == 'F')
       printf("Foo %d\n", ((struct Foo*)foobar)->i);
    else
       printf("Bar %f\n", ((struct Bar*)foobar)->f);
}

int main() {
    struct Foo *foo = (struct Foo*)malloc(sizeof(struct Foo));
    struct Bar *bar = (struct Bar*)malloc(sizeof(struct Bar));

    foo->t = 'F';
    foo->i = 1020;
    detector((union FooBar*)foo);

    bar->t = 'B';
    bar->f = 3.04;
    detector((union FooBar*)bar);

    return 0;
}

Note in the second call, t was written as a "bar's t" but then in order to discern which kind it has, the detector reads it as a "foo's t"

My reaction coming from C++ would be that you'd only be able to do it if you had "allocated it as a FooBar union in the first place". It's counter-intuitive to me to imagine this as legal, but for dynamic allocations in C there's no such thing. So if you can't do that, what exactly can you do with a dynamic memory allocation such as the above under this exception?

Community
  • 1
  • 1
  • Don't cast the result of `malloc` & friends in C or `void *` to/from other pointers in general. – too honest for this site Nov 25 '15 at 13:37
  • @Olaf I write code designed to be compiled under both C and C++. So I program in the nonexistent "C/C++" language, which includes the ability to use C++ as a static analyzer while building a piece of tech that can compile under C89 also. [It's a hobby of mine...](http://blog.hostilefork.com/c-casts-for-the-masses/) – HostileFork says dont trust SE Nov 25 '15 at 13:39
  • So you do not use VLAs and other C99/C11 features. And you don't care about the different semantics of other contructs either. Bad approach. But as you don't do that professional, I do not mind much. – too honest for this site Nov 25 '15 at 13:41
  • @Olaf: I don't quite get the attitude. – gnasher729 Nov 25 '15 at 13:47
  • @gnasher729: Restricting C to C89 just to make it compile with C++ for static code analysis is just a bad idea. It does not use features added since then to make your code safer and better to maintain. But if you only know how to use a hammer, every problem looks like a nail. – too honest for this site Nov 25 '15 at 14:39
  • @Olaf Thanks for the offer of last word: *"A similar argument to yours could be made about C++ vs C itself. The power, type safety, expressiveness, etc. outguns C by miles--especially these days. But there are good reasons people don't use it for some tasks. Then a similar argument about the safety and elegance of Haskell vs the muck of imperative C++. And so on. Given the mire of such discussions, it's better to save comments for the question actually being asked, when the question is perfectly clear."* – HostileFork says dont trust SE Nov 25 '15 at 14:59

2 Answers2

2

If Foo and Bar have different alignment, you shouldn't do that already for that reason alone. The union will have the maximum alignment of the two, and casting the one with the smaller value will give you a union that is not correctly aligned.

Your code is not a good example for the aliasing rules, because you basically don't have aliasing here. But in general, casts to another type are always bad in cases where you may have aliasing. Your compiler may make assumptions about two (or more) pointers that a code sees. If they are of different type (with exception of char types) the compiler can assume that they never point to the same object.

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
  • In [@dbush's answer](http://stackoverflow.com/a/33919291/211160) he suggests that for a lone-allocated struct, the alignment would not be an issue... as malloc returns memory "which is suitably aligned for any kind of variable". It suggests the above code is okay...would you agree? – HostileFork says dont trust SE Nov 25 '15 at 15:05
  • @HostileFork, if and only if it is obtained by `malloc`. If you then try to use the same code on variables or maybe if you use C++'s `new` (as you seem also want to use C++), you may encounter a BUS error. – Jens Gustedt Nov 25 '15 at 17:09
  • Ok, thanks. *(Using a C++ compiler but not the C++ allocator--unless it's debug build code running some kind of parallel check to the low-level stuff and wanting to use C++ standard library for that...)* – HostileFork says dont trust SE Nov 25 '15 at 19:08
  • Neither gcc nor clang can reliably handle all cases where storage used as one type is later used as another, even if every read is performed using the same type as the preceding write, and even if each lvalue of a different type is derived from a common lvalue after any preceding operation with a different type, and abandoned before any succeeding operation with a new type. Even if code doesn't use aliasing as written, gcc may optimize the code so as to introduce aliasing but then fail to handle the consequences thereof. – supercat Jun 20 '18 at 21:48
2

If you did something like this:

struct Foo foo;
struct Bar bar;
...
detector((union FooBar*)&foo);
detector((union FooBar*)&bar);

Then you might have issues with alignment, since the compiler could place each of these structs on the stack in a way that might not align properly for the other.

But because in your case you're dynamically allocating the memory for each struct, alignment is not an issue.

From the man page for malloc:

For calloc() and malloc(), the value returned is a pointer to the allocated memory, which is suitably aligned for any kind of variable, or NULL if the request fails.

But if you want to be sure that this won't be an issue, just declare an instance of the union instead of the containing struct anyplace where a function expecting the union would be called.

dbush
  • 205,898
  • 23
  • 218
  • 273
  • Okay...well I wasn't thinking of alignment specifically, but a general dislike of using a different allocated type and then casting it, so good to hear there's a go-to rule to help with that. And I was wondering specifically about this question of malloc, vs. using a union as a stack variable...so you're saying that clause about calloc/malloc makes the above code okay in C... *and* that it would be okay if I had allocated as a union in the first place in the stack-allocation case? – HostileFork says dont trust SE Nov 25 '15 at 14:49
  • @HostileFork Yes *if* the struct was allocated dynamically *by itself* (i.e. not part of a dynamically allocated array in which case the alignment would be thrown off), then you don't have to worry about alignment issues. – dbush Nov 25 '15 at 14:53
  • It's worth bearing in mind that this excerpt doesn't mean you can just allocate a `struct Foo` and expect casting it to `struct Bar` to magically work, especially due to possible differences in structure padding and size differences between `int` and `float`. For that reason, again, allocate `union FooBar` if you want to be safer, not individual instances of `struct Foo` and `struct Bar`. It's one extra level of indirection in the source code, but I'd say it's worth it if you want this sort of ability. –  Nov 25 '15 at 16:25
  • I actually turned out restructuring and doing what I was doing another way--due to the tenuous conditions under which this would work...largely because of the feedback here. Thanks for the reminder on the malloc alignment issue! – HostileFork says dont trust SE Nov 27 '15 at 01:55