21

Related question: Any good reason why assignment operator isn't a sequence point?

From the comp.lang.c FAQ I would infer that the program below is undefined. Strangely, it only mentions the call to f as a sequence point, between the computation of the arguments and the transfer of control to f. The transfer of control from f back to the calling expression is not listed as a sequence point.

int f(void) { i++; return 42; }
i = f();

Is it really undefined?

As an end-note that I add to many of my questions, I am interested in this in the context of static analysis. I am not writing this myself, I just want to know if I should warn about it in programs written by others.

Community
  • 1
  • 1
Pascal Cuoq
  • 79,187
  • 7
  • 161
  • 281
  • 19
    To whomever it may concern, "You could always try it" is never a correct answer to "Is X an undefined behavior?". – Pascal Cuoq May 19 '11 at 07:35
  • +1, but also note that UB isn't necessarily bad. C doesn't forbid the use of UB, it merely says "I don't know what will happen. Hope you do." – Philip May 19 '11 at 11:33
  • 5
    UB is bad. Unlike implementation-defined behavior, the intended usage for compiler authors is that the behavior can and will change even between different releases of the same compiler, according to whatever best serves optimization of valid code. – R.. GitHub STOP HELPING ICE May 19 '11 at 11:40

2 Answers2

9

The transfer of control from f back to the calling expression is not listed as a sequence point.

Yes it is.

at the end of the evaluation of a full expression

 

The complete expression that forms an expression statement, or one of the controlling expressions of an if, switch, while, for, or do/while statement, or the expression in an initializer or a return statement.

You have a return statement, therefore, you have a sequence point.

It doesn't even appear that

int f(void) { return i++; } // sequence point here, so I guess we're good
i = f();

is undefined. (Which to me is kind of weird.)

David Titarenco
  • 32,662
  • 13
  • 66
  • 111
  • 2
    So, what happens if the compiler tries to inline this? Edit: Everything breaks, good to know... – James Greenhalgh May 19 '11 at 07:49
  • Oh, I see why, (return i++) evaluates to i as expected then increments i, end of sequence point. Then assign old i to i, end of sequence point. – James Greenhalgh May 19 '11 at 07:55
  • 10
    @James Greenhalgh: Nothing breaks. Inlining changes absolutely nothing with regard to sequence points. "Sequence point" is a concept, not a physical entity somehow associated with a physical function body. The code is OK in any case, since each function has a sequence point at the entrance and at the end, regardless of whether it is inlined or not. – AnT stands with Russia May 19 '11 at 08:13
  • 1
    Another way of saying this is that behavior of a program compiled with and without inlining must be identical unless the program has invoked undefined behavior. – R.. GitHub STOP HELPING ICE May 19 '11 at 11:42
8

That's not undefined at all. One of the sequence points listed in Appendix C of C99 is the end of a full expression, of which one is the expression in a return statement.

Since you're returning 42, there's a sequence point immediately following that return statement.

For completeness, the C99 sequence points are listed here, with the relevant one bolded:

The following are the sequence points described in 5.1.2.3:


  • The call to a function, after the arguments have been evaluated (6.5.2.2).
  • The end of the first operand of the following operators: logical AND && (6.5.13); logical OR || (6.5.14); conditional ? (6.5.15); comma , (6.5.17).
  • The end of a full declarator: declarators (6.7.5);
  • The end of a full expression: an initializer (6.7.8); the expression in an expression statement (6.8.3); the controlling expression of a selection statement (if or switch) (6.8.4); the controlling expression of a while or do statement (6.8.5); each of the expressions of a for statement (6.8.5.3); the expression in a return statement (6.8.6.4).
  • Immediately before a library function returns (7.1.4).
  • After the actions associated with each formatted input/output function conversion specifier (7.19.6, 7.24.2).
  • Immediately before and immediately after each call to a comparison function, and also between any call to a comparison function and any movement of the objects passed as arguments to that call (7.20.5).
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • Are you sure your example is UB without the function? I think it's perfectly well-defined. The object is accessed (read) only once, and it is for the purpose of determining the new value to be assigned to it. I don't see anything that would invoke UB. – R.. GitHub STOP HELPING ICE May 19 '11 at 11:44
  • Ah, I see that it's from the standard, but I still can't find any justification for why it would be UB... – R.. GitHub STOP HELPING ICE May 19 '11 at 11:47
  • 1
    @R. Is it because g is a union? g.u2.f3 = g.u1.f2 certainly looks like it would invoke undefined behvaiour. – James Greenhalgh May 19 '11 at 13:47
  • "Looks like" in what way? UB needs a reason to be UB, not just that it "looks suspicious". – R.. GitHub STOP HELPING ICE May 19 '11 at 14:49
  • @R.. I'm coming late to the discussion about an example that has since been deleted, but `g.u2.f3 = g.u1.f2;` violates 6.5.16.1:3 "If the value being stored in an object is read from another object that overlaps in any way the storage of the first object, then the overlap shall be exact..." – Pascal Cuoq May 19 '11 at 16:10
  • If that's the only reason, then whether it's UB depends on the size and/or alignment of `int` and `double`. – R.. GitHub STOP HELPING ICE May 19 '11 at 16:15