compiler bug or misuse of a cast?

Discussion in 'C Programming' started by gvarndell, Dec 27, 2004.

  1. gvarndell

    gvarndell Guest

    Hi,

    In the following code fragment, gcc seems to ignore the initial value
    assigned to pData. (compiled -fvolatile -O2 with gcc)

    void test(void)
    {
    void *pData = (void*)0x3400;

    pData = (void*)(*((unsigned char**)&pData) + 4);
    memcpy(pData,(void*)0x1000,10);
    return;
    }

    The generated code simply reads whatever garbage is at [sp], adds 4 to
    that value, stores it back to [sp], and then calls memcpy.

    I've found that, if I declare pData as

    void * volatile pData = (void*)0x3400;

    then the generated code behaves as I would expect -- that is, the value
    actually passed to memcpy is 0x3404 because the initializer is not
    optimized away.

    I also found that, if I write this fragment this way..

    void test(void)
    {
    void *pData = (void*)0x3400;
    unsigned char *pChar;

    pchar = pData;
    pData = pChar + 4;
    memcpy(pData,(void*)0x1000,10);
    return;
    }

    then the generated code works as expected, passing 0x3404 to memcpy.

    I know that the casts create temporary unnamed values, but I wouldn't
    expect that to cause the complier to ignore the initializer.

    It seems as though gcc doesn't consider the read access (&pData) when
    deciding whether to optimize the initializer out of existence.

    Does this make sense? Is a compiler allowed to ignore the code sequence
    when read accesses are buried in a cast expression?

    TIA,

    GV
     
    gvarndell, Dec 27, 2004
    #1
    1. Advertising

  2. gvarndell

    Jack Klein Guest

    On 27 Dec 2004 05:28:33 -0800, "gvarndell" <>
    wrote in comp.lang.c:

    > Hi,
    >
    > In the following code fragment, gcc seems to ignore the initial value
    > assigned to pData. (compiled -fvolatile -O2 with gcc)


    You're going to have to take this up with the compiler maintainers,
    somewhere in the gnu group hierarchy.

    > void test(void)
    > {
    > void *pData = (void*)0x3400;


    Initializing or assigning an integer value to a pointer type, with a
    suitable cast, is legal but implementation-defined behavior.

    > pData = (void*)(*((unsigned char**)&pData) + 4);


    Now you are attempting to use the value in pData as a valid pointer,
    regardless of type. This is where the code crosses the line into
    undefined behavior. The C standard places no requirements on the
    result of this operation. Whether or not this code produces the
    results you want is not a language issue.

    > memcpy(pData,(void*)0x1000,10);
    > return;
    > }
    >
    > The generated code simply reads whatever garbage is at [sp], adds 4 to
    > that value, stores it back to [sp], and then calls memcpy.
    >
    > I've found that, if I declare pData as
    >
    > void * volatile pData = (void*)0x3400;
    >
    > then the generated code behaves as I would expect -- that is, the value
    > actually passed to memcpy is 0x3404 because the initializer is not
    > optimized away.
    >
    > I also found that, if I write this fragment this way..
    >
    > void test(void)
    > {
    > void *pData = (void*)0x3400;
    > unsigned char *pChar;
    >
    > pchar = pData;
    > pData = pChar + 4;
    > memcpy(pData,(void*)0x1000,10);
    > return;
    > }
    >
    > then the generated code works as expected, passing 0x3404 to memcpy.
    >
    > I know that the casts create temporary unnamed values, but I wouldn't
    > expect that to cause the complier to ignore the initializer.
    >
    > It seems as though gcc doesn't consider the read access (&pData) when
    > deciding whether to optimize the initializer out of existence.
    >
    > Does this make sense? Is a compiler allowed to ignore the code sequence
    > when read accesses are buried in a cast expression?


    Regardless of whether or not the compiler generates code that operates
    as the compiler coders intended, you won't get any support for your
    position here. Once your program produces undefined behavior, neither
    ISO C nor this group take a position one way or the other.

    Of course, your expression is unnecessarily convoluted.

    > pData = (void*)(*((unsigned char**)&pData) + 4);'


    The first thing I would do is eliminate the unnecessary cast to
    pointer to void. Then I'd eliminate & operator and the extra level of
    indirection. I would write this expression, if I had to write it at
    all, as:

    pData = (unsigned char *)pData + 4;

    ....and let it go at that.

    But you need to take this up with the GNU people.

    --
    Jack Klein
    Home: http://JK-Technology.Com
    FAQs for
    comp.lang.c http://www.eskimo.com/~scs/C-faq/top.html
    comp.lang.c++ http://www.parashift.com/c -faq-lite/
    alt.comp.lang.learn.c-c++
    http://www.contrib.andrew.cmu.edu/~ajo/docs/FAQ-acllc.html
     
    Jack Klein, Dec 27, 2004
    #2
    1. Advertising

  3. gvarndell

    Guest

    gvarndell <> wrote:
    >
    > In the following code fragment, gcc seems to ignore the initial value
    > assigned to pData. (compiled -fvolatile -O2 with gcc)


    That's because you're lying to the compiler and it's getting its
    revenge.

    > void test(void)
    > {
    > void *pData = (void*)0x3400;
    >
    > pData = (void*)(*((unsigned char**)&pData) + 4);


    Here's the lie -- you're telling the compiler to pretend that pData is
    an unsigned char * rather than a void *. Instead of pretending, you
    should just do a normal conversion:

    pData = (unsigned char *)pData + 4;

    Note that casting to void * is unnecessary (and, many would argue,
    undesirable, unless you're actually writing C++ rather than C, in which
    case it's required and your question is off-topic here).

    -Larry Jones

    I don't need to improve! Everyone ELSE does! -- Calvin
     
    , Dec 28, 2004
    #3
  4. gvarndell

    gvarndell Guest

    Thanks to you, and to Jack, for replying. The replies were helpful, but
    don't get to the crux of my question -- which is my fault for framing
    the question as a gcc question and supplying a convoluted example. So
    I'd like to try again.

    int test(void)
    {
    int a = 17;

    a += 17;
    return a;
    }

    If this function returned anything other than 34, the compiler would be
    clearly broken.

    int test(void)
    {
    int a = 17;

    *((int *)&a) += 17;
    return a;
    }

    Because of the fleeting nature of the values that result from casts,
    I'm not so sure that any conforming C compiler would be to be blame if
    this function returned 17 instead of the expected value of 34.

    Wouldn't it be okay for any compiler to generate code that did this?
    1. allocate storage for the integer variable 'a'
    2. read an integer value from the address of 'a'
    3. store whatever is read into a temporary
    4. add 17 to the temporary and store the result in a temporary
    5. store 17 into 'a' and return 'a'
    I hope this clarifies my real question.

    TIA
    GV
     
    gvarndell, Dec 28, 2004
    #4
  5. gvarndell

    infobahn Guest

    gvarndell wrote:
    > Thanks to you, and to Jack, for replying. The replies were helpful, but
    > don't get to the crux of my question -- which is my fault for framing
    > the question as a gcc question and supplying a convoluted example. So
    > I'd like to try again.
    >
    > int test(void)
    > {
    > int a = 17;
    >
    > a += 17;
    > return a;
    > }
    >
    > If this function returned anything other than 34, the compiler would be
    > clearly broken.
    >
    > int test(void)
    > {
    > int a = 17;
    >
    > *((int *)&a) += 17;


    &a has type int *, and points to an int object with the value 17.
    The cast is from int * to int *, so it's a nop. And the & and *
    cancel. So the expression resolves, effectively, to

    a += 17;


    > Because of the fleeting nature of the values that result from casts,
    > I'm not so sure that any conforming C compiler would be to be blame if
    > this function returned 17 instead of the expected value of 34.


    It would in fact be blameworthy.

    >
    > Wouldn't it be okay for any compiler to generate code that did this?
    > 1. allocate storage for the integer variable 'a'


    Yes.

    > 2. read an integer value from the address of 'a'


    Yes.

    > 3. store whatever is read into a temporary


    Yes.

    > 4. add 17 to the temporary and store the result in a temporary


    Yes.

    > 5. store 17 into 'a' and return 'a'


    No. The function must return the value 34.
     
    infobahn, Dec 28, 2004
    #5
  6. gvarndell

    Mole Wang Guest

    "gvarndell" <> wrote in message news:...
    > Hi,
    >
    > In the following code fragment, gcc seems to ignore the initial value
    > assigned to pData. (compiled -fvolatile -O2 with gcc)
    >
    > void test(void)
    > {
    > void *pData = (void*)0x3400;
    >
    > pData = (void*)(*((unsigned char**)&pData) + 4);
    > memcpy(pData,(void*)0x1000,10);
    > return;
    > }
    >
    > The generated code simply reads whatever garbage is at [sp], adds 4 to
    > that value, stores it back to [sp], and then calls memcpy.

    The garbage at [sp] is pData.
    pData is a local variant which is stored in the stack. And [sp] is the register
    pointed to the stack. The compiler may also use [bp]/[ebp] to access it,
    which is read-writeable.
    >
    > I've found that, if I declare pData as
    >
    > void * volatile pData = (void*)0x3400;
    >
    > then the generated code behaves as I would expect -- that is, the value
    > actually passed to memcpy is 0x3404 because the initializer is not
    > optimized away.
    >
    > I also found that, if I write this fragment this way..
    >
    > void test(void)
    > {
    > void *pData = (void*)0x3400;
    > unsigned char *pChar;
    >
    > pchar = pData;
    > pData = pChar + 4;
    > memcpy(pData,(void*)0x1000,10);
    > return;
    > }
    >
    > then the generated code works as expected, passing 0x3404 to memcpy.
    >
    > I know that the casts create temporary unnamed values, but I wouldn't
    > expect that to cause the complier to ignore the initializer.
    >
    > It seems as though gcc doesn't consider the read access (&pData) when
    > deciding whether to optimize the initializer out of existence.
    >
    > Does this make sense? Is a compiler allowed to ignore the code sequence
    > when read accesses are buried in a cast expression?
    >
    > TIA,
    >
    > GV
    >
     
    Mole Wang, Dec 29, 2004
    #6
  7. gvarndell

    Guest

    gvarndell <> wrote:
    >
    > int test(void)
    > {
    > int a = 17;
    >
    > *((int *)&a) += 17;
    > return a;
    > }
    >
    > Because of the fleeting nature of the values that result from casts,
    > I'm not so sure that any conforming C compiler would be to be blame if
    > this function returned 17 instead of the expected value of 34.


    I am -- that code is perfectly well defined and any compiler that
    doesn't return 34 is broken. Your original code contained type punning
    that is *not* well defined and thus compilers are free to do whatever
    they like with it.

    -Larry Jones

    Things are never quite as scary when you've got a best friend. -- Calvin
     
    , Dec 29, 2004
    #7
  8. gvarndell

    gvarndell Guest

    wrote:
    > gvarndell <> wrote:
    > >
    > > int test(void)
    > > {
    > > int a = 17;
    > >
    > > *((int *)&a) += 17;
    > > return a;
    > > }
    > >
    > > Because of the fleeting nature of the values that result from

    casts,
    > > I'm not so sure that any conforming C compiler would be to be blame

    if
    > > this function returned 17 instead of the expected value of 34.

    >
    > I am -- that code is perfectly well defined and any compiler that
    > doesn't return 34 is broken. Your original code contained type

    punning
    > that is *not* well defined and thus compilers are free to do whatever
    > they like with it.
    >
    > -Larry Jones


    Thank you once again. This makes sense to me.
    One final question on this, if I may.

    Since this code

    void test(void)
    {
    void *pData=(void*)0x3400;

    (*(unsigned char**)&pData) += 4;
    memcpy((unsigned char *)pData, (unsigned char *)0x1000,10);
    return;
    }

    apparently causes undefined behavior, is a compiler required to at
    least issue a warning about it?
    I tried -pedantic with gcc and it doesn't make a peep about the code.
    TIA,
    GV
     
    gvarndell, Dec 29, 2004
    #8
  9. gvarndell

    Flash Gordon Guest

    On 29 Dec 2004 09:29:28 -0800
    "gvarndell" <> wrote:

    <snip>

    > Thank you once again. This makes sense to me.
    > One final question on this, if I may.
    >
    > Since this code
    >
    > void test(void)
    > {
    > void *pData=(void*)0x3400;
    >
    > (*(unsigned char**)&pData) += 4;
    > memcpy((unsigned char *)pData, (unsigned char *)0x1000,10);
    > return;
    > }
    >
    > apparently causes undefined behavior, is a compiler required to at
    > least issue a warning about it?
    > I tried -pedantic with gcc and it doesn't make a peep about the code.


    No. A compiler is not required to issue a warning for undefined
    behaviour. One reason for this is that there are instances of undefined
    behaviour that cannot be detected at compile time.

    <OT>
    gcc can give you warnings about some instances of undefined behaviour,
    so it is well worth turning up the warnings. Something like "-ansi
    -pedantic -Wall -O" might be useful. Others have different opinions
    about the best set of options.
    <OT>

    However, always make sure you know *why* any given warning has been
    produced before changing your code to remove it and never assume a
    clean compile means correct code.
    --
    Flash Gordon
    Living in interesting times.
    Although my email address says spam, it is real and I read it.
     
    Flash Gordon, Dec 29, 2004
    #9
  10. In article <>,
    "gvarndell" <> wrote:

    > wrote:
    > > gvarndell <> wrote:
    > > >
    > > > int test(void)
    > > > {
    > > > int a = 17;
    > > >
    > > > *((int *)&a) += 17;
    > > > return a;
    > > > }
    > > >
    > > > Because of the fleeting nature of the values that result from

    > casts,
    > > > I'm not so sure that any conforming C compiler would be to be blame

    > if
    > > > this function returned 17 instead of the expected value of 34.

    > >
    > > I am -- that code is perfectly well defined and any compiler that
    > > doesn't return 34 is broken. Your original code contained type

    > punning
    > > that is *not* well defined and thus compilers are free to do whatever
    > > they like with it.
    > >
    > > -Larry Jones

    >
    > Thank you once again. This makes sense to me.
    > One final question on this, if I may.
    >
    > Since this code
    >
    > void test(void)
    > {
    > void *pData=(void*)0x3400;
    >
    > (*(unsigned char**)&pData) += 4;
    > memcpy((unsigned char *)pData, (unsigned char *)0x1000,10);
    > return;
    > }
    >
    > apparently causes undefined behavior, is a compiler required to at
    > least issue a warning about it?
    > I tried -pedantic with gcc and it doesn't make a peep about the code.


    "Warnings" are not part of the C language. Good compilers give warnings
    in situations where you have written code that is legitimate C code but
    looks to the compiler (which is written by experienced programmers) as
    if you had unintentionally done something that you didn't actually want
    to do.

    The memcpy call most certainly doesn't look unintentionally. Whoever
    wrote that code most certainly intenteded to write that code. Therefore,
    no warning.
     
    Christian Bau, Dec 29, 2004
    #10
    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. Simon North
    Replies:
    0
    Views:
    381
    Simon North
    Aug 5, 2004
  2. Peter Flynn
    Replies:
    2
    Views:
    454
    Peter Flynn
    Aug 9, 2004
  3. Michael Laplante

    Page won't validate -- misuse of A element?

    Michael Laplante, May 18, 2006, in forum: HTML
    Replies:
    3
    Views:
    493
    Jonathan N. Little
    May 18, 2006
  4. John Roth

    Re: Misuse of <tab>

    John Roth, Jul 30, 2003, in forum: Python
    Replies:
    8
    Views:
    390
    Robin Munn
    Aug 12, 2003
  5. Michael Sampson

    Re: Misuse of <tab>

    Michael Sampson, Jul 30, 2003, in forum: Python
    Replies:
    5
    Views:
    376
    Ben Finney
    Jul 31, 2003
Loading...

Share This Page