3

I've been reading about strict aliasing quite a lot lately. The C/C++ standards say that the following code is invalid (undefined behavior to be correct), since the compiler might have the value of a cached somewhere and would not recognize that it needs to update the value when I update b;

float *a;
...
int *b = reinterpret_cast<int*>(a);
*b = 1;

The standard also says that char* can alias anything, so (correct me if I'm wrong) compiler would reload all cached values whenever a write access to a char* variable is made. Thus the following code would be correct:

float *a;
...
char *b = reinterpret_cast<char*>(a);
*b = 1;

But what about the cases when pointers are not involved at all? For example, I have the following code, and GCC throws warnings about strict aliasing at me.

float a = 2.4;
int32_t b = reinterpret_cast<int&>(a);

What I want to do is just to copy raw value of a, so strict aliasing shouldn't apply. Is there a possible problem here, or just GCC is overly cautious about that?

EDIT

I know there's a solution using memcpy, but it results in code that is much less readable, so I would like not to use that solution.

EDIT2

int32_t b = *reinterpret_cast<int*>(&a); also does not work.

SOLVED

This seems to be a bug in GCC.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
  • The compiler won't necessarily reload all variables when you write though a char pointer, but it will be able to work out which variable has changed, and reload that one. – James Feb 20 '11 at 20:25
  • Are you sure that last example gives a warning? Compiling with GCC 4.1.2 (`-Wall -ansi -pedantic`), I don't get any complaints. – Oliver Charlesworth Feb 20 '11 at 20:26
  • 1
    You won't get *any* working code out of the first two examples, because you implicitly assigned an int* to an int and a char* to a char. – Puppy Feb 20 '11 at 20:30
  • @DeadMG: oops, my fault. –  Feb 20 '11 at 20:33
  • @Oli Charlesworth: you need to enable strict aliasing firstly either with `-fstrict-aliasing` command line switch or using `-O2` –  Feb 20 '11 at 20:34

3 Answers3

3

If you want to copy some memory, you could just tell the compiler to do that:

Edit: added a function for more readable code:

#include <iostream>
using std::cout; using std::endl;
#include <string.h>

template <class T, class U>
T memcpy(const U& source)
{
    T temp;
    memcpy(&temp, &source, sizeof(temp));
    return temp;
}

int main()
{
    float f = 4.2;
    cout << "f: " << f << endl;

    int i = memcpy<int>(f);
    cout << "i: " << i << endl;
}

[Code] [Updated Code]

Edit: As user/GMan correctly pointed out in the comments, a full-featured implementation could check that T and U are PODs. However, given that the name of the function is still memcpy, it might be OK to rely on your developers treating it as having the same constraints as the original memcpy. That's up to your organization. Also, use the size of the destination, not the source. (Thanks, Oli.)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Bill
  • 14,257
  • 4
  • 43
  • 55
  • I know there's a solution using memcpy, but it results in code that is much less readable, so I would like not to use it. Anyway, thanks! –  Feb 20 '11 at 20:31
  • If you just want to "copy some raw memory", isn't memcpy the obvious way of doing this? That is one criterion for "readable". You are not doing this very often, are you? :-) – Bo Persson Feb 20 '11 at 21:02
  • In my case there are maybe few hundreds of small inline wrapper functions that interface with even lower level API, and yes, unfortunately currently at least half of them break strict aliasing. So a readable solution will pay back. –  Feb 20 '11 at 21:06
  • +1 for a nice template. Although it's probably safer to use `sizeof(dest)` as the `memcpy` param. – Oliver Charlesworth Feb 20 '11 at 21:15
  • 1
    Not sure about the use of the template there... to me that implies that you can pass any type of C++ class in and everything will be hunkydory even though what happens is only defined with POD objects. – user470379 Feb 20 '11 at 21:19
  • Like @user says, this code should `static_assert` that `T` and `U` are POD types, and that they are the same size. I personally have this function named `bit_cast`. – GManNickG Feb 20 '11 at 21:48
  • @Oli: Good point! I updated the code. Ultimately, it should check whether T is at least as large as U, but I didn't flesh it out as much as I could have. – Bill Feb 21 '11 at 16:00
1

Basically the strict aliasing rules is "it is undefined to access memory with another type than its declared one, excepted as array of characters". So, gcc isn't overcautious.

AProgrammer
  • 51,233
  • 8
  • 91
  • 143
  • But as far as I understand my case does not break strict aliasing, since `&b` and `a` do not refer to the same memory locations. –  Feb 20 '11 at 20:39
  • b is initialized with the content of a interpreted as an int while it is a float, breaking the strict aliasing rule. You should get the same error if you feed the reinterpret_cast result to something needing an int32_t value. – AProgrammer Feb 20 '11 at 20:42
  • Yes, I agree, that particular memory access does break strict aliasing. But is there anything that could go wrong if we take the context into the account? Can compiler defer any writes to `a` under strict aliasing? –  Feb 20 '11 at 20:48
  • @jons34yp, My understanding of the rule is: yes. – AProgrammer Feb 20 '11 at 21:00
  • The compiler can (and obviously does) optimize under the assumption that strict aliasing isn't broken. If you write to the variable, but do not (visibly) read from it, the compiler could possibly remove it altogether. At least it seems like the compiler is a bit confused about the code. That is never a good thing! – Bo Persson Feb 20 '11 at 21:07
0

If this is something you need to do often, you can also just use a union, which IMHO is more readable than casting or memcpy for this specific purpose:

union floatIntUnion {
  float a;
  int32_t b;
};

int main() {
  floatIntUnion fiu;
  fiu.a = 2.4;
  int32_t &x = fiu.b;
  cout << x << endl;
}

I realize that this doesn't really answer your question about strict-aliasing, but I think this method makes the code look cleaner and shows your intent better.

And also realize that even doing the copies correctly, there is no guarantee that the int you get out will correspond to the same float on other platforms, so count any network/file I/O of these floats/ints out if you plan to create a cross-platform project.

user470379
  • 4,879
  • 16
  • 21
  • 3
    I believe that this construction (accessing both fields of the union) is, according to strict C++ standard, undefined behavior ; and probably also breaks strict aliasing. See http://stackoverflow.com/questions/4328342/float-bits-and-strict-aliasing – rotoglup Feb 20 '11 at 21:29
  • I get no messages using `-fstrict-aliasing -Wall -pedantic`, but you are right that it is technically UB. That being said, you'd be hard-pressed to find a compiler that behaved differently. – user470379 Feb 20 '11 at 21:37
  • "_I get no messages_" because GCC decided to explicitly define this special case (but it is not clear which set of programs are GCC-defined exactly). – curiousguy Jul 21 '12 at 04:17