Jemtaly
Jemtaly

Reputation: 151

Reinterpret cast in C - cannot take the address of an rvalue

I have two structs struct Temp and struct MyTemp with the same memory layout, and I want to cast a return value of type struct Temp to type struct MyTemp.

For example, in C++, I can achieve this goal in a single line (without any temporary variable) like this:

#include <utility>

struct Temp {
    int a;
    int b;
};

struct MyTemp {
    int a;
    int b;
};

struct Temp get_temp(int a, int b) {
    struct Temp temp = {
        .a = a,
        .b = b,
    };
    return temp;
}

int main() {
    struct MyTemp mt = reinterpret_cast<MyTemp &&>(std::move(get_temp(1, 2)));
}

However, I can't find an equivalent in C, for example, the following code in C does not compile:

struct Temp {
    int a;
    int b;
};

struct MyTemp {
    int a;
    int b;
};

struct Temp get_temp(int a, int b) {
    struct Temp temp = {
        .a = a,
        .b = b,
    };
    return temp;
}

int main() {
    struct MyTemp mt = *(struct MyTemp *)&get_temp(1, 2);
    // error: cannot take the address of an rvalue of type 'struct Temp'
}

So my question is, is there any way to do the same thing in C that I did in C++? If not, why? And why can std::move bypass this trouble?

Upvotes: 3

Views: 203

Answers (3)

dbush
dbush

Reputation: 224842

The error you're getting is because a value returned from a function is not an lvalue (i.e. it doesn't designate an object) and therefore has no address to be taken. You can get around this by assigning the return value to a temporary:

struct Temp t = get_temp(1, 2);
struct MyTemp mt = *(struct MyTemp *)&t;

However this is a strict aliasing violation as it is not allowed to reinterpret a struct of one type through a pointer to a struct of another type.

What you can do in C (which you can't do in C++) is use a union of these two structs to do the desired reinterpretation:

union MyUnion {
    struct Temp t;
    struct MyTemp mt;
};

union MyUnion u = { .mt = get_temp(1, 2) };
print("t.a=%d, t.b=%d\n", u.t.a, u.t.b);

This is allowed because the two structs have a common initial sequence, i.e. they start with one or more fields of the same type, and in such a case it is allowed to read one of these common initial members in either struct through the union.

Such a use case however most likely indicates a design issue. If your code requires this type of conversion, it should probably be refactored to avoid having to do this type of reinterpretation.

Upvotes: 3

Lundin
Lundin

Reputation: 214770

In general and contrary to popular belief, C doesn't really allow wild and crazy pointer-based conversions between all manner of unrelated types. Or rather, as far as the rules of pointer conversion alone go, the conversion itself may be allowed, but the outcome or pointer dereferencing from there on rarely has well-defined behavior.


The rules for compatibility of structs in C specifically are quite detailed and pedantic (C17 6.2.7):

(I've reformatted the wall of text from the standard into a bullet list readable by humans.)

Two types have compatible type if their types are the same. /--/
Moreover, two structure, union, or enumerated types declared in separate translation units are compatible if their tags and members satisfy the following requirements:

  • If one is declared with a tag, the other shall be declared with the same tag.

  • If both are completed anywhere within their respective translation units, then the following additional requirements apply:

    there shall be a one-to-one correspondence between their members such that each pair of corresponding members are declared with compatible types; if one member of the pair is declared with an alignment specifier, the other is declared with an equivalent alignment specifier; and if one member of the pair is declared with a name, the other is declared with the same name.

  • For two structures, corresponding members shall be declared in the same order.

  • For two structures or unions, corresponding bit-fields shall have the same widths.

In your case there are two different tags (and are in the same translation unit too?) so it fails on that - the structs are not compatible and therefore you cannot do wild pointer conversions between them or you'll be violating the strict aliasing rules, invoking undefined behavior.


As for your compiler error, it's not related to structs but fundamental C. The return value of a function is a so-called rvalue and therefore you cannot take its address. You'll have to store it somewhere first.


Regarding solutions to the problem, the most obvious one is to get rid of one of the structs since it seems superfluous.

If you for some reason must have two structs with identical members, then you can typedef one type into another name (you won't be able to use "struct tag" coding style then).

Failing all of that because of some severely weird requirements... toss both structs in a union. That's truly obfuscated and weird code:

typedef union
{
  struct Temp {
    int a;
    int b;
  } temp;
  struct MyTemp {
    int a;
    int b;
  } my_temp;
} last_resort_t;

Now while that union is visible to your translation unit, it is well-defined to do wild and crazy casts between the two struct types and/or the union type, under an exception in "strict aliasing" as well as an oddball rule called "common initial sequence". But why would you...

Upvotes: 1

Eric Postpischil
Eric Postpischil

Reputation: 223795

Standard ways in C to reinterpret memory as a different type are to copy the bytes or to use a union. Neither of these will operate on a mere value (a value that is not an lvalue). You will need to use a temporary object, as in:

struct MyTemp m;
memcpy(&m, (struct Temp []) { get_temp(1, 2) }, sizeof m);

or:

struct MyTemp m = (union { struct Temp t; struct MyTemp m; }) { get_temp(1, 2) } .m;

However, any program that has a “need” for this should be reexamined with the goal of redesigning it to avoid the need for this.

Upvotes: 3

Related Questions