4

I am trying to set the bits in a double (IEEE Standard 754). Saying I want to 'build' a 3, I would set the 51-th and the 62-nd bit of the double floating point representation, so that I get in binary 1.1 * 2 that in decimal is 3. I wrote this simple main:

int main() {
  double t;
  uint64_t *i = reinterpret_cast<uint64_t*>(&t);
  uint64_t one = 1;
  *i = ((one << 51) | (one << 62));
  std::cout << sizeof(uint64_t) << " " <<  sizeof(uint64_t*) << " "
            << sizeof(double) << " " <<  sizeof(double*) << std::endl;
  std::cout << t << std::endl;
  return 0;
}

The output of this would be

8 8 8 8
3

when compiling with g++4.3 and no optimization. However, I get a strange behavior if I add the -O2 or -O3 optimization flags. That is, if I just leave the main as it is, I get the same output. But if I delete the line that outputs the 4 sizeof, than I get the output

0

The unoptimized version without the output of the sizeof returns 3 as well, correctly.

So I am wondering whether this is a bug of the optimizer, or if I am doing something wrong here.

stefano
  • 579
  • 5
  • 12
  • 3
    This violates strict aliasing, and thus its behavior is undefined. –  Jun 22 '11 at 12:42
  • `sizeof(pointer)` will be the same for any pointer. – 0xbadf00d Jun 22 '11 at 12:45
  • @Fanael - The `socket library` frequently use `type punning` ... – 0xbadf00d Jun 22 '11 at 12:52
  • 1
    @stefano - You could disable strict aliasing or use `char*` instead. – 0xbadf00d Jun 22 '11 at 12:56
  • @FrEEzE2046 I've used machines where pointers to different types had different sizes. – James Kanze Jun 22 '11 at 13:02
  • @FrEEzE2046: sizeof(pointer) doesn't have to be the same for any pointer (see http://c-faq.com/null/machexamp.html ), and the fact that socket library uses type punning doesn't mean that OP's code is well-defined. –  Jun 22 '11 at 13:03
  • @FrEEzE2046: I know this is overly pedantic, but Try: `std::cout << sizeof(double*) << ":" << sizeof(void (std::istream::*)(std::size_t)) << "\n";` – Martin York Jun 22 '11 at 13:23
  • @Martin - I'm sorry for being unclear. My comment wasn't about function pointers. – 0xbadf00d Jun 22 '11 at 13:29

3 Answers3

3

Yes, you are violating the alias rules of the language. Writing to an object of one type through a pointer to another type is not allowed (with some exceptions for char*).

As you never write to any doubles in the code, the compiler is allowed to assume that t is never assigned a value. (And outputting that is wrong in itself :-)

GCC has an extension that allows you to write a value of one type and read it as another type, if you put them both in a union. That's compiler specific though (but semi-portable as others have to follow the lead).

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
  • I don`t like that exception for `char*`. I`ve seen many implementations that rely on `sizeof(char) == 1`, while the standard says (3.9.1): "Objects declared as characters (char) shall be large enough to store any member of the implementation’s basic character set." – 0xbadf00d Jun 22 '11 at 13:01
  • @FrEEzE2046: By definition: `sizeof(char) == 1`. What you are worried about `CHAR_BITS` which may not by 8. – Martin York Jun 22 '11 at 13:04
  • sizeof(char) is guaranteed to be one. – David Hammen Jun 22 '11 at 13:05
  • @FrEEzE2046: The Standard defines `sizeof(1)==1 byte`, and it defines a byte to be at least 8 bits wide (yes, indeed a C++ byte can be bigger than 8 bits, which would be needed for a conforming C++ compiler for [one of these](http://en.wikipedia.org/wiki/36-bit) – Sebastian Mach Jun 22 '11 at 13:08
  • @FrEEzE2046: See http://stackoverflow.com/questions/271076/what-is-the-difference-between-an-int-and-a-long-in-c/271132#271132 – Martin York Jun 22 '11 at 13:08
  • @Martin - I always remembered the line I quoted. But, you`re right (5.3.3): "sizeof(char), sizeof(signed char) and sizeof(unsigned char) are 1.". – 0xbadf00d Jun 22 '11 at 13:25
2

Technically, you have undefined behavior, although it's clearly the intent of the standard that this work in obvious cases, and it's perverse of the compiler to break it if it can see the reinterpret_cast. If you know the endianness of your platform, you can forse the issue by using a uint8_t (a character type) to manipulate the bits, or memcpy into a uint64_t, then memcpy the results back into the double.

g++ will make this work if you use a union. On the condition that all accesses go through the union type. The standard explicitly bans this, however (although it was the preferred solution in pre-standard days), and I've used compilers where it wouldn't work.

With g++, there's also an option, -fnostrict-aliasing, which will make it work.

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • Some people (e.g. http://lkml.org/lkml/2003/2/26/158) think that `-fnostrict-aliasing` is the only way to use gcc. – David Hammen Jun 22 '11 at 13:06
  • @David Hammen They're partially right. Globally, g++ is doing the right thing with aliasing, and it's really no problem to add the `-fno-string-aliasing` in the few modules that need it (although they should turn it on automatically for any function which has a `reinterpret_cast` in it. On the other hand, they speak of `memcpy`, which involves `void*`; if g++ doesn't consider that `void*` might alias anything, and that that aliasing is transitive (in other words, if a `void*` is present, everything can alias), then it is broken. – James Kanze Jun 22 '11 at 14:45
1

Try:

int main()
{
      volatile double    t;
  //  ^^^^^^^^ Tells the compiler this may be modified unexpectedly.

      volatile uint64_t& i   = reinterpret_cast<volatile uint64_t&>(t);
      uint64_t           one = 1;

      i = ((one << 51) | (one << 62));
      std::cout << t << std::endl;
}
Martin York
  • 257,169
  • 86
  • 333
  • 562
  • nice try :) but the compiler ignored the volatile suggestion. – stefano Jun 22 '11 at 13:13
  • @ stefano: Technically it is not allowed to ignore the volatile (its **not** a suggestion). I compile dwith `g++ -O3 test.cpp` and it worked fine (output 3). – Martin York Jun 22 '11 at 13:15
  • mmhhh, then I do not know why my binary (compiled with g++-4.3 -O3) is giving me 0 as a result instead of 3. While instead with -O1 or without optimization flag it is printing 3. – stefano Jun 22 '11 at 13:34
  • @stefano: Try now.maybe it was optimizing the usage of `i` not `t` so lets make them both volatile. – Martin York Jun 22 '11 at 14:43