Is null pointer increment correct?

There are many examples of undefined / unspecified behavior when performing pointer arithmetic - pointers must point inside the same array (or one after the end) or inside the same object, restrictions on when you can do comparisons / operations based on the foregoing and etc.

Is the following operation correct?

int* p = 0; p++; 
+47
c ++ pointers language-lawyer
Apr 23 '15 at 13:44 on
source share
8 answers

§5.2.6 / 1:

The value of the operand object is modified by adding 1 to it, if only the object is of type bool [..]

And additive expressions containing pointers are defined in §5.7 / 5:

If both pointer operands and the result point to the elements of the same array object or one after the last element of the array object, the evaluation should not lead to overflow; otherwise, the behavior is undefined.

+37
Apr 23 '15 at 13:51 on
source share

It seems like a pretty poor understanding of what undefined behavior means.

In C, C ++, and related languages, such as Objective-C, there are four types of behavior: there is behavior defined by the language standard. There is a specific implementation behavior, which means that the language standard explicitly states that the implementation should define the behavior. There is undefined behavior when the locale states that several behaviors are possible. And there is undefined behavior , where the locale says nothing about the result . Since the locale says nothing about the result, everything can happen with undefined behavior.

Some people suggest that "undefined behavior" means "something bad." It is not right. This means that “everything can happen,” and that includes “something bad can happen,” not “something bad must happen.” In practice, this means: "nothing bad happens when you test your program, but as soon as it is sent to the client, all hell breaks." Since anything can happen, the compiler can actually assume that your code does not have undefined behavior, because either it is true or it is false, in which case something can happen, which means that everything happens due to a wrong compiler assumption still true.

Someone claimed that when p points to an array of three elements and p + 4 is calculated, nothing bad will happen. Wrong. Here is your optimization 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; } 

A score of p + 4 is undefined behavior if p points to [0] but does not point to b [0]. Therefore, the compiler is allowed to assume that p points to b [0]. Therefore, the compiler is allowed to assume that x! = 0, because x == 0 leads to undefined behavior. Therefore, the compiler is allowed to remove the x == 0 check in the return statement and simply return 1,000,000 / x. This means that your program crashes when you call f (0) instead of returning 0.

Another assumption was that if you increase the null pointer and then decrease it again, the result is the null pointer again. Wrong. Besides the possibility that incrementing a null pointer might just work on some hardware, that’s about it: since incrementing a null pointer is an undefined behavior, the compiler checks if the pointer is null and only increments the pointer if it is not a null pointer, so p + 1 is again a null pointer. And, as a rule, it will do the same for decrementing, but, being a smart compiler, notices that p + 1 is always undefined if the result was a null pointer, so we can assume that p + 1 is not a null pointer, so check null pointer may be omitted. This means that (p + 1) - 1 is not a null pointer if p was a null pointer.

+14
Apr 23 '15 at 23:27
source share

Operations on a pointer (for example, incrementing, add, etc) are usually valid only if the initial value of the pointer and the result point to elements of the same array (or one last element). Otherwise, the result will be undefined. There are various conditions in the standard for various operators talking about it, including for incrementing and adding.

(There are a few exceptions, such as adding zero to NULL or subtracting a zero from NULL, but this does not apply here).

The NULL pointer does not indicate anything, so incrementing it gives undefined behavior (the “otherwise” clause applies).

+13
Apr 23 '15 at 13:55
source share

Turns out this is really undefined. There are systems for which this is true.

 int *p = NULL; if (*(int *)&p == 0xFFFF) 

Therefore, ++ p will disable the undefined overflow rule (it turns out that sizeof (int *) == 2)). Pointers do not guarantee the absence of unsigned integers, so the unsigned zeroing rule does not apply.

+1
Apr 23 '15 at 22:31
source share

As Colombo said, this is UB. And from the point of view of a language advocate, this is the final answer.

However, all the C ++ compiler implementations that I know will give the same result:

 int *p = 0; intptr_t ip = (intptr_t) p + 1; cout << ip - sizeof(int) << endl; 

gives 0 , which means that p has a value of 4 in a 32-bit implementation and 8 on 64 bits

They say otherwise:

 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 
0
Apr 23 '15 at 2:04
source share

From ISO IEC 14882-2011 §5.2.6:

The value of the postfix ++ expression is the value of its operand. [Note: the value obtained is a copy of the original value -end note] The operand must be a modifiable value of lvalue. The operand type must be an arithmetic type or a pointer to the full type of the object.

Since nullptr is a pointer to the full type of an object. Therefore, I do not understand why this will be undefined.

As mentioned above, the same document also states in paragraph 5.2.6 / 1:

If both pointer operands and the result point to elements of the same array object or one past the last element of the array object, the evaluation should not lead to overflow; otherwise, the behavior is undefined.

This expression seems a bit ambiguous. In my interpretation, the undefined part may well be an evaluation of the object. And I think no one would agree with that. However, pointer arithmetic requires only a complete object.

Of course, the operators of postfix [] and subtraction or multiplication by a pointer to array objects are only clearly defined if they actually point to the same array. Mostly important, because you might be tempted to think that 2 arrays defined sequentially in 1 object can be repeated as if they were a single array.

So, my conclusion would be that the operation is well defined, but there will be no evaluation.

0
May 20 '15 at 8:05
source share

Back in the fun C days, if p was a pointer to something, p ++ effectively added the size p to the pointer value to make p the next point. If you set the p pointer to 0, it makes sense that p ++ will still point it to the next one by adding the size p to it.

What more, you could do something like adding or subtracting numbers from p to move it from memory (p + 4 would point to the fourth something past p.) These were good times that made sense. Depending on the compiler, you can go anywhere in your memory space. Programs ran quickly, even on slow hardware, because C just did what you told him and crashed if you are too crazy / messy.

So the real answer is that setting the pointer to 0 is correctly defined and the pointer increment is well defined. Any other restrictions are imposed on you by the compiler developers, developers, and hardware developers.

-one
Apr 24 '15 at 7:49
source share

Given that you can increment any pointer by a clearly defined size (so everything that is not a void pointer), and the value of any pointer is just an address (there is no special handling of NULL pointers if they exist), I suppose not the reasons why the incremental null pointer will not (uselessly) point to "one after the NULL element".

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)); 

and_j did the math, but it equals j, so this is a null pointer.

Therefore, I would suggest that it is clearly defined, just useless.

(And a null pointer that has a value of 0 when printfed doesn't matter. The value of a null pointer is platform dependent. Using zero in a language to initialize pointer variables is a language definition.)

-2
Apr 23 '15 at 14:56
source share



All Articles