0

The following Rust code:

use std::mem;
fn main() {
    #[derive(Debug)]
    #[repr(C)]
    struct Test {
        y: f32,
        x: u32,
    }
    let a = Test { x: 1, y: 1.2 };
    let y = unsafe { mem::transmute::<Test, u64>(a) };
    let b = unsafe { mem::transmute::<u64, Test>(y) };
    println!("{y} {} {}", b.x, b.y);
}

Works fine: 5361998234 1 1.2

The equivalent (in my opinion) C++ code

#include <iostream>

struct Test {
    unsigned int x;
    float y;
};

int main() {
    Test a = Test{1, 1.2};
    long y = *reinterpret_cast<long *>(&a);
    Test t = *reinterpret_cast<Test *>(&y);
    std::cout << y << " " << t.x << " " << t.y << std::endl;
}

does not: 1 1 1.4013e-45

I've tried all of the following different ways of doing the reinterpret_cast

#include <iostream>

struct Test {
    unsigned int x;
    float y;
};

int main() {
    Test *a = new Test{1, 1.2};
    long y = *reinterpret_cast<long *>(&a);
    Test t = *reinterpret_cast<Test *>(&y);
    std::cout << y << " " << t.x << " " << t.y << std::endl;
}
#include <iostream>

struct Test {
    unsigned int x;
    float y;
};

int main() {
    Test *a = new Test{1, 1.2};
    long y = *reinterpret_cast<long *>(a);
    Test t = *reinterpret_cast<Test *>(&y);
    std::cout << y << t.x << " " << t.y << std::endl;
}

None of these seem to work, generating different outputs for each. What am I doing wrong? What would be a correct equivalent of the Rust program?

Note: This is for purely recreational purposes, there is no reason for me to do this, I just want to.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
yaxley peaks
  • 156
  • 1
  • 9
  • 2
    hm, not equivalent: your rust first element is a float32, your C++ x the first element is an `unsigned int` (so probably, an int32) – Marcus Müller May 09 '22 at 16:51
  • 3
    *The equivalent (in my opinion) C++ code* — you have different order of struct fields and types of variables, and you transmute/reinterpret to a different type. Why did you make these changes and why do you feel they are equivalent? Why is the printed order different? – Shepmaster May 09 '22 at 16:52
  • 1
    A `long` is neither required nor guaranteed to be 64 bit. That's why there's types such as `uintptr_t` for when you really need to store an address in an integer – UnholySheep May 09 '22 at 16:54
  • Warning is quite clear: https://godbolt.org/z/1adr6Mv4K and seems to "work". – Marek R May 09 '22 at 16:55
  • 2
    Is `long` large enough to hold a pointer on your platform? The fact that printing `y` outputs `1` suggests that it isn't – Alan Birtles May 09 '22 at 16:56
  • 1
    Also for which platform you are compiling? Note that `long` can have different size depending on platform. https://godbolt.org/z/Td3qhcx1z – Marek R May 09 '22 at 16:58
  • @Shepmaster Just stuff left over from when i was messing around, the following rust works just as fine though, https://godbolt.org/z/3cMdP1Gbh – yaxley peaks May 09 '22 at 17:03
  • 2
    `long y = *reinterpret_cast(a);` is undefined Behaviour in C++ as `long` and `Test` are not related types. So is `Test t = *reinterpret_cast(&y);` for the same reason. Undefined Behaviour _"...Renders the entire program meaningless...."_ https://en.cppreference.com/w/cpp/language/ub `std::memcpy` is probably the only option. – Richard Critten May 09 '22 at 17:07
  • @MarekR the following doesn't work https://godbolt.org/z/d31srT7jr , why? – yaxley peaks May 09 '22 at 17:10
  • @yaxleypeaks typo: https://godbolt.org/z/ze5oYoPzG – Marek R May 09 '22 at 17:12
  • @RichardCritten: `Test` and `unsigned int` aren't "related" in the usual sense the word is used (inheritance), yet casting a pointer-to-structure as pointer-to-first-member is allowed as long as the structure is standard-layout. That doesn't help here, but it shows that "related types" is not the correct criteria. – Ben Voigt May 09 '22 at 17:12
  • @yaxleypeaks: That may appear to work, but it's still *wrong*. – Ben Voigt May 09 '22 at 17:14
  • @yaxleypeaks please note that this code opens word of [nasal demons](https://en.wikipedia.org/wiki/Undefined_behavior). – Marek R May 09 '22 at 17:16
  • @BenVoigt yes i know, the program is undefined behaviour. I know that very well. I just wanted to have some fun. I realize I am to never use it in production – yaxley peaks May 09 '22 at 17:16
  • How big is `long` on your system? 4 bytes, by chance? – user253751 May 09 '22 at 18:07
  • `sizeof(long) >= sizeof(int)` and almost always they are equal. The only cases I know when it's larger are systems with `sizeof(int)` equal to 2. – Swift - Friday Pie May 09 '22 at 18:24
  • @Swift-FridayPie `long` is 64-bit on 64-bit Linux and macos: https://en.cppreference.com/w/cpp/language/types – Alan Birtles May 10 '22 at 05:32
  • @AlanBirtles for 10 years I'm working on a 64bit linux distro and that's not the case on that one, at least. I saw that being a brief case for 2.6 kernel. `long long` takes 64bit mantle. – Swift - Friday Pie May 10 '22 at 11:23
  • @Swift-FridayPie I could be wrong, I avoid using `long` on any platform for this reason but everything I've read says 64-bit Linux is usually 64-bit longs, e.g. https://stackoverflow.com/questions/10040123/long-type-64bit-linux https://lwn.net/images/pdf/LDD3/ch11.pdf – Alan Birtles May 10 '22 at 16:19

2 Answers2

2

In C++, reinterpret_cast doesn't relax strict aliasing rules, so it cannot be used for punning arbitrary types. The only transmutation allowed universally is to inspect an object as a sequence of narrow character (char or signed char or unsigned char and/or std::byte).

You should instead use std::memcpy as Richard Critten suggested in a comment. In most cases the compiler will generate the same efficient machine code, but also handle aliasing assumptions correctly.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
-1

I'm not sure if it this helps, but I wrote what seems to be a rough equivalent of Rust's std::mem::transmute using a union, based on this reddit post: https://www.reddit.com/r/cpp/comments/1139u3v/equivalent_of_rusts_memforget_for_avoiding/j8qdy1a/

template <class To, class From>
std::enable_if_t<sizeof(To) == sizeof(From) && alignof(To) == alignof(From), To>
transmute(From from) {
    union Union {
        From from;
        To to;

        ~Union() {}
    };

    Union un = {};
    un.from = std::move(from);
    return std::move(un.to);
}
xyz
  • 113
  • 9