Search code examples
clanguage-lawyerorder-of-executionsequence-points

Am I interpreting C order of operations correctly here?


I was puzzled by the fact that CPPReference says that postincrement’s value evaluation is sequenced before its side-effect, but preincrement has no such guarantee.

I have now come up with an example where this matters but I am unsure if my analysis is correct.

As I understand it, these two programs differ in that the first contains UB, and the second does not:

#include <stddef.h>
#include <stdio.h>

int main(void) {
    int arr[] = {0, 1, 2};
    int i = 1;
    int x = ++arr[arr[i]];
}
#include <stddef.h>
#include <stdio.h>

int main(void) {
    int arr[] = {0, 1, 2};
    int i = 1;
    int x = arr[arr[i]]++;
}

My analysis of the expression ++arr[arr[i]] is as follows:

  1. There are these sequenced-before relations:
    • Value computation of i is sequenced before value computation of arr[i]
    • Value computation of arr[i] is sequenced before value computation of arr[arr[i]]
    • Value computation of arr[arr[i]] is sequenced before value computation of ++arr[arr[i]]
  2. The side-effect of ++arr[arr[i]] is unsequenced with respect to these.
  3. The compiler may choose any order satisfying these relations and may delete the value computation of arr[arr[i]] since it is not used.
  4. In any possible ordering, the value computation of arr[arr[i]] refers to the same scalar object as arr[i].
  5. The side effect of ++arr[arr[i]] modifies the scalar object arr[arr[i]], but it is accessed unsequenced to that by arr[i].

However, if we use postincrement instead, we introduce a new sequenced-before relation: The value computation of arr[arr[i]]++ is sequenced before its side-effect. Therefore, by transitivity, the side-effect is no longer unsequenced to arr[i].

However, I am unsure if this is accurate. In particular, I am not sure how exactly evaluation of post-/preincrement is defined. Does it perform a value computation of its operand? If it does, does this mean that ++*ptr is UB while (*ptr)++ is not? If it does not, how is the value computation of the full expression performed—can any operator access the value of an lvalue expression without performing value computation on that expression?


Solution

  • The analysis is incorrect. In particular, it is not the case that the side-effect of ++arr[arr[i]] is unsequenced to the other value computations. This is because the C standard (Note: I have only read a draft of the C standard, N3096) specifies that ++E is equivalent to E += 1:

    [6.5.16]
    The expression ++E is equivalent to (E+=1), where the value 1 is of the appropriate type.

    Further, it specifies that assignment operators incl. augmented assignment operators have their constituent expressions’ value computations sequenced before their side effect:

    [6.5.3.1]
    The side effect of updating the stored value of the left operand is sequenced after the value computations of the left and right operands.