One of the very first architectures that C targeted were some with 36-bit or 18-bit words words (the type int
). Only the words were directly addressable at addresses like 0, 1, 2 using the native pointers. However one word for one character would have wasted too much memory, so a 9-bit char
type was added, with 2 or 4 characters in one word. Since these would not have been addressable by the word pointer, char *
was made from two words: one pointing to the word, and another telling which of the bytes within the word should be manipulated.
Of course now the problem is that char *
is two words wide, whereas int *
is just one, and this matters when calling a function without prototype or with ellipsis - while (void*)0
would have a representation compatible with (char *)0
, it wouldn't be compatible with (int *)0
, hence an explicit cast is required.
There is another problem with NULL
. While GCC seems to assure that NULL
will be of type void *
, the C standard does not guarantee that, so even using NULL
in a function call like execl
that expects char *
s as variable arguments is wrong without a cast, because an implementation can define
#define NULL 0
(sem_t*)-1
is not a NULL pointer, it is the integer -1
converted to pointer with implementation-defined results. On POSIX systems it will (by necessity) result in an address that can never be a location of any sem_t
.
It is actually a really bad convention to use -1
here since the resulting address most likely doesn't have a correct alignment for sem_t
, so the entire construct has undefined behaviour in itself.