I was trying to explain to someone why the following code produces UB:
int i=1;
printf("%d %d %d", i++, i++, i++);
I said that if there was a sequence point between evaluating the
parameters to a function, that it would be legal. And I wanted to give
this example as legal code:
int i=1;
printf("%d %d %d", (0, i++), (0, i++), (0, i++));
since a comma generates a sequence point.
Am I right that this code is legal? And what might the output be?
Thanks for indulging my pedantism, fellow pedants.
-Peter
> I was trying to explain to someone why the following code produces UB:
> int i=1;
> printf("%d %d %d", i++, i++, i++);
> I said that if there was a sequence point between evaluating the
> parameters to a function, that it would be legal.
Yes, this is correct. Different parameters inside the same function
call do not constitute sequence points.
> And I wanted to give
> this example as legal code:
> int i=1;
> printf("%d %d %d", (0, i++), (0, i++), (0, i++));
> since a comma generates a sequence point.
> Am I right that this code is legal? And what might the output be?
Yes, you are correct. The comma is a sequence point.
But... this is still un*specified* behaviour, even though it isn't
un*defined* behaviour.
All of the parameters (0, i++) must evaluate by evaluating 0 first
and i++ second, and the incrementing of i must occur before another
parameter can be evaluated. What is not set in stone is the order in
which the parameters are evaluated. Thus there are six possible
outputs:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
> Thanks for indulging my pedantism, fellow pedants.
Pedantry is always good on comp.lang.c.
--
/-- Joona Palaste (pal...@cc.helsinki.fi) ---------------------------\
| Kingpriest of "The Flying Lemon Tree" G++ FR FW+ M- #108 D+ ADA N+++|
| http://www.helsinki.fi/~palaste W++ B OP+ |
\----------------------------------------- Finland rules! ------------/
"I will never display my bum in public again."
- Homer Simpson
It only guarantees the side-effects of the evaluation of 0! [I.e. nothing!]
A more subtle example might be...
printf("%d %d %d\n", (i++, 0), (i++, 0), (i++, 0));
But even this invokes undefined behaviour because of two clauses within the
standard; [quoting from N869] firstly, the one we all know about:
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 accessed only to determine the value to be
stored.
And secondly:
6.5.2.2 Function calls
...
[#10] The order of evaluation of the function designator,
the actual arguments, and subexpressions within the actual
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
arguments is unspecified, but there is a sequence point
^^^^^^^^^^^^^^^^^^^^^^^^
before the actual call.
The underlined section effectively means that there is no prescribed
ordering of the multiple sequence points that may occur in the evaluation of
multiple arguments.
Since neither example above can guarantee the a sequence point /between/ the
evaluation of the i++ sub-expressions, they invoke undefined behaviour.
--
Peter
> 6.5 Expressions
> And secondly:
How about this?
printf("%d %d %d", (0, i++, 0), (0, i++, 0), (0, i++, 0));
This should increment i by 3 and print out the following:
0 0 0
If you want to display the value of i rather than 0, you can do it
this way:
printf("%d %d %d", (0, i++, i), (0, i++, i), (0, i++, i));
The two comma operators around each i++ forbid the side effect from
taking place outside the individual parameter, so it is completely
harmless. The only thing that can vary in this case is the order in
which 1, 2 and 3 are printed.
--
/-- Joona Palaste (pal...@cc.helsinki.fi) ---------------------------\
| Kingpriest of "The Flying Lemon Tree" G++ FR FW+ M- #108 D+ ADA N+++|
| http://www.helsinki.fi/~palaste W++ B OP+ |
\----------------------------------------- Finland rules! ------------/
"You will be given the plague."
- Montgomery Burns
1 1 1 is also possible.
Where is the sequence point between (0, i++) and the next (0, i++)?
These two expressions can be evaluated in parallel. So what will
happen? It still smells of undefined behaviour.
Jirka
No. Why do you think so? The compiler is allowed to evaluate the (0,i++,i)'s
in any order, even in parallel. What value will i have then? 4 ist a possibility.
(42 is another one, but it is unlikely IMO.)
Jirka
Well spotted. See my other reply in this thread for a more robust
version.
--
/-- Joona Palaste (pal...@cc.helsinki.fi) ---------------------------\
| Kingpriest of "The Flying Lemon Tree" G++ FR FW+ M- #108 D+ ADA N+++|
| http://www.helsinki.fi/~palaste W++ B OP+ |
\----------------------------------------- Finland rules! ------------/
"A bee could, in effect, gather its junk. Llamas (no poor quadripeds) tune
and vow excitedly zooming."
- JIPsoft
This is getting really hairy. There are sequence points _inside_ the
parameters but not _between_ them. So my ubiquitous commas only
guarantee that each 0 must be evaluated before its "own" i++, and
each i++ must be evaluated before its "own" i? There is nothing
preventing somebody "other's" i++ from being evaluated in between?
So is the only way to make this safe something like this:
#include <stdio.h>
int increment(int *i) {
return ++(*i);
}
int main(void) {
int i=0;
printf("%d %d %d", increment(&i), increment(&i), increment(&i));
return 0;
}
--
/-- Joona Palaste (pal...@cc.helsinki.fi) ---------------------------\
| Kingpriest of "The Flying Lemon Tree" G++ FR FW+ M- #108 D+ ADA N+++|
| http://www.helsinki.fi/~palaste W++ B OP+ |
\----------------------------------------- Finland rules! ------------/
"Keep shooting, sooner or later you're bound to hit something."
- Misfire
Methinks we finally got the unspecified instead of undefined behaviour. :-)
Jirka
Agreed. The abstract machine is guaranteed to execute function calls
sequentially (i.e. no overlap or intermixing is possible between
the execution of two functions), but the order of evaluation of the
arguments is still unspecified, so the output is unspecified, but it
can only be a permutation of 1 2 3.
Dan
--
Dan Pop
DESY Zeuthen, RZ group
Email: Dan...@ifh.de
Thanks for your enlightening response. I'd like to clarify a point in
this last section, though. I first interpreted "the order of evaluation
of...subexpressions within the actual arguments is unspecified" to mean
that all the usual rules fly out the window; for example, that
someEmptyFunction((puts("Hello"), puts("World")));
might output
World
Hello
But now I interpret it to mean that the implementation is free to mix
and match subexpressions as long as it follows the required ordering on
a per-parameter basis. E.g. for
func((1,2), (3,4));
the parameters can be evaluated in any order as long as 1 appears before
2 and 3 appears before 4.
Am I correct that the intent of the standard is the second
interpretation? The wording of this section seems ambiguous.
-Peter
>Thanks for your enlightening response. I'd like to clarify a point in
>this last section, though. I first interpreted "the order of evaluation
>of...subexpressions within the actual arguments is unspecified" to mean
>that all the usual rules fly out the window; for example, that
>
>someEmptyFunction((puts("Hello"), puts("World")));
>
>might output
>
>World
>Hello
This is definitely not an option for a conforming implementation.
>But now I interpret it to mean that the implementation is free to mix
>and match subexpressions as long as it follows the required ordering on
>a per-parameter basis. E.g. for
>
>func((1,2), (3,4));
>
>the parameters can be evaluated in any order as long as 1 appears before
>2 and 3 appears before 4.
>
>Am I correct that the intent of the standard is the second
>interpretation? The wording of this section seems ambiguous.
To make things more explicit, in the case of:
func((expr1, expr2), (expr3, expr4));
you are guaranteed that expr1 is completely evaluated before the
evaluation of expr2 starts and that expr3 is completely evaluated before
the evaluation of expr4 starts. There are NO other guarantees about
the order of evaluation of these 4 expressions. E.g. the evaluation
of the first two expressions can be overlapped/intermixed with the
evaluatation of the last two expressions, or the expressions are evaluated
sequentially, in the order: expr1, expr3, expr4, expr2.
>>Thanks for your enlightening response. I'd like to clarify a point in
>>this last section, though. I first interpreted "the order of evaluation
>>of...subexpressions within the actual arguments is unspecified" to mean
>>that all the usual rules fly out the window; for example, that
>>
>>someEmptyFunction((puts("Hello"), puts("World")));
>>
>>might output
>>
>>World
>>Hello
> This is definitely not an option for a conforming implementation.
This is sneaky, since:
someEmptyFunction(puts("Hello"), puts("World"));
can indeed result in the above output, but adding an extra layer of
parantheses:
someEmptyFunction((puts("Hello"), puts("World")));
makes this impossible.
>>But now I interpret it to mean that the implementation is free to mix
>>and match subexpressions as long as it follows the required ordering on
>>a per-parameter basis. E.g. for
>>
>>func((1,2), (3,4));
>>
>>the parameters can be evaluated in any order as long as 1 appears before
>>2 and 3 appears before 4.
>>
>>Am I correct that the intent of the standard is the second
>>interpretation? The wording of this section seems ambiguous.
I guess this is correct, all it guarantees is that 1 will be fully
evaluated before 2 starts to be evaluated, and 3 will be fully evaluated
before 4 starts to be evaluated.
> To make things more explicit, in the case of:
> func((expr1, expr2), (expr3, expr4));
> you are guaranteed that expr1 is completely evaluated before the
> evaluation of expr2 starts and that expr3 is completely evaluated before
> the evaluation of expr4 starts. There are NO other guarantees about
> the order of evaluation of these 4 expressions. E.g. the evaluation
> of the first two expressions can be overlapped/intermixed with the
> evaluatation of the last two expressions, or the expressions are evaluated
> sequentially, in the order: expr1, expr3, expr4, expr2.
OK, this is exactly as I thought.
But what about this?
#include <stdio.h>
int function(int i) {
return i;
}
int main(void) {
int i=0;
printf("%d %d %d"), function(i++), function(i++), function(i++));
return 0;
}
Is this undefined or unspecified behaviour?
--
/-- Joona Palaste (pal...@cc.helsinki.fi) ---------------------------\
| Kingpriest of "The Flying Lemon Tree" G++ FR FW+ M- #108 D+ ADA N+++|
| http://www.helsinki.fi/~palaste W++ B OP+ |
\----------------------------------------- Finland rules! ------------/
"Life without ostriches is like coffee with milk."
- Mika P. Nieminen
>But what about this?
>
>#include <stdio.h>
>int function(int i) {
> return i;
>}
>int main(void) {
> int i=0;
> printf("%d %d %d"), function(i++), function(i++), function(i++));
^
Bzzzt!
> return 0;
>}
>
>Is this undefined or unspecified behaviour?
It's code requiring a diagnostic, because the parentheses are unbalanced!
After "fixing" it, we get undefined behaviour: there is a sequence
point before each call of function(), but there is still nothing
preventing the compiler from using this evaluation order:
1. i++
2. i++
3. i++
4. sequence point
5. function() is called
6. function() is called
7. function() is called
8. printf() is called
Note that just adding the extra parens is not sufficient, you also have to
modify the function (and its prototype) to take one arg instead of two.
There must be some Obfuscated C entries that do this kind of thing:
f((this, function, looks, like, it, takes, a, lot, of, args, but,
looks, can, be, deceiving));
--Ben
--
Probably. The syntax has a more practical use, however:
#ifndef NDEBUG
#define DEBUG(x) printf x
#else
#define DEBUG(ignore)
#endif
#include <stdio.h>
int main(void)
{
int i;
int sum = 0;
for(i = 1; i <= 10; i++)
{
DEBUG(("Adding %d to %d making %d so far\n",
i,
sum,
i + sum)); /* [1] */
sum += i;
}
printf("S(%d) = %d\n", --i, sum);
return 0;
}
[1] If you prefer, you can put the ; inside the )) like this );)
(Exercise for occasional readers: why might you want to?)
--
Richard Heathfield : bin...@eton.powernet.co.uk
"Usenet is a strange place." - Dennis M Ritchie, 29 July 1999.
C FAQ: http://www.eskimo.com/~scs/C-faq/top.html
K&R answers, C books, etc: http://users.powernet.co.uk/eton
>Peter Nilsson wrote:
> >
>Thanks for your enlightening response. I'd like to clarify a point in
>this last section, though. I first interpreted "the order of evaluation
>of...subexpressions within the actual arguments is unspecified" to mean
>that all the usual rules fly out the window; for example, that
>
>someEmptyFunction((puts("Hello"), puts("World")));
>
>might output
>
>World
>Hello
>
>But now I interpret it to mean that the implementation is free to mix
>and match subexpressions as long as it follows the required ordering on
>a per-parameter basis. E.g. for
>
>func((1,2), (3,4));
>
>the parameters can be evaluated in any order as long as 1 appears before
>2 and 3 appears before 4.
>
>Am I correct that the intent of the standard is the second
>interpretation? The wording of this section seems ambiguous.
I believe that this is wrong. The comma separating function parameters
is not a sequence point, so the compiler can evaluate them in any
order. Therefore it could evaluate puts("world") first, as you
originally said.
--
Mark McIntyre
CLC FAQ <http://www.eskimo.com/~scs/C-faq/top.html>
CLC readme: <http://www.angelfire.com/ms3/bchambless0/welcome_to_clc.html>
> I believe that this is wrong. The comma separating function parameters
> is not a sequence point, so the compiler can evaluate them in any
> order. Therefore it could evaluate puts("world") first, as you
> originally said.
Please read the function call again, carefully this time.
--
/-- Joona Palaste (pal...@cc.helsinki.fi) ---------------------------\
| Kingpriest of "The Flying Lemon Tree" G++ FR FW+ M- #108 D+ ADA N+++|
| http://www.helsinki.fi/~palaste W++ B OP+ |
\----------------------------------------- Finland rules! ------------/
"It's not survival of the fattest, it's survival of the fittest."
- Ludvig von Drake
> Note that just adding the extra parens is not sufficient, you also have to
> modify the function (and its prototype) to take one arg instead of
two.
But what if it's a varargs function whose first argument happens to be
of the same type as puts()'s return type?
> There must be some Obfuscated C entries that do this kind of thing:
> f((this, function, looks, like, it, takes, a, lot, of, args, but,
> looks, can, be, deceiving));
I can do even better.
#define foo(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) \
bar((a, b, c, d, e, f, g, h, i, j, k, l, m, n, o))
foo(this, function, looks, like, it, takes, a lot, of, args, but,
looks, can, be, deceiving);
Shoot, for that matter, why not this?
#define foo(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) bar(o)
foo(this, function, looks, like, it, takes, a lot, of, args, but,
looks, can, be, deceiving);
Maybe it's not such a clever technique after all.
--
/-- Joona Palaste (pal...@cc.helsinki.fi) ---------------------------\
| Kingpriest of "The Flying Lemon Tree" G++ FR FW+ M- #108 D+ ADA N+++|
| http://www.helsinki.fi/~palaste W++ B OP+ |
\----------------------------------------- Finland rules! ------------/
"And according to Occam's Toothbrush, we only need to optimise the most frequent
instructions."
- Teemu Kerola
>Mark McIntyre <markmc...@spamcop.net> scribbled the following:
>> On Tue, 10 Sep 2002 08:07:18 -0400, in comp.lang.c , Peter Ammon
>> <pa...@cornell.edu> wrote:
>>>Peter Nilsson wrote:
>>> >
>>>Thanks for your enlightening response. I'd like to clarify a point in
>>>this last section, though. I first interpreted "the order of evaluation
>>>of...subexpressions within the actual arguments is unspecified" to mean
>>>that all the usual rules fly out the window; for example, that
>>>
>>>someEmptyFunction((puts("Hello"), puts("World")));
>>>
>>>might output
>>>
>>>World
>>>Hello
>
>> I believe that this is wrong. The comma separating function parameters
>> is not a sequence point, so the compiler can evaluate them in any
>> order. Therefore it could evaluate puts("world") first, as you
>> originally said.
>
>Please read the function call again, carefully this time.
It won't help: the double parenthesis following the function name was
hard to miss. It is its significance, in context, that he missed.
>Mark McIntyre <markmc...@spamcop.net> scribbled the following:
>> On Tue, 10 Sep 2002 08:07:18 -0400, in comp.lang.c , Peter Ammon
>> <pa...@cornell.edu> wrote:
>>>Peter Nilsson wrote:
>>> >
>>>Thanks for your enlightening response. I'd like to clarify a point in
>>>this last section, though. I first interpreted "the order of evaluation
>>>of...subexpressions within the actual arguments is unspecified" to mean
>>>that all the usual rules fly out the window; for example, that
>>>
>>>someEmptyFunction((puts("Hello"), puts("World")));
>>>
>>>might output
>>>
>>>World
>>>Hello
>
>> I believe that this is wrong. The comma separating function parameters
>> is not a sequence point, so the compiler can evaluate them in any
>> order. Therefore it could evaluate puts("world") first, as you
>> originally said.
>
>Please read the function call again, carefully this time.
oooo yes, the extra parens. mea culpa.
> oooo yes, the extra parens. mea culpa.
So, Dan Pop, you see now? =)
--
/-- Joona Palaste (pal...@cc.helsinki.fi) ---------------------------\
| Kingpriest of "The Flying Lemon Tree" G++ FR FW+ M- #108 D+ ADA N+++|
| http://www.helsinki.fi/~palaste W++ B OP+ |
\----------------------------------------- Finland rules! ------------/
"'It can be easily shown that' means 'I saw a proof of this once (which I didn't
understand) which I can no longer remember'."
- A maths teacher
>Mark McIntyre <markmc...@spamcop.net> scribbled the following:
>> On 11 Sep 2002 05:21:49 GMT, in comp.lang.c , Joona I Palaste
>> <pal...@cc.helsinki.fi> wrote:
>>>Mark McIntyre <markmc...@spamcop.net> scribbled the following:
>>>> I believe that this is wrong. The comma separating function parameters
>>>> is not a sequence point, so the compiler can evaluate them in any
>>>> order. Therefore it could evaluate puts("world") first, as you
>>>> originally said.
>>>
>>>Please read the function call again, carefully this time.
>
>> oooo yes, the extra parens. mea culpa.
>
>So, Dan Pop, you see now? =)
It was actually my previous post that pointed him in the right direction.
Dan :-)