Aliassing and reinterpret_cast and optimization

Discussion in 'C++' started by ciccio, Apr 15, 2008.

  1. ciccio

    ciccio Guest

    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;
    }


    --
    "You're very sure of your facts, " he said at last, "I
    couldn't trust the thinking of a man who takes the Universe
    - if there is one - for granted. "
     
    ciccio, Apr 15, 2008
    #1
    1. Advertising

  2. ciccio

    Guest

    On 15 Apr., 15:14, ciccio <> wrote:
    > 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
     
    , Apr 15, 2008
    #2
    1. Advertising

  3. ciccio

    gpderetta Guest

    On Apr 15, 3:14 pm, ciccio <> wrote:
    > 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).

    --
    gpd
     
    gpderetta, Apr 15, 2008
    #3
  4. ciccio

    James Kanze Guest

    On 15 avr, 17:47, gpderetta <> wrote:
    > On Apr 15, 3:14 pm, ciccio <> wrote:
    > > 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.


    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

    > > (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).
    >
    > --
    > gpd
     
    James Kanze, Apr 15, 2008
    #4
  5. ciccio

    James Kanze Guest

    On 15 avr, 17:47, gpderetta <> wrote:
    > On Apr 15, 3:14 pm, ciccio <> wrote:


    > > 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.


    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.

    --
    James Kanze (GABI Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
     
    James Kanze, Apr 15, 2008
    #5
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Christopher Benson-Manica

    reinterpret_cast and enumerations

    Christopher Benson-Manica, Nov 2, 2004, in forum: C++
    Replies:
    5
    Views:
    831
    Ron Natalie
    Nov 3, 2004
  2. Kobe
    Replies:
    3
    Views:
    587
    Tomás
    Feb 15, 2006
  3. Alex Vinokur
    Replies:
    4
    Views:
    558
    Jakob Bieling
    Mar 27, 2006
  4. Ravikiran

    Zero Optimization and Sign Optimization???

    Ravikiran, Nov 17, 2008, in forum: C Programming
    Replies:
    22
    Views:
    877
    Thad Smith
    Nov 24, 2008
  5. Alex Vinokur
    Replies:
    1
    Views:
    582
Loading...

Share This Page