There are lots of examples of undefined/unspecified behavior when doing pointer arithmetics - pointers have to point inside the same array (or one past the end), or inside t
Operations on a pointer (like incrementing, adding, etc) are generally only valid if both the initial value of the pointer and the result point to elements of the same array (or to one past the last element). Otherwise the result is undefined. There are various clauses in the standard for the various operators saying this, including for incrementing and adding.
(There are a couple of exceptions like adding zero to NULL or subtracting zero from NULL being valid, but that doesn't apply here).
A NULL pointer does not point at anything, so incrementing it gives undefined behaviour (the "otherwise" clause applies).
There seems to be quite low understanding what "undefined behaviour" means.
In C, C++, and related languages like Objective-C, there are four kinds of behaviour: There is behaviour defined by the language standard. There is implementation defined behaviour, which means the language standard explicitely says that the implementation must define the behaviour. There is unspecified behaviour, where the language standard says that several behaviours are possible. And there is undefined behaviour, where the language standard doesn't say anything about the result. Because the language standard doesn't say anything about the result, anything at all can happen with undefined behaviour.
Some people here assume that "undefined behaviour" means "something bad happens". That's wrong. It means "anything can happen", and that includes "something bad can happen", not "something bad must happen". In practice it means "nothing bad happens when you test your program, but as soon as it is shipped to a customer, all hell breaks loose". Since anything can happen, the compiler can actually assume that there is no undefined behaviour in your code - because either it is true, or it is false, in which case anything can happen, which means whatever happens because of the compiler's wrong assumption is still correct.
Someone claimed that when p points to an array of 3 elements, and p + 4 is calculated, nothing bad will happen. Wrong. Here comes your optimising compiler. Say this is your code:
int f (int x)
{
int a [3], b [4];
int* p = (x == 0 ? &a [0] : &b [0]);
p + 4;
return x == 0 ? 0 : 1000000 / x;
}
Evaluating p + 4 is undefined behaviour if p points to a [0], but not if it points to b [0]. The compiler is therefore allowed to assume that p points to b [0]. The compiler is therefore allowed to assume that x != 0, because x == 0 leads to undefined behaviour. The compiler is therefore allowed to remove the x == 0 check in the return statement and just return 1000000 / x. Which means your program crashes when you call f (0) instead of returning 0.
Another assumption made was that if you increment a null pointer and then decrement it again, the result is again a null pointer. Wrong again. Apart from the possibility that incrementing a null pointer might just crash on some hardware, what about this: Since incrementing a null pointer is undefined behavour, the compiler checks whether a pointer is null and only increments the pointer if it isn't a null pointer, so p + 1 is again a null pointer. And normally it would do the same for the decrementing, but being a clever compiler it notices that p + 1 is always undefined behaviour if the result was a null pointer, therefore it can be assumed that p + 1 isn't a null pointer, therefore the null pointer check can be ommitted. Which means (p + 1) - 1 is not a null pointer if p was a null pointer.
Given that you can increment any pointer of a well-defined size (so anything that isn't a void pointer), and the value of any pointer is just an address (there's no special handling for NULL pointers once they exist), I suppose there's no reason why an incremented null pointer wouldn't (uselessly) point to the 'one after NULL'est item.
Consider this:
// These functions are horrible, but they do return the 'next'
// and 'prev' items of an int array if you pass in a pointer to a cell.
int *get_next(int *p) { return p+1; }
int *get_prev(int *p) { return p-1; }
int *j = 0;
int *also_j = get_prev(get_next(j));
also_j has had maths done to it, but it's equal to j so it's a null pointer.
Therefore, I would suggest that's it's well-defined, just useless.
(And the null pointer appearing to have the value zero when printfed is irrelevant. The value of the null pointer is platform dependent. The use of a zero in the language to initialise pointer variables is a language definition.)
From ISO IEC 14882-2011 §5.2.6:
The value of a postfix ++ expression is the value of its operand. [ Note: the value obtained is a copy of the original value —end note ] The operand shall be a modifiable lvalue. The type of the operand shall be an arithmetic type or a pointer to a complete object type.
Since a nullptr is a pointer to a complete object type. So I wouldn't see why this would be undefined behaviour.
As has been said before the same document also states in §5.2.6/1:
If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.
This expression seems a bit ambiguous. In my interpretation, the undefined part might very well be the evaluation of the object. And I think nobody would disagree with this being the case. However, pointer arithmetics seem to only require a complete object.
Of course postfix [] operators and subtractions or multiplications on pointer to array objects are only well defined, if they in fact point to the same array. Mostly important because one might be tempted to think that 2 arrays defined in succession in 1 object, can be iterated over like they were a single array.
So my conclusion would be that the operation is well defined, but evaluation would not be.
Back in the fun C days, if p was a pointer to something, p++ was effectively adding the size of p to the pointer value to make p point at the next something. If you set the pointer p to 0, then it stands to reason that p++ would still point it at the next thing by adding the size of p to it.
What's more, you could do things like add or subtract numbers from p to move it along through memory (p+4 would point at the 4th something past p.) These were good times that made sense. Depending on the compiler, you could go anywhere you wanted within your memory space. Programs ran fast, even on slow hardware because C just did what you told it to and crashed if you got too crazy/sloppy.
So the real answer is that setting a pointer to 0 is well-defined and incrementing a pointer is well-defined. Any other constraints are placed on you by compiler builders, os developers and hardware designers.
As said by Columbo it is UB. And from a language lawyer point of view this is the definitive answer.
However all C++ compiler implementations I know will give same result :
int *p = 0;
intptr_t ip = (intptr_t) p + 1;
cout << ip - sizeof(int) << endl;
gives 0
, meaning that p
has value 4 on a 32 bit implementation and 8 on a 64 bits one
Said differently :
int *p = 0;
intptr_t ip = (intptr_t) p; // well defined behaviour
ip += sizeof(int); // integer addition : well defined behaviour
int *p2 = (int *) ip; // formally UB
p++; // formally UB
assert ( p2 == p) ; // works on all major implementation