ewx: (geek)
Richard Kettlewell ([personal profile] ewx) wrote2008-03-14 12:01 pm

#define NULL 0

A discussion elsewhere raised the question of null pointers and varargs functions. The issue is essentially that NULL on its own is not guaranteed to a be a null pointer, and varargs functions are one of the contexts where this may actually matter. (The comp.lang.c FAQ has a whole section on this area.)

In particular a platform with 32-bit ints and 64-bit pointers that defined NULL to 0 would be expected to pass a value of the wrong size when just NULL was used where a pointer was expected by a varargs function.

I looked at a number of platforms to see how they defined NULL and what the outcome was.

Platform Definition of NULL
Solaris 0 on 32-bit, 0L on 64-bit
AIX 0
Linux ((void *)0)
HPUX 0L
Windows ((void *)0)
Mac OS X ((void *)0)

In all cases my test program (which passed a bare NULL for printf()'s %p conversion specifier) worked fine.

For the Windows/Linux/Mac definition of ((void *)0) this is completely unsurprising.

For HPUX, long is always the same width as a pointer. The same is true on Solaris but they went for distinct definitions for 32- and 64-bit anyway, probably to remain absolutely identical to historical definitions for 32-bit builds.

AIX is the slightly surprising one. Superficially in a 64-bit build you're passing a 32-bit value where a 64-bit value is required. In practice it turns out that it always gets away with it: the code generated to store even a 32-bit 0 is:

        addi    r30,r0,0
        std     r30,184(SP)

i.e. it always stores a 64-bit 0 even if you only ask for 32 bits (std is “store double-word”, a word being 32 bits in this context.)

simont: A picture of me in 2016 (Default)

[personal profile] simont 2008-03-14 12:07 pm (UTC)(link)
The AIX compiler may generate a 64-bit store instruction, but surely the more important question is whether it advances the argument list pointer by 64 bits? If you printf("%d %p %d", 1, NULL, 2), does it still get it right?
ext_8103: (Default)

[identity profile] ewx.livejournal.com 2008-03-14 12:25 pm (UTC)(link)
Yes, it does - in fact my test was quite similar to the example you give there.

[identity profile] bellinghman.livejournal.com 2008-03-14 01:20 pm (UTC)(link)
AIX 64-bit varargs fix? http://gcc.gnu.org/ml/gcc-patches/1999-05n/msg00063.html

(Personally, I'm using xlC on AIX, last 64-bit compile being 30 minutes ago, next being (I hope) months away.)
ext_8103: (Default)

[identity profile] ewx.livejournal.com 2008-03-14 01:25 pm (UTC)(link)
I tested with the native compiler. With GNU C on AIX, at least in the install we've got lying around, the definition of NULL is (void *)0.

[identity profile] imc.livejournal.com 2008-03-14 02:10 pm (UTC)(link)
So questions arising from this might be (a) does it always pass ints as 64 bits (or always for vararg functions), and (b) did it parse the printf format and therefore know you were expecting a pointer?
ext_8103: (Default)

[identity profile] ewx.livejournal.com 2008-03-14 02:23 pm (UTC)(link)

It always passes 64 bits, even if it doesn't know anything about the called function. Source code:

#include <stdarg.h>
#include <stddef.h>

int f(int x, ...);

void g(void) {
  f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, NULL, 99);
}

(Lots of args because if you only have a few they end up in registers instead of on the stack.)

Assembler fragment:

        addi    r11,r0,15
        std     r11,160(SP)
        addi    r11,r0,0
        std     r11,168(SP)
        addi    r11,r0,99
        std     r11,176(SP)

If you turn on optimization then the assembler is harder to read as things are re-ordered rather, but it's still all std.