Since this question has been asked due to discussion based on comments here , I will provide some context:
first comment: the order of operations is not guaranteed to be the order in which you pass arguments to a function. Some people (by mistake) assume that the arguments will be evaluated from right to left, but according to the standard, the behavior is undefined.
The OP accepts and understands this. It makes no sense to repeat the fact that your_function(++i, ++i) is UB.
In response to this comment: thanks to your comment, I see that printf can be evaluated in any order, but I realized that this is because the printf arguments are part of va_list . Are you saying that the arguments of any function are executed in random order?
OP asks for clarification, so I clarified a bit:
Second comment: Yes, thatβs exactly what Iβm saying. even calling int your_function(int a, int b) { return a - b; } int your_function(int a, int b) { return a - b; } does not guarantee that the expressions you pass will be evaluated from left to right. There is no point in the sequence (the point at which all side effects of previous evaluations are performed). Take this example . The nested call is the sequence point, so the external call passes i+1 (13), and the return value of the internal call (undefined, in this case -1, because i++ , i evaluates to 12, 13, obviously) , but there is no guarantee, that this will always be the case
This made it pretty clear that these types of constructs launch UB for all functions.
Wikipedia Concern
OP Quotes this:
After the action associated with the input / output conversion format specifier. For example, in the expression printf ("foo% n% d", & a, 42), there is a sequence point after evaluating% n before printing 42.
Then it applies it to his fragment ( prinf("%d - %d - %d\n", i, your_function(++i, ++i), i); ), chasing format specifiers to serve as sequence points.
What the "I / O format specifier" refers to 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 so far. Naturally, %n must be evaluated before the rest of the arguments are printed. However, using the pointer passed to %n in other arguments is still dangerous: it is not UB (well, it is not, but it may be):
printf("Foo %n %*s\n", &a, 100-a, "Bar");//DANGER!!
There is a sequence point before the function call, so the expression 100-a will be evaluated before %n sets &a to the correct value. If a initialized, then 100-a is UB. If a initialized to 0, for example, the result of the expression will be 100. In general, however, this kind of code requires a lot of trouble. See it as a very bad practice, or worse ...
Just look at the result generated by one of the following 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
As you can see, n reassigned inside printf , so you cannot use its new value in the argument list (because there is a sequence point). If you expect n be reassigned βin place,β you essentially expect C to jump out of the function call, evaluate the other arguments, and return back to the call. It is simply not possible. If you were to change unsigned int a = 90; on unsigned int a; , then the behavior is undefined.
As for the 12 '
Now, when the OP reads the points of the sequence, it correctly notices that this statement:
printf("%d - %d - %d\n", i, your_function(++i, ++i), i);
A little different: your_function(++i, ++i) is a point in the sequence, but ensures that i will double up. This function call is a sequence point because:
Before a function is introduced into a function call. The order in which the arguments are evaluated is not specified, but this point in the sequence means that all of their side effects are complete before the function is entered
This means that before calling printf your_function must be called (since its return value is one of the arguments for calling printf ) and i will double. This one could explain that the result is "12 - 0 - 12", but is this a guaranteed way out?
No
Technically, although most compilers will first appreciate the call to your_function(++i, ++i); , the standard will allow the compiler to evaluate the arguments passed to sprintf from left to right (order is not specified after all). Thus, this will be an equal result:
10 - 0 - 12 //or even 12 - 0 - 10 //and 10 - 0 - 10 //technically, even this would be valid 12 - 0 - 11
Although the last exit is extremely unlikely (it would be very inefficient)