They might be different in theory; they are not in practice.
No, it is a purely practical matter. It's the difference between code working reliably and not.
In addition, in Objective-C, C, and C++, NULL is, in fact, (void *)0L.
NULL is a macro who's definition is implementation-defined. It is most commonly defined as 0 or 0L, but I have also seen (void *)0 and even (char *)0. I just looked at string.h from gcc 4.3.4 and it is defined as 0. You're looking at this and thinking (void *)0 is reinterpreting the number 0 as a pointer. It is not, and none of the languages that you mention allow you to convert integers into pointers. 0L, in this context, is implicitly a pointer which may or may not have a numeric value of 0 (look at DOS - it had at least different kinds of pointers with different sizes, some of which were tuples and therefore didn't evaluate to single numeric values at all).
This is not something that is likely to *ever* change, given the absolutely enormous body of code that assumes that (!pointer) is identical to (pointer == NULL); this is not something limited to x86.
It changes constantly, but it's more of an implementation thing than a platform thing. (Of course, the platform plays a big role in the implementer's decision.)
Also, char *pointer = 0; being anything other than 0 is rubbish. There are systems for which 0 can contain valid data, and therefore you must be able to assign 0 to a pointer.
Those are the very systems which tend to define null as something other than a pointer to memory at address 0. C, C++ and Objective-C do not allow you to assign integer values to pointers anyway - literal zero integers implicitly evaluate to null pointers (whatever those are) which can be anything. Let me give you an example.
int zero = 0;
int null_is_zero = (0 == *(void **)(&zero)); /* nonzero if and only if null is defined as zero. Might break if size of int and void* are different. */
char *pointer = 0; /* pointer is null, which is who-knows-where */
pointer = zero; /* error. zero has a value of 0, but there is no implicit integer-to-pointer conversion. */
pointer = 1; /* error - you can't assign integers to pointers
pointer = *(char **)(&zero); /* dangerous because sizeof(char*) might be different to sizeof(int). pointer now points to memory with address 0, and may or may not be a null pointer */