Hi
are you saying that
---
int foo(void* bar)
{
char *st = (char*)bar;
:
}
:
:
char *bar;
:
foo((void*)bar);
No, because here you are doing a well-defined conversion from char *
to void * and back. There is no type-punning going on.
(In fact, you have not even dereferenced a pointer at all!)
The problem is pretending that a char * object /is/ a void *, by
aiming a void ** pointer at it and dereferencing it. This is what your
original program does, and what is diagnosed by GCC (it break strict
aliasing rules).
What this means is that when the compiler is optimizing code, it won't
be making any consideration about data flow paths through the code
which are based on wrongful aliasing.
The C language allows this, by marking such wrongful aliasing as
undefined behavior. Undefined behavior means that the program has
problem, and there are no requirements about how your language
implementation should respond. (Or any such requirements do not come
from the language standard, but from somewhere else, like the
impelmentor's commitment to some additional design documents about
their compiler).
Here is an example.
Suppose you have a function in which you are working with, say,
variables of type double (it's some kind of number crunching). And
suppose that the same code also manipulates data through int * type
pointers.
The compiler can assume that the int * pointers are not aimed at any
of the double type objects, and optimize accordingly.
If an assignment occurs thorugh the int *, the compiler can assume
that none of the values of type double are touched.
Similar reasoning applies to any two incompatible types:
char *a = "abc";
void **pa = (void **) &a; // pa is a type-punned pointer
*pa = "def";
puts(a);
What should this print, abc or def? Trick question: the behavior is
undefined, so anything can happen. But it's a realistic possibility
that in fact abc is printed, because an optimizer may simply discard
the possibility that the *pa = "def" assignment has any effect on the
value of a. The reason is that *pa and a have different,
incompatible types. Assignment to a void * lvalue is not supposed to
have an effect on the value of any char * object anywhere in the
program.
Recent versions of the GNU compiler can diagnose some cases of type-
punned pointers. Code like that is recognized as representing a kind
of common programming practice. It is supported by GCC as an extension
to undefined behavior. You must use the compiler option -fno-strict-
aliasing. The diagnostic message will then go away. Of course, you are
no longer programming in ICO C, but in a dialect which supports
aliasing.
And note that this is not perfectly well-defined either. GNU C does
not say, for instance, what will happen to the value of x if you do
this, even if -fno-strict-aliasing is enabled:
double x = 3.14;
int *p = (int *) &x;
*p = 42;
The -fno-strict-aliasing extension works with objects that have a
compatible representation. I.e. things that are de-facto compatible,
or highly compatible, at the implementation level. Things like
structurally equivalent structs, pointers (on most machines), signed
and unsigned versions of integral types, etc.
Is there really no way at all to modify a generic
pointer by passing a reference to it to a function?
That is false. You can convert the pointer to a void * type, stored in
a void * object, then have the function modify the void * object.
Then, convert the modified void * back to the original type.
int wrap_realloc(void **pp, size_t nsz)
{
void *np = realloc(*pp, nsz);
if (np != 0) {
*pp = np;
return 1;
}
return 0; // leave *pp alone
}
Use it:
char *ptr = malloc(10);
/* check for errors etc */
void *modify = ptr;
if (wrap_realloc(&modify, 20)) {
/* success */
ptr = modify;
}
The problem with the void ** aliasing hack is that it doesn't do a
proper conversion like this. It just assumes that the original pointer
ptr can be treated as if it were a void *. The aliasing rules forbid
this even if a void * has exactly the same representation as a char *
in order to allow for aggressive optimization based on type.