2

What does the rule about sequence points say about the following code?

int main(void) { int i = 5; printf("%d", ++i, i); /* Statement 1 */ } 

There is just one %d. I am confused because I am getting 6 as output in compilers GCC, Turbo C++ and Visual C++. Is the behavior well defined or what?

This is related to my last question.

6
  • 4
    Cripes I hope this kind of code doesn't live in the wild.. Commented Mar 22, 2011 at 11:13
  • Too localized. Voted to close. Commented Mar 22, 2011 at 11:15
  • 1
    Just because code invokes undefined behavior doesn't mean you won't get consistent results. Commented Mar 22, 2011 at 11:34
  • @jaya: Ok, I have to ask. Why have you accepted an incorrect answer? Commented Mar 22, 2011 at 11:38
  • @Space_C0wb0y : The accepted answer is no-way incorrect. Commented Mar 22, 2011 at 11:41

5 Answers 5

7

It's undefined because of 2 reasons:

  1. The value of i is twice used without an intervening sequence point (the comma in argument lists is not the comma operator and does not introduce a sequence point).

  2. You're calling a variadic function without a prototype in scope.

  3. The number of arguments passed to printf() are not compatible with the format string.

  4. the default output stream is usually line buffered. Without a '\n' there is no guarantee the output will be effectively output.

Sign up to request clarification or add additional context in comments.

4 Comments

3 is not a correct reason. You can have number of arguments greater than the number of format specifiers. BTW +1 for mentioning 2.
I'm not sure about 4, either. stdout is flushed at program exit. What the terminal does with output that isn't line-terminated is outside the scope of the standard, but it's all written out of the program regardless of how stdout is or isn't buffered.
Thanks @Steve. I tend to consider the lack of a terminating '\n' an error because some utilities I use do not consider characters after the last line break. That is a bug in the utilities, not in the C language :)
or possibly it's a bug in this program, since it doesn't write the output format that is expected by those utilities. Just not a UB bug.
6

All arguments get evaluated when calling a function, even if they are not used, so, since the order of evaluation of function arguments is undefined, you have UB again.

7 Comments

Technically, it isn't UB but unspecified behaviour. See last paragraph on the first page of ISO 9899:1999 Annex J.1.
@Lundin: I am not sure if it is the same in C++03 as in C99.
@Lundin: in C++ the order of evaluation of function arguments is unspecified, meaning that in this case the behavior is undefined because of 5/4 (last sentence). C isn't quite so explicit that if any permitted order breaks the rules then behavior is undefined, but in this case one order of evaluating the arguments breaks the "shall" rules of 6.5/2, and the other doesn't. Since you can't predict which order they'll be evaluated, it's unspecified whether behavior is defined or not, which isn't a good place to be.
@Steve Jessop: I think this is an interesting point for discussion, and I am not so sure. The order of execution is unspecified, but that only has two possible outcomes, either i or ++i is executed before the other. Because i has no side effects and is ignored by the function ("%d" will only use the first argument) then the output of the printf is necessarily fixed. I know I am treading a fine line here, and I am not really sure of this, but I don't think it is undefined.
@David Rodríguez - dribeas : After a bit of researching I've found an exact dupe: stackoverflow.com/questions/3450582/…
|
3

I think it's well defined. The printf matches the first % placeholder to the first argument, which in this instance is a preincremented variable.

8 Comments

It's undefined, because those arguments are still evaluated just like normal; the only difference is that printf won't use that value.
@DeadMG Doesn't that mean it's defined? As the second parameter doesn't modify the value and printf doesn't use the second parameter, it's value is irrelevant to the output / behaviour.
@forsvarir: Say i=5. If i evaluated first, then the function is called with 6,5 as arguments. If ++i is evaluated first, then the function is called with 6,6 as arguments. It does not make a difference here, but in other scenarios it might.
@Space_C0wb0y: I guess it depends how you define 'defined behaviour'. I agree that what gets passed to printf isn't defined and so it's something to be cautious of, however for the example given, the measurable behaviour is defined (it will always output 6 to stdout). But I'm willing to acknowledge I may have misinterpreted the question :)
The behavior is undefined. It has nothing to do with what gets passed to printf, or whether it is used or not. The standard says that behavior is undefined if you modify an object, and you access it elsewhere without an intervening sequence point, other that to determine the new value. Theoretically, at least, printf may never be called; the program could crash before that.
|
0

All arguments are evaluated. Order not defined. All implementations of C/C++ (that I know of) evaluate function arguments from right to left. Thus i is usually evaluated before ++i.

In printf, %d maps to the first argument. The rest are ignored.

So printing 6 is the correct behaviior.

I believe that the right-to-left evaluation order has been very very old (since the first C compilers). Certainly way before C++ was invented, and most implementations of C++ would be keeping the same evaluation order because early C++ implementations simply translates into C.

There are some technical reasons for evaluating function arguments right-to-left. In stack architectures, arguments are typically pushed onto the stack. In C, you can call a function with more arguments than actually specified -- the extra arguments are simiply ignored. If arguments are evaluated left-to-right, and pushed left-to-right, then the stack slot right under the stack pointer will hold the last argument, and there is no way for the function to get at the offset of any particular argument (because the actual number of arguments pushed depends on the caller).

In a right-to-left push order, the stack slot right under the stack pointer will always hold the first argument, and the next slot holds the second argument etc. Argument offsets will always be deterministic for the function (which may be written and compiled elsewhere into a library, separately from where it is called).

Now, right-to-left push order does not mandate right-to-left evaluation order, but in early compilers, memory is scarce. In right-to-left evaluation order, the same stack can be used in-place (essentially, after evaluating the argument -- which may be an expression or a funciton call! -- the return value is already at the right position on the stack). In left-to-right evaluation, the argument values must be stored separately and the pushed back to the stack in reverse order.

Would be interested to know the true history behind right-to-left evaluation though.

Comments

0

According to this documentation, any additional arguments passed to a format string shall be ignored. It also mentions for fprintf that the argument will be evaluated then ignored. I'm not sure if this is the case with printf.

1 Comment

Every argument get's evaluated /before/ the function call, and printf /then/ decides to ignore some of them. Nevertheless you delivered those parameters so you have undefined behaviour™.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.