[Python-Dev] PyObject* aliasing (Was: PyRange_New() alternative?)

"Martin v. Löwis" martin at v.loewis.de
Sat Jun 24 15:30:13 CEST 2006

Scott David Daniels wrote:
>     ... (PyObject*) (void *) &PyRange_Type, ...
> Says a pointer to PyRange_Type should have the structure of a pointer
> PyObject.  Since the prelude to PyTypeObject matches that of PyObject,
> this should be an effective cast.

The C standard says it's undefined behaviour. The compiler is free
to layout PyObject entirely different from PyTypeObject, and
dereferencing a PyTypeObject through a PyObject* is undefined
behaviour. Python does this all the time, and it did not cause much
problems in the past, but that all still does not make it
defined behaviour.

In particular, gcc now starts to assume (rightfully) that a
PyTypeObject* and a PyObject* possibly cannot refer to the same
memory when being dereferenced (hence the warning about aliasing).
That means that the compiler does not need to re-read contents of
one of them (e.g. the reference count) even if the memory gets
changed through the other pointer. That may cause bad code to be
generated (if the pointers actually do alias).

The only well-defined way to alias between types in this context
is that a pointer to a struct may alias with a pointer to its
first member. So if PyTypeObject was defined as

struct PyTypeObject {
  PyObject _ob;
  Py_ssize_t ob_size;
  const char *tp_name;

then Python's behaviour would be well-defined (i.e. one could
dereference the _ob member through a PyObject*).

> Do not mistake this for advocacy of changing Python's macros; I was
> telling the OP how he could shut up the complaint he was getting.  In
> C extensions I'd be likely to do the "convert through void *" trick
> myself.

And risk that the compiler generates bad code. In most cases, the
compiler cannot detect that a program breaks the standard C aliasing
rules. However, in some cases, it can, and in these cases, it issues
a warning to make the programmer aware that the program might be
full of errors (such as Python). It's unfortunate that people silence
the warnings before understanding them.


P.S. As for an example where the compiler really does generate bad
code: consider

#include <stdio.h>

long f(int *a, long *d){
        *a = 5;
        return  *d;

int main()
        long d = 0;
        printf("%ld\n", f((int*)&d, &d));
        return 0;

Here, d starts out as 0, then gets incremented (to 1) in
f. Then, a value of 5 is assigned through a (which also
points to d), and then the value of d (through the pointer)
is printed.

Without optimization, gcc 4.1 generates code that prints 5.
With optimization, the compiler recognizes that d and a
cannot alias, so that it does not need to refetch *d at
the end of f (it still has the value in a register). So
it does not reread the value, and instead returns the old
value (1), and prints that.

In calling f, the compiler notices that undefined behavior
is invoked, and generates the warning. Casting through
void* silences the warning; the generated code is still
"incorrect" (of course, it's undefined, so anything
is "correct").

More information about the Python-Dev mailing list