Re: Stanard compliant bit-casting
On Mar 12, 1:09 am, Joshua Maurice <joshuamaur...@gmail.com> wrote:
On Mar 11, 4:29 pm, James Kanze <james.ka...@gmail.com> wrote:
On Mar 11, 1:49 am, Joshua Maurice <joshuamaur...@gmail.com> wrote:
reinterpret_cast was not added to the language to support type
punning.
Why was it added to the language, then?
See my previous post else-thread for my understanding.
I didn't see any real explination, other than to provide a new
style cast for certain C style casts.
Finally we have type punning through unions. While
explicitly not supported by the standard(s), it's
supported as a compiler extension by basically every C and
C++ compiler. (Confirmation anyone?)
The only compiler I've seen that documents it as being
supported is g++ (but I've not really looked at all of the
documentation). And even with g++, it depends on the
context---there are cases where it will fail.
From a QoI point of view: if the union or the
reinterpret_cast is visible, I would expect the code to give
the expected results. Any accesses elsewhere, and all bets
are off, e.g.:
int f(int* pi, double* pd)
{
int retval = *pi;
*pd = 3.14159;
return retval;
}
int
main()
{
union U { int i; double d; } x;
x.i = 42;
std::cout << f(&x.i, &x.d) << std::endl;
return 0;
}
I would not count on this code outputting 42, regardless of
any guarantees the compiler might give.
Note that my statement above is based on how compiler optimizers
work. The motivation behind the anti-aliasing rule (e.g. that
two pointers to different types cannot refer to the same object)
is to allow certain optimizations. Optimizations which are
important in some code. But this only works if the above is not
guaranteed to work.
As the C++ standard is currently written, the above *is*
guaranteed to work. I don't think that this was the intent,
however. C has (or had) similar rules. I know that the issue
was discussed in the C committee, but I don't know the exact
status of the proposed resolution.
//Start code for foo.cpp
#include <iostream>
using namespace std;
int main()
{
cout << sizeof(int) << " " << sizeof(short) << endl;
{
int x = 1;
short* s = reinterpret_cast<short*>(&x);
s[0] = 2;
s[1] = 3;
cout << x << endl;
}
{
int x = 1;
union { int u_int; short u_short_array[2]; };
u_int = x;
u_short_array[0] = 2;
u_short_array[1] = 3;
x = u_int;
cout << x << endl;
}
}
//End code
//Start prompt copy
bash-3.2$ g++ --version
g++ (GCC) 4.1.2 20080704 (Red Hat 4.1.2-44)
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There
is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE.
bash-3.2$ g++ -O3 foo.cpp -Wall
foo.cpp: In function =E2int main()=E2:
foo.cpp:9: warning: dereferencing type-punned pointer will break
strict-aliasing rules
bash-3.2$ ./a.out
4 2
1
196610
bash-3.2$
//end prompt copy
So, when the union is in scope, gcc "does the right thing",
and when reinterpret_cast is in scope, gcc does not "do the
right thing".
Which is, from a QoI point of view, an error.
In fact, I don't know whether this is an error in the coding,
a problem in the way that the optimizer works which would make
it difficult (and perhaps not worth it) to fix, or simply a bit
of stubborness on the part of g++ developers. It does mean that
reinterpret_cast is useless, which is certainly not the intent
of the committee.
The issue isn't simple. Historically (pre-ISO C), the union was
the preferred solution, at least from what I understood. For
whatever reasons, the ISO C committee (or at least the parts of
it which formallized the wording in this regard) designed to
make type punning via a union undefined behavior, which means
that only casting remains. The C++ committee simply followed
the C committee in this respect---I'm 100% sure that the intent
of the C++ committee is that a reinterpret_cast, when legal,
behave exactly the same as a cast in C.
In addition, there is a note (non-normative) in the C++ standard
(=A75.2.10/3) concerning the mapping done by reinterpret_cast: "it
is intended to be unsurprising to those who know the addressing
structure of the underlying machine". Although this note is
directly attached to the pointer to integer conversion, in the
absense of any other indications, it seems reasonable to me to
apply it to the other uses of reinterpret_cast as well.
In any case, the current standard is very unclear with regards
to type punning---with the exception of character types. And I
don't think that this has changed in the more recent drafts---in
a very real sense, I think that it is more a C problem; that the
C++ committee should simply wait, and adopt whatever the C
committee finally decides.
Now, I don't know any other compilers offhand which optimize
with the strict aliasing allowance besides newer gcc versions,
I don't know of any that don't. It's a common optimization. It
was present in Microsoft C 1.0, for example (in which your union
example would break).
but I would suggest you revise your understanding of the QoI
implications of reinterpret_cast.
Why? My understanding of the QoI implications are based on the
actual words in the standard, and the various discussions that
I've followed in the standardization committees.
My previous argument succinctly: C-style casts were never
intended to get around strict aliasing.
Yes and no. An optimizer is expected to use the knowledge at
its disposal. If it can see that there is aliasing, whether
from a union or a cast, it should take it into account.
That is, by the way, the direction the C committee was going the
last time I looked.
reinterpret_cast was never intended to be more powerful than
C-style casts.
Certainly not. But since the C style cast should behave as
expected, when visible, so should the reinterpret_cast.
(The named casts were each intended to fulfill a specific role
of the several roles of C-style casts to remove potential
ambiguity to the code writer and readers.) Thus
reinterpret_cast was never intended to get around strict
aliasing.
You seem to be misunderstanding the motivation behind the strict
aliasing rule. It is to allow the compiler to assume no
aliasing in cases where it otherwise couldn't. There was never
any intent to allow the compiler to totally ignore aliasing that
it can clearly see.
Perhaps I am wrong about the original intent. However, I'm at
least right on the questions of fact, at least if we count gcc
as a good example.
I don't think you can count any single compiler as a
"reference".
Also, there are plenty of good reasons to do type punning.
For example, games. I'm pretty sure that using a portable
UTF8 text format for ints for their network packets would
result in rather unacceptable performance.
So you use any one of a number of binary formats. You still
don't need (nor want) type punning to implement them.
You would need to type pun somewhere as the OS network calls
probably only work in terms of char pointers.
No. You do need to convert value types, but that's all.
For integral types, there's really no need for any type punning
whatsoever. In the case of floating point, the issue is more
complex, since the code necessary to portably convert a string
of bytes of a given format into a floating point value is
relatively complex, and more expensive than type punning an
uint_64 to a double, in the case where you know that the
external format and the internal format are the same (e.g.
IEEE).
So either the game code is type punning, or the network
library on top of the OS is type punning, or the device driver
is type punning (or written in assembly). Someone is probably
type punning in C or C++.
I've written a lot of network code in which there was no type
punning. I have an implemenation of an xdrstream which does no
type punning, even for floating point. For integral types, it's
about as fast as implementations which do type pun (but it is
far more portable); for floating point, it's measurably slower
(but has the advantage that it works regardless of the machine
floating point format), but not nearly as much as I initially
expected.
--
James Kanze