Because this question was asked because of a comment-based discussion here, I'll provide some context:
first comment: The order of operations is not guaranteed to be the order in which you pass arguments to the function. Some people (wrongly) assume that the arguments will be evaluated right to left, but according to the standard, the behaviour is undefined.
The OP accepts and understands this. No point in repeating the fact that your_function(++i, ++i)
is UB.
In response to that comment: Thanks to your comment I see that printf
may be evaluated in any order, but I understood that to be because printf
arguments are part of a va_list
. Are you saying that the arguments to any function are executed in an arbitrary order?
OP asking for clarification, so I elaborated a bit:
Second comment: Yes, that's exactly what I'm saying. even calling int your_function(int a, int b) { return a - b; }
does not guarantee that the expressions you pass will be evaluated left to right. There's no sequence point (a point at which all side effects of previous evaluations are performed). Take this example. The nested call is a sequence point, so the outer call passes i+1
(13), and the return value of the inner call (undefined, in this case -1 because i++
, i
evaluates to 12, 13 apparently), but there's no guarantee that this will always be the case
That made it pretty clear that these kinds of constructs trigger UB for all functions.
Wikipedia confusion
OP Quotes this:
After the action associated with input/output conversion format specifier. For example, in the expression printf("foo %n %d", &a, 42), there is a sequence point after the %n is evaluated before printing 42.
Then applies it to his snippet (prinf("%d - %d - %d\n", i, your_function(++i, ++i), i);
) expeciting the format specifiers to serve as sequence points.
What is being referred to by saying "input/output conversion format specifier" is the %n
specifier. The corresponding argument must be a pointer to an unsigned integer, and it will be assigned the number of characters printed thus far. Naturally, %n
must be evaluated before the rest of the arguments are printed. However, using the pointer passed for %n
in other arguments is still dangerous: it's not UB (well, it isn't, but it can be):
printf("Foo %n %*s\n", &a, 100-a, "Bar");//DANGER!!
There is a sequence point before the function is called, so the expression 100-a
will be evaluated before %n
has set &a
to the correct value. If a
is uninitialized, then 100-a
is UB. If a
is initialized to 0, for example, the result of the expression will be 100. On the whole, though, this kind of code is pretty much asking for trouble. Treat it as very bad practice, or worse...
Just look at the output generated by either one of these statements:
unsigned int a = 90;
printf("%u %n %*s\n",a, &a, 10, "Bar");//90 Bar
printf("%u\n", a);//3
printf("Foo %u %n %*s\n",a, &a, 10-a, "Bar");//Foo 3 Bar < padding used: 10 - 3, not 10 - 6
printf("%u\n", a);//6
In as you can see, n
gets reassigned inside of printf
, so you can't use its new value in the argument list (because there's a sequence point). If you expect n
to be reassigned "in-place" you're essentially expecting C to jump out of the function call, evaluate other arguments, and jump back into the call. That's just not possible. If you were to change unsigned int a = 90;
to unsigned int a;
, then the behaviour is undefined.
Concerning the 12
's
Now because the OP read up on sequence points, he correctly notices that this statement:
printf("%d - %d - %d\n", i, your_function(++i, ++i), i);
Is slightly different: your_function(++i, ++i)
is a sequence point, and guarantees that i
will be incremented twice. This function call is a sequence point because:
Before a function is entered in a function call. The order in which the arguments are evaluated is not specified, but this sequence point means that all of their side effects are complete before the function is entered
That means that, before printf
is called, your_function
has to be called (because its return value is one of the arguments for the printf
call), and i
will be incremented twice.
This could explain the output being "12 - 0 - 12", but is it guaranteed to be the output?
No
Technically, although most compilers will evaluate the your_function(++i, ++i);
call first, the standard would allow a compiler to evaluate the arguments passed to sprintf
left to right (the order isn't specified after all). So this would be an equally valid result:
10 - 0 - 12
//or even
12 - 0 - 10
//and
10 - 0 - 10
//technically, even this would be valid
12 - 0 - 11
Although the latter output is extremely unlikely (it'd be very inefficient)