Pointer Comparisons ">" with One Before the First Element of an Array Object

pointer comparisons “” with one before the first element of an array object

  1. Yes, both are undefined behavior: in the first case, array-1 is undefined; in the second case, ptr becomes undefined when it is decremented after the iteration when it is equal to array.
  2. This is covered by section 6.5.6 of the standard, part 8 (emphasis is mine):

When an expression that has integer type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the pointer operand points to an element of an array object, and the array is large enough, the result points to an element offset from the original element such that the difference of the subscripts of the resulting and original array elements equals the integer expression. In other words, if the expression P points to the i-th element of an array object, the expressions (P)+N (equivalently, N+(P)) and (P)-N (where N has the value n) point to, respectively, the i+n-th and i−n-th elements of the array object, provided they exist. Moreover, if the expression P points to the last element of an array object, the expression (P)+1 points one past the last element of the array object, and if the expression Q points one past the last element of an array object, the expression (Q)-1 points to the last element of the array object. 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. If the result points one past the last element of the array object, it shall not be used as the operand of a unary * operator that is evaluated.

The standard goes length to cover the element at the position one past the last element of the array object, while the element at the position one prior the first element falls under the "otherwise" clause of the above rule.

Pointer to one before first element of array

(1) Is it legal to write --p?

It's "legal" as in the C syntax allows it, but it invokes undefined behavior. For the purpose of finding the relevant section in the standard, --p is equivalent to p = p - 1 (except p is only evaluated once). Then:

C17 6.5.6/8

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.

The evaluation invokes undefined behavior, meaning it doesn't matter if you de-reference the pointer or not - you already invoked undefined behavior.

Furthermore:

C17 6.5.6/9:

When two pointers are subtracted, both shall point to elements of the same array object, or one past the last element of the array object;

If your code violates a "shall" in the ISO standard, it invokes undefined behavior.

(2) Is it legal to write p-1 in an expression?

Same as (1), undefined behavior.


As for examples of how this could cause problems in practice: imagine that the array is placed at the very beginning of a valid memory page. When you decrement outside that page, there could be a hardware exception or a pointer trap representation. This isn't a completely unlikely scenario for microcontrollers, particularly when they are using segmented memory maps.

pointer comparisons with one past the last element of an array object

Yes, a pointer is permitted to point to the location just past the end of the array. However you aren't permitted to deference such a pointer.

C99 6.5.6/8 Additive operators (emphasis added)

if the expression P points to the last element of an array object, the
expression (P)+1 points one past the last element of the array object,
and if the expression Q points one past the last element of an array
object, the expression (Q)-1 points to the last element of the array
object. 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. If the result points one past the last element
of the array object, it shall not be used as the operand of a unary *
operator that is evaluated.

And, specifically for comparision operations on pointers:

C99 6.5.8/5 Relational operators

If the expression P points to an element of an array object and the
expression Q points to the last element of the same array object, the
pointer expression Q+1 compares greater than P. In all other cases,
the behavior is undefined.

Why is pointing to one before the first element of an array not allowed in C?

The topic "a pointer is allowed to point to any element of an array" has been part of the C language for a very long time; it can already be found in the ANSI-C standard / C89 (cf. section 6.3.6 Additive operators, page 46f), which already mentions that within this range implementations must prevent arithmetic overflows. Therein, at the end of page 47, one can also find a footnote that explains the rational behind it:

Another way to approach pointer arithmetic is to first convert the
pointer(s) to character pointers: In this scheme the integral
expression added to or subtracted from the converted pointer is first
multiplied by the size of the object originally pointed to and the
resulting pointer is converted back to the original type. For pointer
subtraction the result of the difference between the character
pointers is similarly divided by the size of the object originally
pointed to.

When viewed in this way, implementations need only provide one extra
byte (which may overlap another object in the program) just after the
end of the object in order to satisfy the "one past the last element"
requirement
.

Therefrom we may deduce that the reason is preventing arithmetic overflows, which an implementation must avoid in the range of an array and one past (but which obviously does not need to be guaranteed for an element one before the first element of an array).

Why not one before - is probably (I have no proof for that) connected with the fact that pointers ranges could be limited to particular memory segments, and placing objects at the begin of a memory segment together with arithmetics like "one before the first element" could lead to an overflow. I'd say statement "implementations need only provide one extra byte (which may overlap another object in the program) just after the end of the object" is a clear indicator supporting this assumption, as the standard at this time explicitly addressed the "one extra byte"-topic at the end but did not suggest something similar for the begin of memory segments.

Is comparing to a pointer one element past the end of an array well-defined?

Comparing to a pointer one step past the end of an array is well defined. However, your pointer and pointer2 examples are undefined, even if you do literally nothing with those pointers.

A pointer may point to one element past the end of the array. That pointer may not be dereferenced (otherwise that would be undefined behavior) but it can be compared to another pointer within the array.

Section 6.5.6 of the C standard says the following regarding pointer addition (emphasis added):

8 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
. If the result points one past the last element
of the array object, it shall not be used as the operand of a unary *
operator that is evaluated.

Section 6.5.8 says the following regarding pointer comparisons (emphasis added):

5 When two pointers are compared, the result depends on the relative locations in the address space of the objects pointed to. If two
pointers to object types both point to the same object, or both point
one past the last element of the same array object, they compare
equal.
If the objects pointed to are members of the same aggregate
object, pointers to structure members declared later compare greater
than pointers to members declared earlier in the structure, and
pointers to array elements with larger subscript values compare
greater than pointers to elements of the same array with lower
subscript values. All pointers to members of the same union object
compare equal. If the expression P points to an element of an array
object and the expression Q points to the last element of the same
array object, the pointer expression Q+1 compares greater than P. In
all other cases, the behavior is undefined.

In the case of pointer1, it starts out pointing to NULL. Incrementing this pointer invokes undefined behavior because it don't point to a valid object.

For pointer2, it is increased by 4, putting it two elements past the end of the array, not one, so this is again undefined behavior. Had it been increased by 3, the behavior would be well defined.

Pointers comparison

No. It is only valid to compare pointers which point to an element within an array object or one past the end of the array object. But there is another problem here.

As they are defined above (p1 pointing to the first element of arr and p2 pointing to the last element of arr), you might think that attempting to dereference p1 - 3 or p2 + 6 would lead to undefined behavior; but even attempting to form the addresses p1 - 3 or p2 + 6 by using pointer arithmetic leads to undefined behavior, so the program is invalid the moment this pointer arithmetic is attempted. You can use pointer arithmetic to form an address that is within the array object, or one past the end of the array object. No other addresses may be formed from a pointer to an element of the array using pointer arithmetic.

Treating a single object like an array with one element, taking one-past-end pointer

The answer is in the paragraph before the one you quote:

4/ For the purposes of these operators, a pointer to a nonarray object behaves the same as a pointer to the first element of an array of length one with the type of the object as its element type.

(Note: I'm quoting C++11 as I don't have C++03 to hand. I'm fairly sure nothing has changed.)

So yes, &na + 1 is a valid past-the-end pointer.

Is it legal to compare a pointer to the beginning of an array with a pointer of the same type pointing before the beginning of the array?

No, it isn't. Using a pointer which doesn't point to an element of the array or one past its end (i. e. which isn't in the range [&arr[0], &arr[size]]) invokes undefined behavior.

C11 Standard, 6.5.6.8 ("Additive Operators"):

If both the pointer operand and the result [of P + N] 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.

(emphasis mine)



Related Topics



Leave a reply



Submit