2

Consider the following code:

#include <cstdint>
#include <iostream>
#include <iomanip>

int main()
{
    auto x=std::uint32_t(1)<<31;
    std::cout << " x: 0x" << std::hex << x << " =  " << std::dec << x << "\n";
    int32_t sx=x;
    std::cout << "sx: 0x" << std::hex << sx << " = " << std::dec << sx << "\n";
}

I get the following output from it:

 x: 0x80000000 =  2147483648
sx: 0x80000000 = -2147483648

Here the value of x can't be represented in int32_t, and the C++11 Standard says the following about this conversion:

If the destination type is signed, the value is unchanged if it can be represented in the destination type (and bit-field width); otherwise, the value is implementation-defined.

Is this still implementation-defined even with intXX_t, for which we have certain guarantees on representation? If yes, then how can I guarantee that the result will be as shown above? Should I memcpy my unsigned value to signed to get two's complement interpretation, or is there a more straightforward way?

Ruslan
  • 18,162
  • 8
  • 67
  • 136
  • `memcpy` from one type to another would give undefined behaviour, so that's not an ideal workaround. – Alan Stokes Sep 27 '15 at 10:34
  • @AlanStokes according to [this](http://stackoverflow.com/questions/24405129/) it seems to not give UB. – Ruslan Sep 27 '15 at 10:50
  • One way to "solve" this is to limit portability to systems where *implementation-defined* means that it works as expected. On systems where the conversion traps you are likely to have much worse problems than this. – Bo Persson Sep 27 '15 at 10:55
  • That link says `memcpy` obeys the aliasing rules, so it's ok if you know the layout of the types. But you don't in general know the layout of `int32_t`, although it is guaranteed to use 2s complement. – Alan Stokes Sep 27 '15 at 10:56

3 Answers3

4

int32_t sx=x; is implementation-defined.

The rule, which you quoted, is that converting a value to a type for which that value is out-of-range causes implementation-defined behaviour.

The range for int32_t goes up to INT32_MAX which is 2147483647, but (uint32_t)1 << 31 is one more than that.

It's nothing to do with representations, it's all about whether the value can remain unchanged or not.

Using memcpy would generate the int32_t which has the same representation as the other value you copy from (I think this is well-defined since int32_t cannot have padding or traps)

M.M
  • 138,810
  • 21
  • 208
  • 365
  • 2
    Using `memcpy` should work, but it needs a stronger guarantee than what you put in your answer. It matters that the C standard specifies that `int32_t` and `uint32_t` are *corresponding* signed and unsigned integer types, which basically means their representations have to match, with the unsigned version's highest value bit mapping to the signed version's sign bit. Without that requirement, a malicious implementation could make one of them big-endian, and the other one little-endian. –  Sep 27 '15 at 10:57
  • @hvd Is there a similar guarantee in C++? – Alan Stokes Sep 27 '15 at 10:57
  • 1
    @AlanStokes That guarantee applies to C++ too, because C++ specifies that `int32_t` and `uint32_t` (if present) mean the same thing they do in C. –  Sep 27 '15 at 10:58
  • @hvd Yes, and the strict aliasing rule specifically allows this case – M.M Sep 27 '15 at 11:07
  • @M.M Ah, right! So that *should* mean `memcpy` isn't needed, and `int32_t sx=(int32_t&)x;` should work too. –  Sep 27 '15 at 11:14
  • @user743382 unfortunately(?) reference type-punning is strict-aliasing UB, just like `*(int32_t*)&x`. You need memcpy in ISO C++, or your own copying via `unsigned char*` which is allowed to alias anything. (Or in some C++ implementations, union type punning is well-defined as an extension. And in some (like MSVC), pointer-casting *is* well-defined, like gcc/clang `-fno-strict-aliasing`) – Peter Cordes Mar 30 '20 at 10:58
1

Yes, it is implementation defined.

There are values that an unsigned 32 bit integral type (such as uint32_t) can represent that a signed 32 bit integral type (int32_t) cannot be guaranteed able to represent. One of those values is being produced in your code. Hence the clause you quoted from C++11 is applicable.

Peter
  • 35,646
  • 4
  • 32
  • 74
0

I think it is implementation defined because 2's compliment is not guaranteed. (Imagine a separate sign bit).

Thus no safe mechanism would exist.

mksteve
  • 12,614
  • 3
  • 28
  • 50
  • 2
    `int32_t ` is guaranteed to be exactly 32 bits and 2's complement. Otherwise the typedef will not be present. – Bo Persson Sep 27 '15 at 10:48
  • @Bo Persson, where is representation guaranteed to be two's compliment. I am not aware of any strange implementations, but I think they would be compliant – mksteve Sep 27 '15 at 12:52