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.