4

Now I know I can implement inheritance by casting the pointer to a struct to the type of the first member of this struct.

However, purely as a learning experience, I started wondering whether it is possible to implement inheritance in a slightly different way.

Is this code legal?

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

struct base
{
    double some;
    char space_for_subclasses[];
};

struct derived
{
    double some;
    int value;
};

int main(void) {
    struct base *b = malloc(sizeof(struct derived));
    b->some = 123.456;
    struct derived *d = (struct derived*)(b);
    d->value = 4;
    struct base *bb = (struct base*)(d);
    printf("%f\t%f\t%d\n", d->some, bb->some, d->value);
    return 0;
}

This code seems to produce desired results , but as we know this is far from proving it is not UB.

The reason I suspect that such a code might be legal is that I can not see any alignment issues that could arise here. But of course this is far from knowing no such issues arise and even if there are indeed no alignment issues the code might still be UB for any other reason.

Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261

2 Answers2

4

As I read the standard, chapter §6.2.6.1/P5,

Certain object representations need not represent a value of the object type. If the stored value of an object has such a representation and is read by an lvalue expression that does not have character type, the behavior is undefined. [...]

So, as long as space_for_subclasses is a char (array-decays-to-pointer) member and you use it to read the value, you should be OK.


That said, to answer

Is char space_for_subclasses[]; necessary?

Yes, it is.

Quoting §6.7.2.1/P18,

As a special case, the last element of a structure with more than one named member may have an incomplete array type; this is called a flexible array member. In most situations, the flexible array member is ignored. In particular, the size of the structure is as if the flexible array member were omitted except that it may have more trailing padding than the omission would imply. However, when a . (or ->) operator has a left operand that is (a pointer to) a structure with a flexible array member and the right operand names that member, it behaves as if that member were replaced with the longest array (with the same element type) that would not make the structure larger than the object being accessed; the offset of the array shall remain that of the flexible array member, even if this would differ from that of the replacement array. If this array would have no elements, it behaves as if it had one element but the behavior is undefined if any attempt is made to access that element or to generate a pointer one past it.

Remove that and you'd be accessing invalid memory, causing undefined behavior. However, in your case (the second snippet), you're not accessing value anyway, so that is not going to be an issue here.

Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261
0

This is more-or-less the same poor man's inheritance used by struct sockaddr, and it is not reliable with the current generation of compilers. The easiest way to demonstrate a problem is like this:

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

struct base
{
    double some;
    char space_for_subclasses[];
};
struct derived
{
    double some;
    int value;
};

double test(struct base *a, struct derived *b)
{
    a->some = 1.0;
    b->some = 2.0;
    return a->some;
}

int main(void)
{
    void *block = malloc(sizeof(struct derived));
    if (!block) {
        perror("malloc");
        return 1;
    }
    double x = test(block, block);
    printf("x=%g some=%g\n", x, *(double *)block);
    return 0;
}

If a->some and b->some were allowed by the letter of the standard to be the same object, this program would be required to print x=2.0 some=2.0, but with some compilers and under some conditions (it won't happen at all optimization levels, and you may have to move test to its own file) it will print x=1.0 some=2.0 instead.

Whether the letter of the standard does allow a->some and b->some to be the same object is disputed. See http://blog.regehr.org/archives/1466 and the paper it links to.

zwol
  • 135,547
  • 38
  • 252
  • 361
  • Yes, I was reading manpages mentioning `struct sockaddr`, `struct sockaddr_in`, etc… and these very manpages inspired me to post this question :) –  May 23 '17 at 21:23
  • Have you managed to get `1.0 2.0` and if so, under what conditions? Because if this technique is so unreliable, how does POSIX ensure that using `struct sockaddr_in` doesn’t end up in UB? –  May 23 '17 at 21:29
  • The reason I’m asking it is that I was hoping that since POSIX can use this poor man’s inheritance, then perhaps I can use it as well, at least on POSIX… so, does POSIX put any more guarantees on top of C that would make it more reliable? –  May 23 '17 at 21:32
  • 1
    https://wandbox.org/permlink/XroTfKL1XOrkQqw8 All right, Got `1.0 2.0`. Thank you for pointing this out to me. How, in this case, may I use networking facilities of POSIX, which require the use of `struct sockaddr_in`, without triggering UB, remains a mystery to me. Am I supposed to put `-fno-strict-aliasing` to any piece of code that uses sockets? Though, this probably is worthy of a different question. –  May 23 '17 at 22:53
  • 1
    @gaazkam This question has already been asked several times wrt `sockaddr`. The short practical answer is "use `getaddrinfo` and treat the sockaddr objects it returns as opaque", and the short language-lawyer answer is that this is a conflict between POSIX and C which has not yet been resolved to anyone's satisfaction. – zwol May 24 '17 at 12:18
  • Thank you. While in general [there definitely seems to be a problem there](https://wandbox.org/permlink/Ul3KUyg0ZcjpxXfG), it should be noted that POSIX [explicitely permits](http://pubs.opengroup.org/onlinepubs/000095399/basedefs/netinet/in.h.html) casting `struct sockaddr_int*` and the likes to `struct sockaddr*` when passing these as arguments to socket functions. Therefore, can it be maintained that doing that cast in this particular scenario ONLY is well-defined on POSIX systems? –  May 24 '17 at 13:25
  • By definition, defining some additional guarantees in a situation where the underlying standard defines nothing should not create any incongruence, so I’m not sure why You write that there is a conflict between C and POSIX. After all, AFAIK, “program execution in a documented manner characteristic of the environment” is one of the explicitly listed outcomes of UB. –  May 24 '17 at 13:26
  • @gaazkam POSIX does indeed intend to specify that casting among sockaddr variants is well-defined, but the text of POSIX does not _directly_ supersede the aliasing rules in C (because the POSIX committee didn't realize they needed to - this has only become a serious problem since the last major update to POSIX), so you can argue that they didn't actually "define some additional guarantees in a situation where the underlying standard defines nothing" and that's how we get to the present day situation where some implementations honor POSIX's intent and others don't. – zwol May 24 '17 at 13:48
  • @gaazkam Note that some implementations honor POSIX's intent by writing a special case for the `sockaddr` structures that supersedes the "normal" C aliasing rules, so you may not get the same result from my test program if you change it to use `sockaddr` and `sockaddr_in` than if you change it to use your own structures that mimic `sockaddr` and `sockaddr_in`. Does that make sense? I know it's convoluted. – zwol May 24 '17 at 13:51
  • "use getaddrinfo and treat the sockaddr objects it returns as opaque" – this might work when connecting to an address, but is insufficient when I receive a packet over from `recvfrom` or have a connection to `accept` and would like to know the IP and port of the sender. –  May 27 '17 at 01:37
  • @gaazkam That is somewhat trickier, but `getnameinfo` will help. Again, I believe this has been addressed in several other places on this site. – zwol May 27 '17 at 15:28
  • Thank you. One last question, if I may: IIUC while it may not be valid to cast pointer types, C allows reinterpreting objects with the help of unions. So, would it be valid to create a union type with two members: `struct sockaddr` and `struct sockaddr_in` – and do the necessary casting by accessing the `sockaddr` or `sockaddr_in` member of this union? –  May 27 '17 at 16:06
  • @gaazkam That _can_ work but takes a fair bit of care. More than I can fit into this comment box. – zwol May 27 '17 at 16:13
  • @zwol: You conflate type punning and strict aliasing here. I agree with your answer wrt. strict aliasing, and this particular use case (the initial structure type is wrong, and there is no union visible); but I do read your answer as if you claimed that `struct sockaddr` requires nonstandard support, which is factually incorrect. I explain my issues [here](https://stackoverflow.com/a/53699702/1475978). Feel free to point out any issues you find, although I do wish to point out that it definitely works in practice, and is quite widely used. – Nominal Animal Dec 10 '18 at 04:59
  • @NominalAnimal: If the non-normative footnote to n1570 6.5p7 had indicated that compiler writers who use the rule as an excuse to throw the Spirit of C out the window in cases that don't actually involve aliasing of seemingly-unrelated lvalues should be unwelcome in polite company, that would have avoided a lot of problems. – supercat Dec 11 '18 at 18:47