compiler bug or misuse of a cast?

G

gvarndell

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
 
J

Jack Klein

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

lawrence.jones

gvarndell said:
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
 
G

gvarndell

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
 
I

infobahn

gvarndell said:
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.
 
M

Mole Wang

gvarndell said:
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.
 
L

lawrence.jones

gvarndell said:
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
 
G

gvarndell

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
 
F

Flash Gordon

On 29 Dec 2004 09:29:28 -0800

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

Christian Bau

"gvarndell said:
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.
 

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,768
Messages
2,569,574
Members
45,051
Latest member
CarleyMcCr

Latest Threads

Top