First some background ...
The macros are
NULL which expands to an implementation-defined null pointer constant; C11 §7.19 3
NULL typically is an integer constant 0 or
(void*)0 or the like. It may have a different implementation or type - It could be
((int*) 0xDEADBEEF) as strange as that may be.
NULL might be type
int. It might be type
void * or something else. The type of
NULL is not defined.
When the null pointer constant
NULL is cast to any pointer, is is a null pointer. An integer
0 cast to a pointer is also a null pointer. A system could have many different (bit-wise) null pointers. They all compare equally to each other. They all compare unequally to any valid object/function. Recall this compare is done as pointers, not integers.
An integer constant expression with the value 0, or such an expression cast to type
void *, is called a null pointer constant. If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function. C11 §220.127.116.11 3
if (&x == NULL) ... // this is false
So after all that chapter and verse how to distinguish
If the macro
NULL is defined as an
0 - it is game over - there is no difference between
NULL is not an
int, then code can use
_Generic() to differentiate
0. This does not help OP's "Any change made can only be made within the function itself." requirement as that function accepts an
NULL is an
int that has a different bit-pattern than
0, then a simple
memcmp() can differentiate.
I suspect the whole reason for this exercise is to realize there is no portable method to distinguish