A compiler should probably give you an error if you are applying the unary negation operator to a pointer. So far as I'm aware, that operator requires a number of some kind, not a pointer.
From the 1999 C Standard:
6.5.3.3 Unary arithmetic operators
Constraints
The operand of the unary + or - operator shall have arithmetic type;
of the ~ operator, integer type; of the ! operator, scalar type.
Semantics
The result of the unary + operator is the value of its (promoted)
operand. The integer promotions are performed on the operand,
and the result has the promoted type.
The result of the unary - operator is the negative of its (promoted)
operand. The integer promotions are performed on the operand,
and the result has the promoted type.
I don't believe there is a valid "integer promotion" for pointers (except in the unusual, but specified, case of the NULL pointer which the standard requires to be able to be promoted to an integer.)
If there were such a valid integer promotion before performing the unary minus operator, then the result would anyway be an integer and not a pointer. So again, there should be an error turning that back into a pointer, again.
But there isn't such a promotion, so it doesn't matter. The point is, either way, there should be an error.
It is valid to take the difference between pointers of the same type. This computes the number of items between the two pointers and produces an integer value. So it WOULD be valid to take the difference, like this:
int array[20];
int *p= &array[5];
int diff= ((char *) 0) - (char *) p;
This first creates a NULL character pointer constant of 0 and then converts the integer pointer to a character pointer as well, before taking the difference. So the result would be a negative value. It amounts to something similar, you could argue. And it is legal, I believe.
However, to reverse this back into a valid pointer would require something like the following:
int *q= (int *) (((char *) 0) + diff);
It's all pretty ugly. Besides, it's probably not guaranteed to have defined behavior. The size of pointers to various types of objects are not guaranteed to be the same size. Only the (void *) pointer is guaranteed to be large enough to hold ANY data pointer (not function pointer, even.) And you cannot do computations with a void pointer. It must first be converted.
Negation is, in effect, the same thing as adding a large constant defined by the word size and the numerical notation form that is used. If you think about that, in terms of pointers, this just "assumes" that memory pointers with some large offset to them can be safely compared with pointers without that added offset. But even that isn't entirely safe to do. So far as I'm aware, there is no such guarantee in C like that. Only the NULL pointer has specific guarantees like that.
So it's not safe and it's likely to generate errors.
I do NOT agree with fallible (or anyone else) that pointers are unsigned ints. On some machines I've experienced, they are VERY SPECIAL values and the very hardware they run on cannot represent them as integers of any kind. Integers in C are usually of a size and type that matches up with the arithmetic logic unit's data size. Pointers can be not only MUCH LARGER but also MUCH MORE COMPLEX. For example, on the 8088, a pointer was two 16-bit values which are added in the hardware with one multiplied by 16 before being added to the other in order to create a 20 bit physical address. The ALU handled 16 bit values. Internally, though, the C compiler usually (not always, because Borland did it differently than Microsoft for some years) handled a pointer as a pairing of two 16-bit values. Worse, there were many different pair-values that would point to the exact same 20 bit physical address, despite being very different pairs of 16 bit values.
If you need storage of different kinds of pointers into the same variable, then you are supposed to use (void *) as the type and convert back and forth.
As mentioned, it is POSSIBLE to negate a pointer by first converting it to a "span" (which is perfectly legal and portable) and then negating that difference (also legal.) This difference can then (legally and portably) be converted back into a pointer by adding it to a pointer that points at the base of an array in memory, for example. This is all within the standard. But simple negation of a pointer is not.
Oh, and you SHOULD have included a declaration/definition of your stack variable.
A very simple depth-first, post-order traversal of a tree is nothing more than this in C:
void treewalk( node * n, void (*p)( node * ) ) {
if ( n != NULL ) {
treewalk( n->left );
treewalk( n->right );
p( n );
}
}