Categories
c increment operator-precedence sequence-points undefined-behavior

Why are these constructs using pre and post-increment undefined behavior?

884

#include <stdio.h>

int main(void)
{
   int i = 0;
   i = i++ + ++i;
   printf("%d\n", i); // 3

   i = 1;
   i = (i++);
   printf("%d\n", i); // 2 Should be 1, no ?

   volatile int u = 0;
   u = u++ + ++u;
   printf("%d\n", u); // 1

   u = 1;
   u = (u++);
   printf("%d\n", u); // 2 Should also be one, no ?

   register int v = 0;
   v = v++ + ++v;
   printf("%d\n", v); // 3 (Should be the same as u ?)

   int w = 0;
   printf("%d %d\n", ++w, w); // shouldn't this print 1 1

   int x[2] = { 5, 8 }, y = 0;
   x[y] = y ++;
   printf("%d %d\n", x[0], x[1]); // shouldn't this print 0 8? or 5 0?
}

18

  • 16

    @Jarett, nope, just needed some pointers to “sequence points”. While working I found a piece of code with i = i++, I thougth “This isn’t modifying the value of i”. I tested and I wondered why. Since, i’ve removed this statment and replaced it by i++;

    – PiX

    Jun 4, 2009 at 18:24

  • 223

    I think it’s interesting that everyone ALWAYS assumes that questions like this are asked because the asker wants to USE the construct in question. My first assumption was that PiX knows that these are bad, but is curious why the behave they way the do on whataver compiler s/he was using… And yeah, what unWind said… it’s undefined, it could do anything… including JCF (Jump and Catch Fire)

    May 24, 2010 at 13:41

  • 37

    I’m curious: Why don’t compilers seem to warn on constructs such as “u = u++ + ++u;” if the result is undefined?

    Sep 20, 2012 at 16:23

  • 5

    (i++) still evaluates to 1, regardless of parentheses

    Jul 19, 2013 at 14:55

  • 3

    Whatever i = (i++); was intended to do, there is certainly a clearer way to write it. That would be true even if it were well defined. Even in Java, which defines the behavior of i = (i++);, it’s still bad code. Just write i++;

    Aug 15, 2013 at 21:07

612

+500

C has the concept of undefined behavior, i.e. some language constructs are syntactically valid but you can’t predict the behavior when the code is run.

As far as I know, the standard doesn’t explicitly say why the concept of undefined behavior exists. In my mind, it’s simply because the language designers wanted there to be some leeway in the semantics, instead of i.e. requiring that all implementations handle integer overflow in the exact same way, which would very likely impose serious performance costs, they just left the behavior undefined so that if you write code that causes integer overflow, anything can happen.

So, with that in mind, why are these “issues”? The language clearly says that certain things lead to undefined behavior. There is no problem, there is no “should” involved. If the undefined behavior changes when one of the involved variables is declared volatile, that doesn’t prove or change anything. It is undefined; you cannot reason about the behavior.

Your most interesting-looking example, the one with

u = (u++);

is a text-book example of undefined behavior (see Wikipedia’s entry on sequence points).

23

  • 9

    @PiX: Things are undefined for a number of possible reasons. These include: there is no clear “right result”, different machine architectures would strongly favour different results, existing practice is not consistent, or beyond the scope of the standard (e.g. what filenames are valid).

    – Richard

    Jun 4, 2009 at 10:57

  • 6

    Just to confuse everyone, some such examples are now well-defined in C11, e.g. i = ++i + 1; .

    – M.M

    Jul 10, 2014 at 5:51

  • 3

    Reading the Standard and the published rationale, It’s clear why the concept of UB exists. The Standard was never intended to fully describe everything a C implementation must do to be suitable for any particular purpose (see the discussion of the “One Program” rule), but instead relies upon implementors’ judgment and desire to produce useful quality implementations. A quality implementation suitable for low-level systems programming will need to define the behavior of actions that wouldn’t be needed in high-end number crunching.applications. Rather than try to complicate the Standard…

    – supercat

    Dec 17, 2017 at 23:12

  • 5

    …by getting into extreme detail about which corner cases are or are not defined, the authors of the Standard recognized that implementors should be better paced to judge which kinds of behaviors will be needed by the kinds of programs they’re expected to support. Hyper-modernist compilers pretend that making certain actions UB was intended to imply that no quality program should need them, but the Standard and rationale are inconsistent with such a supposed intent.

    – supercat

    Dec 17, 2017 at 23:15

  • 3

    @jrh: I wrote that answer before I’d realized how out of hand the hyper-modernist philosophy had gotten. What irks me is the progression from “We don’t need to officially recognize this behavior because the platforms where it’s needed can support it anyway” to “We can remove this behavior without providing a usable replacement because it was never recognized and thus any code needing it was broken”. Many behaviors should have been deprecated long ago in favor of replacements that were in every way better, but that would have required acknowledging their legitimacy.

    – supercat

    Jan 3, 2018 at 18:08

88

Most of the answers here quoted from C standard emphasizing that the behavior of these constructs are undefined. To understand why the behavior of these constructs are undefined, let’s understand these terms first in the light of C11 standard:

Sequenced: (5.1.2.3)

Given any two evaluations A and B, if A is sequenced before B, then the execution of A shall precede the execution of B.

Unsequenced:

If A is not sequenced before or after B, then A and B are unsequenced.

Evaluations can be one of two things:

  • value computations, which work out the result of an expression; and
  • side effects, which are modifications of objects.

Sequence Point:

The presence of a sequence point between the evaluation of expressions A and B implies that every value computation and side effect associated with A is sequenced before every value computation and side effect associated with B.

Now coming to the question, for the expressions like

int i = 1;
i = i++;

standard says that:

6.5 Expressions:

If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined. […]

Therefore, the above expression invokes UB because two side effects on the same object i is unsequenced relative to each other. That means it is not sequenced whether the side effect by assignment to i will be done before or after the side effect by ++.
Depending on whether assignment occurs before or after the increment, different results will be produced and that’s the one of the case of undefined behavior.

Lets rename the i at left of assignment be il and at the right of assignment (in the expression i++) be ir, then the expression be like

il = ir++     // Note that suffix l and r are used for the sake of clarity.
              // Both il and ir represents the same object.  

An important point regarding Postfix ++ operator is that:

just because the ++ comes after the variable does not mean that the increment happens late. The increment can happen as early as the compiler likes as long as the compiler ensures that the original value is used.

It means the expression il = ir++ could be evaluated either as

temp = ir;      // i = 1
ir = ir + 1;    // i = 2   side effect by ++ before assignment
il = temp;      // i = 1   result is 1  

or

temp = ir;      // i = 1
il = temp;      // i = 1   side effect by assignment before ++
ir = ir + 1;    // i = 2   result is 2  

resulting in two different results 1 and 2 which depends on the sequence of side effects by assignment and ++ and hence invokes UB.

0

    80

    I think the relevant parts of the C99 standard are 6.5 Expressions, §2

    Between the previous and next sequence point an object shall have its stored value
    modified at most once by the evaluation of an expression. Furthermore, the prior value
    shall be read only to determine the value to be stored.

    and 6.5.16 Assignment operators, §4:

    The order of evaluation of the operands is unspecified. If an attempt is made to modify
    the result of an assignment operator or to access it after the next sequence point, the
    behavior is undefined.

    9

    • 2

      Would the above imply that ‘i=i=5;” would be Undefined Behavior?

      – supercat

      Nov 20, 2011 at 21:41

    • 2

      @supercat as far as I know i=i=5 is also undefined behavior

      – dhein

      Sep 23, 2013 at 15:39

    • 3

      @Zaibis: The rationale I like to use for most places rule applies that in theory a mutli-processor platform could implement something like A=B=5; as “Write-lock A; Write-Lock B; Store 5 to A; store 5 to B; Unlock B; Unock A;”, and a statement like C=A+B; as “Read-lock A; Read-lock B; Compute A+B; Unlock A and B; Write-lock C; Store result; Unlock C;”. That would ensure that if one thread did A=B=5; while another did C=A+B; the latter thread would either see both writes as having taken place or neither. Potentially a useful guarantee. If one thread did I=I=5;, however, …

      – supercat

      Sep 23, 2013 at 16:18

    • 2

      … and the compiler didn’t notice that both writes were to the same location (if one or both lvalues involve pointers, that may be hard to determine), the generated code could deadlock. I don’t think any real-world implementations implement such locking as part of their normal behavior, but it would be permissible under the standard, and if hardware could implement such behaviors cheaply it might be useful. On today’s hardware such behavior would be way too expensive to implement as a default, but that doesn’t mean it would always be thus.

      – supercat

      Sep 23, 2013 at 16:19


    • 1

      @supercat but wouldn’t the sequence point access rule of c99 alone be enough to declare it as undefined behavior? So it doesn’t matter what technically the hardware could implement?

      – dhein

      Sep 23, 2013 at 16:40