Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is this claimed dereferencing type-punned pointer warning compiler-specific?

I've read various posts on Stack Overflow RE: the derefercing type-punned pointer error. My understanding is that the error is essentially the compiler warning of the danger of accessing an object through a pointer of a different type (though an exception appears to be made for char*), which is an understandable and reasonable warning.

My question is specific to the code below: why does casting the address of a pointer to a void** qualify for this warning (promoted to error via -Werror)?

Moreover, this code is compiled for multiple target architectures, only one of which generates the warning/error - might this imply that it is legitimately a compiler version-specific deficiency?

// main.c
#include <stdlib.h>

typedef struct Foo
{
  int i;
} Foo;

void freeFunc( void** obj )
{
  if ( obj && * obj )
  {
    free( *obj );
    *obj = NULL;
  }
}

int main( int argc, char* argv[] )
{
  Foo* f = calloc( 1, sizeof( Foo ) );
  freeFunc( (void**)(&f) );

  return 0;
}

If my understanding, stated above, is correct, a void**, being still just a pointer, this should be safe casting.

Is there a workaround not using lvalues that would pacify this compiler-specific warning/error? I.e. I understand that and why this will resolve the issue, but I would like to avoid this approach because I want to take advantage of freeFunc() NULLing an intended out-arg:

void* tmp = f;
freeFunc( &tmp );
f = NULL;

Problem compiler (one of one):

user@8d63f499ed92:/build$ /usr/local/crosstool/x86-fc3/bin/i686-fc3-linux-gnu-gcc --version && /usr/local/crosstool/x86-fc3/bin/i686-fc3-linux-gnu-gcc -Wall -O2 -Werror ./main.c
i686-fc3-linux-gnu-gcc (GCC) 3.4.5
Copyright (C) 2004 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

./main.c: In function `main':
./main.c:21: warning: dereferencing type-punned pointer will break strict-aliasing rules

user@8d63f499ed92:/build$

Not-complaining compiler (one of many):

user@8d63f499ed92:/build$ /usr/local/crosstool/x86-rh73/bin/i686-rh73-linux-gnu-gcc --version && /usr/local/crosstool/x86-rh73/bin/i686-rh73-linux-gnu-gcc -Wall -O2 -Werror ./main.c
i686-rh73-linux-gnu-gcc (GCC) 3.2.3
Copyright (C) 2002 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

user@8d63f499ed92:/build$

Update: I've further discovered the warning appears to be generated specifically when compiled with -O2 (still with the noted "problem compiler" only)

like image 739
StoneThrow Avatar asked Nov 11 '19 22:11

StoneThrow


2 Answers

A value of type void** is a pointer to an object of type void*. An object of type Foo* is not an object of type void*.

There is an implicit conversion between values of type Foo* and void*. This conversion may change the representation of the value. Similarly, you can write int n = 3; double x = n; and this has the well-defined behavior of setting x to the value 3.0, but double *p = (double*)&n; has undefined behavior (and in practice will not set p to a “pointer to 3.0” on any common architecture).

Architectures where different types of pointers to objects have different representations are rare nowadays, but they are permitted by the C standard. There are (rare) old machines with word pointers which are addresses of a word in memory and byte pointers which are addresses of a word together with a byte offset in this word; Foo* would be a word pointer and void* would be a byte pointer on such architectures. There are (rare) machines with fat pointers which contain information not only about the address of the object, but also about its type, its size and its access control lists; a pointer to a definite type might have a different representation from a void* which needs additional type information at runtime.

Such machines are rare, but permitted by the C standard. And some C compilers take advantage of the permission to treat type-punned pointers as distinct to optimize code. The risk of pointers aliasing is a major limitation to a compiler's ability to optimize code, so compilers tend to take advantage of such permissions.

A compiler is free to tell you that you're doing something wrong, or to quietly do what you didn't want, or to quietly do what you wanted. Undefined behavior allows any of these.

You can make freefunc a macro:

#define FREE_SINGLE_REFERENCE(p) (free(p), (p) = NULL)

This comes with the usual limitations of macros: lack of type safety, p is evaluated twice. Note that this only gives you the safety of not leaving dangling pointers around if p was the single pointer to the freed object.

like image 70
Gilles 'SO- stop being evil' Avatar answered Oct 14 '22 10:10

Gilles 'SO- stop being evil'


A void * is treated specially by the C standard in part because it references an incomplete type. This treatment does not extend to void ** as it does point to a complete type, specifically void *.

The strict aliasing rules say you can't convert a pointer of one type to a pointer of another type and subsequently dereference that pointer because doing so means reinterpreting the bytes of one type as another. The only exception is when converting to a character type which allows you to read the representation of an object.

You can get around this limitation by using a function-like macro instead of a function:

#define freeFunc(obj) (free(obj), (obj) = NULL)

Which you can call like this:

freeFunc(f);

This does have a limitation however, because the above macro will evaluate obj twice. If you're using GCC, this can be avoided with some extensions, specifically the typeof keyword and statement expressions:

#define freeFunc(obj) ({ typeof (&(obj)) ptr = &(obj); free(*ptr); *ptr = NULL; })
like image 33
dbush Avatar answered Oct 14 '22 11:10

dbush