C / C ++ deliberate indexing out of range

Say I have an array like this:

int val[10]; 

and I intentionally index it with everything from negative values ​​to nothing higher than 9, but WITHOUT using the resulting value in any way. This would be for performance reasons (it might be more efficient to check the input index AFTER accessing the array).

My questions:

  • Is it safe, or will I run into some memory protection barriers, risk of memory corruption, or similar for specific indexes?
  • Perhaps this is not entirely effective if I access data outside the acceptable range? (assuming the array has no built-in range check).
  • Would this be considered bad practice? (assuming the comment is written to indicate that we know about using indices out of range).
+6
source share
4 answers

This behavior is undefined. By definition, undefined means "anything can happen." Your code may fall, it may work perfectly, it can lead to peace and harmony among all people. I would not bet on the second or last.

+17
source

This is Undefined Behavior, and you can really run optimizers.

Imagine this simple code example:

 int select(int i) { int values[10] = { .... }; int const result = values[i]; if (i < 0 or i > 9) throw std::out_of_range("out!"); return result; } 

Now let's look at this from an optimization point of view:

  • int values[10] = { ... }; : actual indices are in [0, 9] .

  • values[i] : i is an index, so i is in [0, 9] .

  • if (i < 0 or i > 9) throw std::out_of_range("out!"); : i is in [0, 9] , never accepted

And therefore, the function rewritten by the optimizer:

 int select(int i) { int values[10] = { ... }; return values[i]; } 

For funnier stories about the direct and reverse distribution of assumptions based on the fact that the developer does not prohibit anything, see What Every Programmer Should Know C Undefined Behavior: Part 2 .

EDIT:

Possible workaround: if you know that you will get access from -M to +N , you can:

  • declare an array with the appropriate buffer: int values[M + 10 + N]
  • offset of any access: values[M + i]
+9
source

As verbose said , this gives undefined behavior. What follows is a higher accuracy.

5.2.1 / 1 says

[...] The expression E1 [E2] is identical (by definition) to * ((E1) + (E2))

Therefore, val[i] equivalent to *((val)+i)) . Since val is an array, conversion from array to pointer (4.2 / 1) occurs before the addition is done. Therefore, val[i] equivalent to *(ptr + i) , where ptr is int* set to &val[0] .

Then 5.7 / 2 explains what ptr + i indicates. He also says (emphasis mine):

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

In the case of ptr + i , ptr is the pointer operand, and the result is ptr + i . According to the quote above, both must point to an element of the array or to one last element. That is, in the case of OP, ptr + i is a well-defined expression for all i = 0, ..., 10 . Finally, *(ptr + i) well defined for 0 <= i < 10 , but not for i = 10 .

Edit

I am puzzled that val[10] (or, equivalently, *(ptr + 10) ) gives the behavior undefined or not (I am considering C ++, not C). In some cases this is true (for example, int x = val[10]; - the behavior is undefined), but in others it is not so clear. For instance,

 int* p = &val[10]; 

As we have seen, this is equivalent to int* p = &*(ptr + 10); , which may be undefined behavior (since it shares a pointer to one of the last elements of val ) or the same as int* p = ptr + 10; which is well defined.

I found these two links that show how fuzzy this question is:

Can I take the address of a one-end-end element from an array?

Take the address of the element of the array "one after the past" through the index: legal according to the C ++ standard or not?

+5
source

If you put it in a structure with several uppercase ints, this should be safe (since the pointer actually points to "known" destinations). But it is better to avoid this.

 struct SafeOutOfBoundsAccess { int paddingBefore[6]; int val[10]; int paddingAfter[6]; }; void foo() { SafeOutOfBoundsAccess a; bool maybeTrue1 = a.val[-1] == a.paddingBefore[5]; bool maybeTrue2 = a.val[10] == a.paddingAfter[0]; } 
+3
source

Source: https://habr.com/ru/post/953759/


All Articles