Aliassing and reinterpret_cast and optimization

C

ciccio

Hi,

I was wondering what the main reason is why reinterpret_cast fails to work
as expected when using optimizations. Here is the simple example code which
fail to give the correct result when optimizing.

#include <iostream>
int main() {
long D1 = 0xbcfbc4f0d9b65179;
long D2 = 0xbcfbc4f0d9b65042;

std::cout.setf(std::ios::showbase | std::ios::showpos);
std::cout.setf(std::ios::hex,std::ios::basefield);
std::cout.setf(std::ios::scientific,std::ios::floatfield);
std::cout.setf(std::ios::internal,std::ios::adjustfield);
std::cout.precision(20);

double E1 = *reinterpret_cast<double *> (&D1);
double E2 = *reinterpret_cast<double *> (&D2);

std::cout << E1 << "\t" << D1 << std::endl;
std::cout << E2 << "\t" << D2 << std::endl;

return 0;
}

This gives me the output when compiled without optimization flags :
$ g++ foo.cpp
$ ./a.out
-6.16602326664765606567e-15 0xbcfbc4f0d9b65179
-6.16602326664741072993e-15 0xbcfbc4f0d9b65042

while compiled with optimization (-O3)
$ g++ -O3 foo.cpp
$ ./a.out
+0.00000000000000000000e+00 0xbcfbc4f0d9b65179
-6.16602326664741072993e-15 0xbcfbc4f0d9b65042

Although this is only a minor difference here, it becomes ludricous when
doing some simple math operations. (Example code below). Identical results
are obtained when using pointer casts directly, i.e. *(double *) &D1 for
example.

I have read some stuff about aliassing (i.e.
<http://mail-index.netbsd.org/tech-kern/2003/08/11/0001.html>) And I was
wondering if this is the same problem here, eventhough the standard of C++
does not say anything about this (or I just don't find it, sec 5.2.10). If
this is the same problem here, is there a way to solve it without using
the gnu extension?

Thanks

Ugly piece of code which goes completely bizerque when optimizing.

#include <iostream>
#include <cmath>
#include <float.h>

int main(void) {

int A = 0x40000000;
float B = *reinterpret_cast<float *>(&A);

std::cout.setf(std::ios::showbase | std::ios::showpos);
std::cout.setf(std::ios::hex,std::ios::basefield);
std::cout.setf(std::ios::scientific,std::ios::floatfield);
std::cout.setf(std::ios::internal,std::ios::adjustfield);
std::cout.precision(20);

for (int i = -3; i <= 3; ++i) {
int C = A+i;
B = *reinterpret_cast<float *>(&C);
std::cout << B << "\t" << A+i << std::endl;
}

int C = -A;
B = *reinterpret_cast<float *>(&C);
std::cout << B << "\t" << C << std::endl;
C = A*2;
B = *reinterpret_cast<float *>(&C);
std::cout << B << "\t" << C << std::endl;
B = 40;
C = *reinterpret_cast<int *>(&B);
std::cout << B << "\t" << C << std::endl;
B = 1;
C = *reinterpret_cast<int *>(&B);
std::cout << B << "\t" << C << std::endl;
B = 0.1;
C = *reinterpret_cast<int *>(&B);
std::cout << B << "\t" << C << std::endl;


long D1 = 0xbcfbc4f0d9b65179;
long D2 = 0xbcfbc4f0d9b65042;
std::cout.setf(std::ios::showbase | std::ios::showpos);
std::cout.setf(std::ios::hex,std::ios::basefield);
std::cout.setf(std::ios::scientific,std::ios::floatfield);
std::cout.setf(std::ios::internal,std::ios::adjustfield);
std::cout.precision(20);

double E1 = *reinterpret_cast<double *> (&D1);
double E2 = *reinterpret_cast<double *> (&D2);
double E3 = fabs(E1-E2);
long D3 = *reinterpret_cast<long *>(&E3);

std::cout << E1 << "\t" << D1 << std::endl;
std::cout << E2 << "\t" << D2 << std::endl;
std::cout << E3 << "\t" << D3 << "\t" << D1-D2 << std::endl;
std::cout << E3/E2 << "\t" << E3/E2/DBL_EPSILON << std::endl;

unsigned long F = D3;
F = F << 1;
F = F >> 53;
std:: cout << F << std::endl;

long GF = 0x42270feeda10f98e;
long G = GF + 512;
long HF = 0x413cd26c40d51215;
double H = (*reinterpret_cast<double *>(&G)) - (*reinterpret_cast<double
*>(&GF));
double I = H/(*reinterpret_cast<double *> (&HF));

std::cout << GF << " " << G << " " << H << " " << I << " " <<
I/DBL_EPSILON << std::endl;

return 0;
}
 
M

Michael.Boehnisch

I was wondering what the main reason is why reinterpret_cast fails to work
as expected when using optimizations. Here is the simple example code which
fail to give the correct result when optimizing.

I think you slip out on the word "correct". The reinterpret_cast is
not failing to work, there is no "correct" result in your case - your
code has undefined behavior.
By casting a bit pattern to double you are assuming a fixed internal
representation of floating point numbers. Most implementations use
IEEE-754 format. However, IEEE-754 is just a general pattern with
space for variations on mantissa and exponent size.
You should check, for example, is sizeof( double ) <= sizeof( long )?
Is it the same with and without -O3? If the double uses more space
than the long, you receive semi-random garbage behind the long as part
of the casted number.
Maybe the compiler uses a less precise but faster floating point
library when optimizing?

Another example, if you restrict yourself casting integer numbers to
byte sequences you will receive different results depending on the
byte order rules of the machine.

int x = 0x41424300;
char *s = reinterpret_cast<char*>( &x )
std::cout << s << std::endl;

may output ABC, BA, or C or nothing.

just my $.02,

Michael
 
G

gpderetta

Hi,

I was wondering what the main reason is why reinterpret_cast fails to work
as expected when using optimizations. Here is the simple example code which
fail to give the correct result when optimizing.

#include <iostream>
int main() {
long D1 = 0xbcfbc4f0d9b65179;
long D2 = 0xbcfbc4f0d9b65042;

std::cout.setf(std::ios::showbase | std::ios::showpos);
std::cout.setf(std::ios::hex,std::ios::basefield);
std::cout.setf(std::ios::scientific,std::ios::floatfield);
std::cout.setf(std::ios::internal,std::ios::adjustfield);
std::cout.precision(20);

double E1 = *reinterpret_cast<double *> (&D1);
double E2 = *reinterpret_cast<double *> (&D2);

std::cout << E1 << "\t" << D1 << std::endl;
std::cout << E2 << "\t" << D2 << std::endl;

return 0;

}

This gives me the output when compiled without optimization flags :
$ g++ foo.cpp
$ ./a.out
-6.16602326664765606567e-15 0xbcfbc4f0d9b65179
-6.16602326664741072993e-15 0xbcfbc4f0d9b65042

while compiled with optimization (-O3)
$ g++ -O3 foo.cpp
$ ./a.out
+0.00000000000000000000e+00 0xbcfbc4f0d9b65179
-6.16602326664741072993e-15 0xbcfbc4f0d9b65042

Although this is only a minor difference here, it becomes ludricous when
doing some simple math operations. (Example code below). Identical results
are obtained when using pointer casts directly, i.e. *(double *) &D1 for
example.

I have read some stuff about aliassing (i.e.
<http://mail-index.netbsd.org/tech-kern/2003/08/11/0001.html>) And I was
wondering if this is the same problem here, eventhough the standard of C++
does not say anything about this (or I just don't find it, sec 5.2.10). If
this is the same problem here, is there a way to solve it without using
the gnu extension?

Thanks

Ugly piece of code which goes completely bizerque when optimizing.

#include <iostream>
#include <cmath>
#include <float.h>

int main(void) {

int A = 0x40000000;
float B = *reinterpret_cast<float *>(&A);

Using reinterpret cast to read from a variable with a different type
than the one used to store into it is undefined behaviour (check on
the standard the exact . Some compilers support type punning via an
union as an extension. The only 'portable' way to do that is to use
memcpy: memcpy(&B, &A, sizeof(float). (and making some assumptions
about int and float sizes and representations, of course).
 
J

James Kanze

I'd consider that an error in the compiler (from a quality of
implementation point of view), in this limited case, since the
compiler can easily see the cast, and should turn off all
optimization involving pointers when it does.

Still, you're creating an alias. The standard is rather clear
that accessing an object through an lvalue whose type is neither
the type of the actual object, nor a character type, is
undefined behavior. The motivation for this is, of course, that
aliasing is the bane of
 
J

James Kanze

From a quality of implementation point of view, I'd consider
that a bug, in this particular case. The compiler can see the
reinterpret_cast, and should turn off all pointer related
optimizations because of it.

The standard, however, is very clear that accessing an object
via an lvalue which is neither the type of the object, nor a
character type, is undefined behavior. And I wouldn't complain
if the compiler had problems when the reinterpret_cast was not
immediately visible, say because it was in another function.

[...]
Using reinterpret cast to read from a variable with a
different type than the one used to store into it is undefined
behaviour (check on the standard the exact .

Not always. There are two issues which need to be addressed:

-- First, the standard says that the result of the conversion
(using reinterpret_cast) is "unspecified. All that is
required is that if he casts it back to the original type,
he get the same value (because in this case, the alignment
requirements are the same). Other language in the standard
suggests, however, that reinterpret_cast should behave in a
manor unsurprising to someone familiar with the addressing
architecture of the machine---in other words, his
expectations aren't unrealistic.

In practice, I wouldn't expect this to work unless
everything was taking place in the same function. There's
too much to be gained in optimization by assuming that two
pointers to different types aren't aliases.

-- The second is the fact that accessing an object other than
through an lvalue with its type, or as an array of character
type, is undefined behavior. The motivation behind this, of
course, is that anything but character types can contain
trapping values---if the bit pattern in his long, for
example, corresponded to a trapping NaN, I wouldn't expect
such code to work.

From a quality of implementation point of view, again, I
would expect it to work provided the bit patterns were in
order. Modulo the business about undetectable aliasing, of
course.
Some compilers support type punning via an union as an
extension.

Historically, I believe that this was the preferred way; at
least, it was what I was taught in C, in the early 1980's. The
C standard (and the C++ standard, following that) makes it quite
clear, however, that this is NOT an allowable practice.
The only 'portable' way to do that is to use memcpy:
memcpy(&B, &A, sizeof(float). (and making some assumptions
about int and float sizes and representations, of course).

Yes. The memcpy works, of course, because 1) you're going
through a void*, which means that all of the casts are
static_casts (although arguably, using reinterpret_cast should
give the same results), and 2) because memcpy copies the
underlying bytes, i.e. it uses an unsigned char* to do the
copying.

Practically, from a quality of implementation point of view, I'd
say that if the reinterpret_cast doesn't work when all of the
operations (including the reinterpret_cast) are visible in the
function, then it's a compiler error. But in practice, the need
for such type punning, except in low level code to access the
underlying raw memory, is so rare that there's no point worrying
about it.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top