<p>C++ disallows <code>reinterpret_cast</code> from <code>int</code> to <code>float</code> as far as I know, and using a <code>union</code> or pointer cast to reinterpret an <code>int</code> to a <code>float</code> or vice versa or even an <code>int</code> to a <code>short[2]</code> is undefined behavior. In C using a union is valid but using a pointer cast <a href="https://stackoverflow.com/q/98650/16217248">remains invalid</a>. Most languages have no facilities to reinterpret the bits of one type to another.</p>
<p>Why are languages often averse to reinterpret/bitcasting a type to an <em>'unrelated'</em> type. What are the advantages or disadvantages of allowing this?</p>

<type-conversion>
Why is type reinterpretation considered highly problematic in many programming languages?
CC BY-SA 4.0
2023-07-13T03:25:26.630Z
Would-have-been dupe: https://langdev.stackexchange.com/questions/2326/what-are-pros-and-cons-of-tagged-union-types
rydwolf
CC BY-SA 4.0
2023-07-13T03:47:34.037Z
C++ disallows conversion from `int` to `float`, but you _can_ convert from `const int&` to `const float&`, which is basically the same thing.
Bbrk24
CC BY-SA 4.0
2023-07-13T13:36:51.220Z
I don't doubt you, but when I first got my degree 4 decades ago, "Most languages have facilities to reinterpret the bits of one type to another." would have been a truer statement. Its arguably a requirement for a system's programming language.
T.E.D.
CC BY-SA 4.0
2023-07-14T09:37:37.490Z
Besides the reasons in the answers, there is also a general assumption that pointers of unrelated types point to different memory.
Simon Richter
CC BY-SA 4.0
2023-07-15T02:57:44.617Z
@Bbrk24 Converting from `const int &` to `const float &` and then reading a `float` value is undefined behavior. What you can legally do to get the effect you want is copy the bytes of the `int`, a `char` at a time, into a `float` (e.g. using `memcpy`).
rpjohnst
CC BY-SA 4.0
2023-10-06T23:08:06.360Z
What "many programming languages" are you talking about?  Its pretty much only C++, and even that is fixed by C++20 with `std::bitcast`
Chris Dodd
<h2>Managed Pointers</h2>
<p>Casting a managed <code>Object</code> to an unmanaged <code>byte*</code> could lead to the object getting deallocated while you're manipulating the underlying data, and the other way around is just asking for the garbage collector to give you UB. Not all languages have a specified or stable GC header, so it's a bad idea to let you manipulate managed objects.</p>
<p>Even if you never dereference the pointer, this can cause UB. Consider the following code:</p>
<pre class="lang-cs prettyprint-override"><code>Object obj = // ...
++(long*)obj;
</code></pre>
<p>Suddenly, the data held by the object is now treated as the GC header, which is problematic for obvious reasons.</p>
<p><code>Object</code> is not a true top type in C# for this exact reason. Instead, there are two top-like types, <code>Object</code> and <code>void*</code>, and there’s no way to convert between the two.</p>

CC BY-SA 4.0
2023-07-13T03:41:56.413Z
2023-07-13T03:51:56.520Z
2023-07-13T03:51:56.520Z
Bbrk24
CC BY-SA 4.0
2023-07-13T06:52:40.457Z
I think you missed the question by one level of indirection ;)
feldentm
CC BY-SA 4.0
2023-10-06T20:13:48.647Z
By my understanding, the CompCert dialect of C, which is designed to allow optimizations to be proven sound, allows data to be accessed via lvalues of any non-pointer data types interchangeably, or by lvalues of single indirect pointer-to-data types interchangeably, or double indirect pointer-to-data-types, etc. but does not storage to be accessed via lvalues representing different levels of indirection, for reasons like those alluded to in this answer.
supercat
CC BY-SA 4.0
2023-10-14T21:05:03.587Z
I think mixing pointer arithmetic and a never-conservative GC is what's the bad idea. Some other error cases: pointers to array members, pointers to struct/class fields, and array slices, do not have access to the GC headers either. Also "the data held by the object is treated as the GC header" requires the GC to follow scalars which most do not do.
Longinus
<p>While casting the pointers to these types is often allowed, casting the values usually isn't. I guess the reason is that the cast has unknown arbitrary cost and requires code. My research project currently has such a cast operation and I eventually resorted to creating a hidden variable, moving the data to it, casting it to the target pointer type and loading the value back. Simply, because it was too cumbersome getting all the pairs right that one would need to consider. I mean, if you think about physical representation, it could be that casting <code>long</code> to <code>float[2]</code> results in moving one value from an integer register somewhere to two float registers in a coprocessor.</p>
<p>Also, there is very little things that you really want to do this way. The code you'd write with this cast is essentially loading all the data into registers just to decide that those were tho wrong registers and that you need to move them to another set of registers. Why not using the correct type in the first place?</p>

CC BY-SA 4.0
2023-07-13T07:00:35.777Z
2023-07-13T07:00:35.777Z
feldentm
CC BY-SA 4.0
2023-07-13T13:50:43.010Z
Why would `reinterpret_cast` require any code? It doesn't make any change to the value, it just uses the existing bit pattern.
Barmar
CC BY-SA 4.0
2023-07-13T14:03:17.820Z
@Barmar The bit pattern that is existing _where_? Part of the point of the answer is that the _where_ matters.
Pablo H
CC BY-SA 4.0
2023-07-13T14:34:35.237Z
The variable that you're reinterpreting. It shouldn't matter where it is.
Barmar
CC BY-SA 4.0
2023-07-13T19:51:56.727Z
@Barmar But processors need to hold the data somewhere, right?
feldentm
CC BY-SA 4.0
2023-07-13T22:13:53.463Z
@Barmar what if I want to add it to a larger 64-bit int, and it's sitting in xmm0?
Junelilly
CC BY-SA 4.0
2023-07-14T02:41:59.850Z
Good point. I was only thinking of variables in memory. @notmyrealname
Barmar
<p><strong>Because it <em>is</em> problematic</strong>. Its a dangerous thing to do. Errors in code that does it can be devilishly hard to track down.</p>
<p>Perhaps an illustration will help.</p>
<p>I once had some code in K&amp;R C that was calling a function with a float as an argument. Simplest thing in the world really. What could go wrong?</p>
<p>The problem was the function wasn't declared before it was called in one program unit (an error on my part). Under the K&amp;R rules this compiler implemented, that meant the compiler assumed it existed as a function returning int, and any parameters sent to it were supposed to be int as well. So the compiler's code it generated to call this routine converted the float parameter to an int before pushing it on the stack.</p>
<p>The code implementing the function off in a different .c file of course knew nothing about this, and when it pulled that parameter off the stack, treated the int as a float.</p>
<p>This was a nightmare to debug, because as far as I knew there were no compilation or link errors, and everything looked coded correctly. The types in the source code matched up. The symptom (my data getting trashed) had no obvious relation to the ultimate coding error. <em>It didn't look like I was doing anything unsafe or dangerous anywhere</em>. Days were wasted.</p>
<p>The moral here (aside from &quot;K&amp;R C sucks&quot;), is that <strong>type reinterpretation of bits is a hazardous operation</strong>. If the coder does something wrong it can cause really difficult to track down bugs.</p>
<p>Its perhaps worth thinking about this as the dataflow equivalent of a <a href="https://en.wikipedia.org/wiki/Goto" rel="noreferrer">goto statement</a> for control flows. A code maintainer looking at an object and seeing its type will have expectations about how the data contained by that object will be used, how it can be changed, and what its values can be. All of those assumptions are out the window once its type representation has been munged.</p>
<p>This is why modern languages are (usually) designed to make such operations difficult and ugly. Not happening silently, like my K&amp;R compiler/linker was doing. The idea is that the language should both discourage casual use of this feature, and implement it in a way that makes its existence in source code very difficult to miss.</p>
<hr />
<p>Of course the obvious next question is, why allow it at all? Well, its tough to do a lot of low-level programming without it. For example, the entire concept of a <a href="https://en.wikipedia.org/wiki/Protocol_stack" rel="noreferrer">protocol stack</a> relies on every layer of protocol code treating the contents of higher layers as untyped data. Higher layers in fact know some of that data is a specific header structure, and the highest layer most likely wants to transmit some floats.</p>

CC BY-SA 4.0
2023-07-13T14:15:14.150Z
2023-08-03T18:18:47.970Z
2023-08-03T18:18:47.970Z
T.E.D.
CC BY-SA 4.0
2023-10-04T20:03:13.233Z
That's why we need implicit declaration errors/warnings.
CPlus
<h2>Non-Portability</h2>
<p>Particularly with pointers and floating-point numbers, there was a lot of hardware out there where even loading an invalid value into certain registers would cause a CPU fault and crash the program.  There are even CPUs where a signed integer value with all bits set to 1 is a “trap” value that generates a hardware exception, and the C standard (up through C17) allowed compilers to use the native instructions, rather than check for this.  Since C is a relatively low-level language often used for OS kernels and device drivers, the Standards committee wanted to let code that did this compile efficiently and transparently, but that meant the results would be unpredictable.</p>
<p>C and C++ don’t prohibit all bit-casts whatsoever, only certain ones that are known to cause problems.  C (but not C++) allows type-punning in unions.  Both allow casts between pointers to layout-compatible <code>struct</code> objects, such as the <code>sockaddr</code> family of BSD sockets.  They also define a safe conversion from any object pointer to <code>void*</code> to <code>inptr_t</code> or <code>unintptr_t</code>, and back.  In many cases, <code>memcpy()</code> on the object representation is guaranteed to work.</p>
<p>C++20 does add <code>std::bit_cast</code>, whose behavior is merely “unspecified,” not undefined (if it does not produce a trap representation, so a round-trip conversion is safe).</p>
<p>A new language designed today might not care about supporting weird architectures from the 1960s.  But C did, fifty years ago.</p>

CC BY-SA 4.0
2023-07-14T17:09:26.127Z
2023-07-14T17:28:17.843Z
2023-07-14T17:28:17.843Z
Davislor
CC BY-SA 4.0
2023-07-14T17:12:19.453Z
*For example, they specify that all `int` types have no padding bits or trap representations* - I thought it was only all the `(u)intN_t`.
CPlus
CC BY-SA 4.0
2023-07-14T17:19:03.623Z
@user16217248-OnStrike Whoops. integer types **are** allowed to have padding bits. A value with all-bits-1 is allowed to be a trap representation, too.
Davislor
CC BY-SA 4.0
2023-07-14T17:20:57.687Z
But that's just `-1`
CPlus
CC BY-SA 4.0
2023-07-14T17:24:02.510Z
@user16217248-OnStrike That’s just `-1` *on a two’s-complement machine*. C also supports one’s-complement and sign-and-magnitude, and the Standard specifically states that it’s up to the implementation whether that representation is a trap representation.
Davislor
CC BY-SA 4.0
2023-07-14T17:25:04.590Z
As of recently (C23), two's complement is required by the C standard.
CPlus
CC BY-SA 4.0
2023-07-14T17:25:26.340Z
@user16217248-OnStrike Okay, I was going by C17.
Davislor
CC BY-SA 4.0
2023-07-14T17:26:05.830Z
But it's true that C17 has no such requirement.
CPlus
CC BY-SA 4.0
2023-07-14T17:26:39.850Z
@user16217248-OnStrike Edited to clarify which standard I was talking about. Thanks for the info about C23.
Davislor
<h2>It forces a well defined interpretation of all types involved</h2>
<p>In order to have a well-defined reinterpretation of a cast between two types, their exact binary representations must always be well defined over all time on all platforms.</p>
<p>While, <code>float</code>, for example, is usually represented as IEEE 754, but the C standard does not require this and your program could have a radically different effect if some other format for floats is used.</p>
<p>By making reinterpreting undefined behavior language implementers have free choice how to represent each type without fear of breaking any well-defined programs.</p>
<p>Some languages guarantee that some specific types can be safely cast. For example, <a href="https://doc.rust-lang.org/std/simd/struct.Simd.html" rel="noreferrer">in Rust, <code>Simd&lt;T, N&gt;</code></a> is guaranteed to be safely transmuted to <code>[T, N]</code>. It may make sense to safely allow this for some specific combinations of types. However, allowing it for arbitrary types means the format of everything needs to be fixed.</p>

CC BY-SA 4.0
2023-07-13T08:01:28.057Z
2023-10-06T07:05:27.277Z
2023-10-06T07:05:27.277Z
mousetail 'he-him'
# Why is type reinterpretation considered highly problematic in many programming languages?
Posted by: username_0 on 2023-07-13
C++ disallows `reinterpret_cast` from `int` to `float` as far as I know, and using a `union` or pointer cast to reinterpret an `int` to a `float` or vice versa or even an `int` to a `short[2]` is undefined behavior. In C using a union is valid but using a pointer cast [remains invalid](https://stackoverflow.com/q/98650/16217248). Most languages have no facilities to reinterpret the bits of one type to another.

Why are languages often averse to reinterpret/bitcasting a type to an *'unrelated'* type. What are the advantages or disadvantages of allowing this?
**Tags:** type-conversion
> **Comments:**
> **username_1**: Would-have-been dupe: https://langdev.stackexchange.com/questions/2326/what-are-pros-and-cons-of-tagged-union-types  
> **username_2**: C++ disallows conversion from `int` to `float`, but you _can_ convert from `const int&` to `const float&`, which is basically the same thing.  
> **username_3**: I don't doubt you, but when I first got my degree 4 decades ago, "Most languages have facilities to reinterpret the bits of one type to another." would have been a truer statement. Its arguably a requirement for a system's programming language.  
> **username_4**: Besides the reasons in the answers, there is also a general assumption that pointers of unrelated types point to different memory.  
> **username_5**: @Bbrk24 Converting from `const int &` to `const float &` and then reading a `float` value is undefined behavior. What you can legally do to get the effect you want is copy the bytes of the `int`, a `char` at a time, into a `float` (e.g. using `memcpy`).  
> **username_6**: What "many programming languages" are you talking about?  Its pretty much only C++, and even that is fixed by C++20 with `std::bitcast`  
---
## 5 answers
### Answer by username_14 on 2023-07-13. Score: 24
## It forces a well defined interpretation of all types involved

In order to have a well-defined reinterpretation of a cast between two types, their exact binary representations must always be well defined over all time on all platforms.

While, `float`, for example, is usually represented as IEEE 754, but the C standard does not require this and your program could have a radically different effect if some other format for floats is used.

By making reinterpreting undefined behavior language implementers have free choice how to represent each type without fear of breaking any well-defined programs.

Some languages guarantee that some specific types can be safely cast. For example, [in Rust, `Simd`](https://doc.rust-lang.org/std/simd/struct.Simd.html) is guaranteed to be safely transmuted to `[T, N]`. It may make sense to safely allow this for some specific combinations of types. However, allowing it for arbitrary types means the format of everything needs to be fixed.
---
### Answer by username_3 on 2023-07-13. Score: 14
**Because it *is* problematic**. Its a dangerous thing to do. Errors in code that does it can be devilishly hard to track down.

Perhaps an illustration will help.

I once had some code in K&R C that was calling a function with a float as an argument. Simplest thing in the world really. What could go wrong?

The problem was the function wasn't declared before it was called in one program unit (an error on my part). Under the K&R rules this compiler implemented, that meant the compiler assumed it existed as a function returning int, and any parameters sent to it were supposed to be int as well. So the compiler's code it generated to call this routine converted the float parameter to an int before pushing it on the stack.

The code implementing the function off in a different .c file of course knew nothing about this, and when it pulled that parameter off the stack, treated the int as a float.

This was a nightmare to debug, because as far as I knew there were no compilation or link errors, and everything looked coded correctly. The types in the source code matched up. The symptom (my data getting trashed) had no obvious relation to the ultimate coding error. *It didn't look like I was doing anything unsafe or dangerous anywhere*. Days were wasted.

The moral here (aside from "K&R C sucks"), is that **type reinterpretation of bits is a hazardous operation**. If the coder does something wrong it can cause really difficult to track down bugs.

Its perhaps worth thinking about this as the dataflow equivalent of a [goto statement](https://en.wikipedia.org/wiki/Goto) for control flows. A code maintainer looking at an object and seeing its type will have expectations about how the data contained by that object will be used, how it can be changed, and what its values can be. All of those assumptions are out the window once its type representation has been munged.

This is why modern languages are (usually) designed to make such operations difficult and ugly. Not happening silently, like my K&R compiler/linker was doing. The idea is that the language should both discourage casual use of this feature, and implement it in a way that makes its existence in source code very difficult to miss.

---

Of course the obvious next question is, why allow it at all? Well, its tough to do a lot of low-level programming without it. For example, the entire concept of a [protocol stack](https://en.wikipedia.org/wiki/Protocol_stack) relies on every layer of protocol code treating the contents of higher layers as untyped data. Higher layers in fact know some of that data is a specific header structure, and the highest layer most likely wants to transmit some floats.
> **Comments:**
> **username_0** (2023-10-04): That's why we need implicit declaration errors/warnings.  
---
### Answer by username_13 on 2023-07-14. Score: 10
## Non-Portability

Particularly with pointers and floating-point numbers, there was a lot of hardware out there where even loading an invalid value into certain registers would cause a CPU fault and crash the program. There are even CPUs where a signed integer value with all bits set to 1 is a “trap” value that generates a hardware exception, and the C standard (up through C17) allowed compilers to use the native instructions, rather than check for this. Since C is a relatively low-level language often used for OS kernels and device drivers, the Standards committee wanted to let code that did this compile efficiently and transparently, but that meant the results would be unpredictable.

C and C++ don’t prohibit all bit-casts whatsoever, only certain ones that are known to cause problems. C (but not C++) allows type-punning in unions. Both allow casts between pointers to layout-compatible `struct` objects, such as the `sockaddr` family of BSD sockets. They also define a safe conversion from any object pointer to `void*` to `inptr_t` or `unintptr_t`, and back. In many cases, `memcpy()` on the object representation is guaranteed to work.

C++20 does add `std::bit_cast`, whose behavior is merely “unspecified,” not undefined (if it does not produce a trap representation, so a round-trip conversion is safe).

A new language designed today might not care about supporting weird architectures from the 1960s. But C did, fifty years ago.
> **Comments:**
> **username_0** (2023-07-14): *For example, they specify that all `int` types have no padding bits or trap representations* - I thought it was only all the `(u)intN_t`.  
> **username_13** (2023-07-14): @user16217248-OnStrike Whoops. integer types **are** allowed to have padding bits. A value with all-bits-1 is allowed to be a trap representation, too.  
> **username_0** (2023-07-14): But that's just `-1`  
> **username_13** (2023-07-14): @user16217248-OnStrike That’s just `-1` *on a two’s-complement machine*. C also supports one’s-complement and sign-and-magnitude, and the Standard specifically states that it’s up to the implementation whether that representation is a trap representation.  
> **username_0** (2023-07-14): As of recently (C23), two's complement is required by the C standard.  
> **username_13** (2023-07-14): @user16217248-OnStrike Okay, I was going by C17.  
> **username_0** (2023-07-14): But it's true that C17 has no such requirement.  
> **username_13** (2023-07-14): @user16217248-OnStrike Edited to clarify which standard I was talking about. Thanks for the info about C23.  
---
### Answer by username_2 on 2023-07-13. Score: 5
Managed Pointers
Casting a managed Object to an unmanaged byte* could lead to the object getting deallocated while you're manipulating the underlying data, and the other way around is just asking for the garbage collector to give you UB. Not all languages have a specified or stable GC header, so it's a bad idea to let you manipulate managed objects.
Even if you never dereference the pointer, this can cause UB. Consider the following code:
Object obj = // ...
++(long*)obj;

Suddenly, the data held by the object is now treated as the GC header, which is problematic for obvious reasons.
Object is not a true top type in C# for this exact reason. Instead, there are two top-like types, Object and void*, and there’s no way to convert between the two.
> **Comments:**
> **username_7** (2023-07-13): I think you missed the question by one level of indirection ;)  
> **username_8** (2023-10-06): By my understanding, the CompCert dialect of C, which is designed to allow optimizations to be proven sound, allows data to be accessed via lvalues of any non-pointer data types interchangeably, or by lvalues of single indirect pointer-to-data types interchangeably, or double indirect pointer-to-data-types, etc. but does not storage to be accessed via lvalues representing different levels of indirection, for reasons like those alluded to in this answer.  
> **username_9** (2023-10-14): I think mixing pointer arithmetic and a never-conservative GC is what's the bad idea. Some other error cases: pointers to array members, pointers to struct/class fields, and array slices, do not have access to the GC headers either. Also "the data held by the object is treated as the GC header" requires the GC to follow scalars which most do not do.  
---
### Answer by username_7 on 2023-07-13. Score: 3
While casting the pointers to these types is often allowed, casting the values usually isn't. I guess the reason is that the cast has unknown arbitrary cost and requires code. My research project currently has such a cast operation and I eventually resorted to creating a hidden variable, moving the data to it, casting it to the target pointer type and loading the value back. Simply, because it was too cumbersome getting all the pairs right that one would need to consider. I mean, if you think about physical representation, it could be that casting `long` to `float[2]` results in moving one value from an integer register somewhere to two float registers in a coprocessor.

Also, there is very little things that you really want to do this way. The code you'd write with this cast is essentially loading all the data into registers just to decide that those were tho wrong registers and that you need to move them to another set of registers. Why not using the correct type in the first place?
> **Comments:**
> **username_10** (2023-07-13): Why would `reinterpret_cast` require any code? It doesn't make any change to the value, it just uses the existing bit pattern.  
> **username_11** (2023-07-13): @Barmar The bit pattern that is existing _where_? Part of the point of the answer is that the _where_ matters.  
> **username_10** (2023-07-13): The variable that you're reinterpreting. It shouldn't matter where it is.  
> **username_7** (2023-07-13): @Barmar But processors need to hold the data somewhere, right?  
> **username_12** (2023-07-13): @Barmar what if I want to add it to a larger 64-bit int, and it's sitting in xmm0?  
> **username_10** (2023-07-14): Good point. I was only thinking of variables in memory. @notmyrealname  
---
