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

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.