Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Function call evaluation order

1 view
Skip to first unread message

Cheng

unread,
Mar 22, 2006, 8:30:15 PM3/22/06
to

Hi folks,

I need help to explain the behavior with the code below. I would like to
chain up function call into a single line, while the order of the function
call is correct, I cannot be certain with order the expression within the
function call is evaluated.

Woud it be better, then, to write all the function call into seperate lines?

With regards,
Cheng Wu


Code:

#include <stdio.h>
#include <string>

using namespace std;

void main()
{
char* data[] = {" zero ", " one ", " two ", " three "};
string s;
int i;

i = 0;
s.assign(data[i++]).append(data[i++]).append(data[i++]);
printf("%s\n", s.c_str());

i = 0;
s.assign(data[++i]).append(data[++i]).append(data[++i]);
printf("%s\n", s.c_str());

i = 0;
s.assign(data[i++]);
s.append(data[i++]);
s.append(data[i++]);
printf("%s\n", s.c_str());
}


Output:

zero zero zero
three three three
zero one two


Igor Tandetnik

unread,
Mar 22, 2006, 9:50:45 PM3/22/06
to
"Cheng" <cheng...@hotmail.com> wrote in message
news:u8X%23alhTG...@tk2msftngp13.phx.gbl

> I need help to explain the behavior with the code below. I would
> like to chain up function call into a single line, while the order of
> the function call is correct, I cannot be certain with order the
> expression within the function call is evaluated.
>
> Woud it be better, then, to write all the function call into seperate
> lines?
>
> s.assign(data[i++]).append(data[i++]).append(data[i++]);

Not only would it be better - you must do so. The statement shown above
exhibits undefined behavior. There exists a possible legal order of
execution of various subexpressions that results in the variable 'i'
being modified more than once between sequence points.
--
With best wishes,
Igor Tandetnik

With sufficient thrust, pigs fly just fine. However, this is not
necessarily a good idea. It is hard to be sure where they are going to
land, and it could be dangerous sitting under them as they fly
overhead. -- RFC 1925


Tim Roberts

unread,
Mar 23, 2006, 2:17:05 AM3/23/06
to
"Igor Tandetnik" <itand...@mvps.org> wrote:

>"Cheng" <cheng...@hotmail.com> wrote:
>> I need help to explain the behavior with the code below. I would
>> like to chain up function call into a single line, while the order of
>> the function call is correct, I cannot be certain with order the
>> expression within the function call is evaluated.
>>
>> Woud it be better, then, to write all the function call into seperate
>> lines?
>>
>> s.assign(data[i++]).append(data[i++]).append(data[i++]);
>
>Not only would it be better - you must do so. The statement shown above
>exhibits undefined behavior. There exists a possible legal order of
>execution of various subexpressions that results in the variable 'i'
>being modified more than once between sequence points.

Is it? I mean, I shudder in horror at that line of code, but I thought a
function call represented a sequence point. Not so?
--
- Tim Roberts, ti...@probo.com
Providenza & Boekelheide, Inc.

Ulrich Eckhardt

unread,
Mar 23, 2006, 3:54:27 AM3/23/06
to

No. Also these are in fact nested function calls, where the result of one is
used as argument to the next. There is no specified evaluation order,
except the requirements that an argument must be evaluated before it can be
used to call a function.

Uli

Igor Tandetnik

unread,
Mar 23, 2006, 8:03:29 AM3/23/06
to
"Tim Roberts" <ti...@probo.com> wrote in message
news:7pi422h3sup242bo9...@4ax.com

A function call itself introduces a sequence point. But before the call
can be made, parameters need to be evaluated. They can be evaluated in
any order, and there is no sequence point between evaluation of two
parameters.

Further, in class member invocation of the form expr.method(params),
expr and params are evaluated in arbitrary order, again with no sequence
point.

Thus, in the example above, all three (i++) subexpressions may be
evaluated first with no intervening sequence points, before any calls
are made that may introduce said sequence points.

Carl Daniel [VC++ MVP]

unread,
Mar 23, 2006, 9:45:31 AM3/23/06
to
Igor Tandetnik wrote:
> "Tim Roberts" <ti...@probo.com> wrote in message
> news:7pi422h3sup242bo9...@4ax.com
>> "Igor Tandetnik" <itand...@mvps.org> wrote:
>>
>>> "Cheng" <cheng...@hotmail.com> wrote:
>>>>
>>>> s.assign(data[i++]).append(data[i++]).append(data[i++]);
>>>
>>> Not only would it be better - you must do so. The statement shown
>>> above exhibits undefined behavior. There exists a possible legal
>>> order of execution of various subexpressions that results in the
>>> variable 'i' being modified more than once between sequence points.
>>
>> Is it? I mean, I shudder in horror at that line of code, but I
>> thought a function call represented a sequence point. Not so?
>
> A function call itself introduces a sequence point. But before the
> call can be made, parameters need to be evaluated. They can be
> evaluated in any order, and there is no sequence point between
> evaluation of two parameters.
>
> Further, in class member invocation of the form expr.method(params),
> expr and params are evaluated in arbitrary order, again with no
> sequence point.
>
> Thus, in the example above, all three (i++) subexpressions may be
> evaluated first with no intervening sequence points, before any calls
> are made that may introduce said sequence points.

Not only may they be, they likely will be, as that usually results in
compact and efficient code for this kind of construct (evaluate and push
operands right to left, then call functions left to right).

-cd


Frederico Pissarra

unread,
Mar 23, 2006, 2:49:35 PM3/23/06
to

"Cheng" <cheng...@hotmail.com> escreveu na mensagem
news:u8X%23alhTG...@tk2msftngp13.phx.gbl...

It seems obvious to me that assign() will be performed before the first
append(), and this one before the second one. Obvious because member
selecion operator "." has the higher precedence and it's evaluated left to
right.

At the same time, in the first s.assign()... the routine uses post-increment
operators, so "i" will be incremented AFTER the expression is evaluated. The
same happens in the second s.assing().... but with pre-increment.

The last sentences are evaluated separately, so "s.assing(data[i++])" is the
same as "s.assign(data[i]); i++;".

Take a look at the precedence table in any good C book (or MSDN)... you'll
notice that . has the higher precedence... followed by ->, followed by [],
followed by funcion call "()"...

Hope it helped!

[]s
Fred


Carl Daniel [VC++ MVP]

unread,
Mar 23, 2006, 3:03:05 PM3/23/06
to
"Frederico Pissarra" <fred...@vgainfo.com> wrote in message
news:%23yG0LLr...@TK2MSFTNGP12.phx.gbl...

But precedence does not fully determine the execution order. A valid order
for the original expression is:

evaluate data[i] ; argument to last append
increment i
evaluate data[i] ; argument to first append
increment i
evaluate data[i] ; argument to assign
increment i
call assign ; sequence point
call append ; sequence point
call append ; sequence point

Another valid order is:

evaluate data[i] ; argument to assign
increment i
call assign ; sequence point
evaluate data[i] ; argument to first append
increment i
call append ; sequence point
evaluate data[i] ; argument to second append
increment i
call append ; sequence point

Note that this results in the reversal of the operands to the three
functions.

Another legal order is:

evaluate data[i] ; argument to last append
evaluate data[i] ; argument to first append
evaluate data[i] ; argument to assign
increment i
increment i
increment i
call assign ; sequence point
call append ; sequence point
call append ; sequence point

Note that this results in all three calls being made with the same
arguments.

An example of an invalid ordering:

evaluate data[i] ; argument to last append
evaluate data[i] ; argument to first append
evaluate data[i] ; argument to assign
call assign ; sequence point
call append ; sequence point
call append ; sequence point
increment i
increment i
increment i

This ordering is invalid because the side-effects of the evaluation of the
parameters for a function call are split across the function call.

The problem that precedence alone doesn't cover is that the relative order
of evaluation of the arguments of the three function calls is not defined,
nor is whether the evaluation of those arguments overlaps.

-cd


Frederico Pissarra

unread,
Mar 23, 2006, 8:47:00 PM3/23/06
to

"Carl Daniel [VC++ MVP]" <cpdaniel_remove...@mvps.org.nospam>
wrote in message news:uLkXMTrT...@TK2MSFTNGP12.phx.gbl...

Still... In the last example I show to you, here is the assembly generated
by compiling without optimizations (cl -Od -FAs -Fa -c test.c) - of course
the code sintax is a little more complex than that... but it shows pos and
pre increment in action...

----------%<--------------%<---------------
_main proc
; i is a local var, allocated on stack
mov [i],0
mov eax,[i]
mov edx,eax

push eax
push edx
push offset szFmt
call _printf ; printf("%d %d", i, i);
mov ecx,[i]
add ecx,1
mov [i],ecx ; i = i + 1;
mov edx,[i]
add edx,1
mov [i],edx ; i = i + 1;

mov eax,[i]
add eax,1
mov [i],eax ; i = i + 1;
mov ecx,[i]
add ecx,1
mov [i],ecx ; i = i + 1
push eax
push ecx
push offset szFmt
call _printf ; printf("%d %d", i, i);
ret
_main endp
----------%<--------------%<---------------

[]s
Fred


Igor Tandetnik

unread,
Mar 23, 2006, 10:01:12 PM3/23/06
to
"Frederico Pissarra" <frederic...@gmail.com> wrote in message
news:eD6gsTuT...@tk2msftngp13.phx.gbl

> Still... In the last example I show to you, here is the assembly
> generated by compiling without optimizations (cl -Od -FAs -Fa -c
> test.c)

Would you be happy writing code that only works correctly when compiled
with a specific version of a specific compiler using specific compiler
switches? Somehow this does not strike me as a prudent course of action.

Carl Daniel [VC++ MVP]

unread,
Mar 23, 2006, 10:54:30 PM3/23/06
to
Frederico Pissarra wrote:
> "Carl Daniel [VC++ MVP]"

>> The problem that precedence alone doesn't cover is that the relative
>> order of evaluation of the arguments of the three function calls is
>> not defined, nor is whether the evaluation of those arguments
>> overlaps.
>
> Still... In the last example I show to you, here is the assembly
> generated by compiling without optimizations (cl -Od -FAs -Fa -c
> test.c) - of course the code sintax is a little more complex than
> that... but it shows pos and pre increment in action...

You missed the point entirely - there's no issue here with understanding of
pre- and post-increment.

Code like the example that started this thread:

s.assign(data[i++]).append(data[i++]).append(data[i++]);

has undefined behavior.

Yes, it may do exactly what you hope it does, but it's not required to, and
there are reasonable code generation sequences where it does something
nearly opposite to what you expected.

-cd


Frederico Pissarra

unread,
Mar 24, 2006, 8:16:03 AM3/24/06
to

"Carl Daniel [VC++ MVP]" <cpdaniel_remove...@mvps.org.nospam>
escreveu na mensagem news:uQgnoavT...@TK2MSFTNGP10.phx.gbl...

I agree that the code used as example is a bit confusing - after all, i is
incremented after each function call or after all funcions?. But this is not
"undefined" behavior!
In every compiler I tested this code (GCC, Borland C++, Visual C++ 6, 7 and
8 and Watcom C++) the result is the same... Because structure member
operator, function calls and array offset operator have higher precedence
than pos or pre increment, those are made AFTER the entire sentence... this
is the expected behavior in C language!

You see... what I cannot agree is the use of word "undefined"...

Of course, we have to avoid those constructs to be certain that our code
will do what is required to do... just to be sure I'll never write a
sentence like that... I prefeer:

----------%<---------------%<-----------
s.assign(data[i]);
s.append(data[i]);
s.append(data[i]);
i += 3;
----------%<---------------%<-----------

It's easy to read and mantain!

[]s
Fred


Igor Tandetnik

unread,
Mar 24, 2006, 8:51:59 AM3/24/06
to
"Frederico Pissarra" <fred...@vgainfo.com> wrote in message
news:e861MU0T...@TK2MSFTNGP11.phx.gbl

> I agree that the code used as example is a bit confusing - after all,
> i is incremented after each function call or after all funcions?. But
> this is not "undefined" behavior!

Is too, in the exact meaning assigned to the term by the C++ standard.
Do you want me to quote chapter and verse? (Never let a Vogon recite
poetry to you.)

> In every compiler I tested this code (GCC, Borland C++, Visual C++ 6,
> 7 and 8 and Watcom C++) the result is the same...

Read the original post in this thread. The whole thread started because
the original poster was not getting the results he or she expected. If
you can't reproduce the OP's results it doesn't mean they are not valid.
Have you tested with optimizations on?

> Because structure
> member operator, function calls and array offset operator have higher
> precedence than pos or pre increment, those are made AFTER the entire
> sentence... this is the expected behavior in C language!

Precedence has very little to do with it. Consider:

int x = f() * g() + h();

'*' has higher precedence than '+'. This only means that, once f(), g()
and h() are calculated, the multiplication will be carried out first and
then the addition, so you won't end up with f*(g+h). But it does not say
anything about which order f(), g() and h() are called in. The compiler
can call all three, in arbitrary order, before performing multiplication
and addition. Or it can call f and g first (in arbitrary order),
multiply the results, then call h, and finally perform the addition. If
f, g or h have side effects, the result may differ depending on which
order the compiler chooses to evaluate subexpressions.

> You see... what I cannot agree is the use of word "undefined"...

"Undefined behavior" (http://en.wikipedia.org/wiki/Undefined_behavior)
is a term of art (http://en.wikipedia.org/wiki/Term_of_art) in C++
programming, not just a figure of speech.

Carl Daniel [VC++ MVP]

unread,
Mar 24, 2006, 10:00:18 AM3/24/06
to

Yes, it IS undefined behavior. The compilers that you tried don't determine
what is undefined behavior - the C++ standard does. This IS undefined
behavior because the variable i is modified more than once between sequence
points.

> In every compiler I tested this code (GCC, Borland C++, Visual C++ 6,


> 7 and 8 and Watcom C++) the result is the same... Because structure
> member operator, function calls and array offset operator have higher
> precedence than pos or pre increment, those are made AFTER the entire
> sentence... this is the expected behavior in C language!

Please re-read my earlier posts where I showed examples that fully honor the
C++ rules of precedence and the bahavior of post-increment and yet don't
behave as you'd like. This is not a precedence issue.

-cd


Tom Widmer [VC++ MVP]

unread,
Mar 24, 2006, 10:00:40 AM3/24/06
to
Frederico Pissarra wrote:
> I agree that the code used as example is a bit confusing - after
all, i is
> incremented after each function call or after all funcions?. But this is not
> "undefined" behavior!
> In every compiler I tested this code (GCC, Borland C++, Visual C++ 6, 7 and
> 8 and Watcom C++) the result is the same... Because structure member
> operator, function calls and array offset operator have higher precedence
> than pos or pre increment, those are made AFTER the entire sentence... this
> is the expected behavior in C language!

On the contrary, any behaviour at all is expected. For a built in type
like int, the expression "i++" has a value (the value of i at the
previous sequence point) and a side-effect (i is incremented before the
next sequence point). If a variable is modified twice without an
intervening sequence point, the behaviour is undefined.

In the example in question:


s.assign(data[i++]).append(data[i++]).append(data[i++]);

The compiler might do:
s.assign(data[i]).append(data[i]).append(data[i]);
i += 3;
or it might do:
s.assign(data[i]).append(data[i + 1]).append(data[i + 2]);
i += 3;
or it might do:
launchSpaceInvaders();

> You see... what I cannot agree is the use of word "undefined"...

It has a well defined meaning in the C and C++ standards. The C faq
covers order of evaluation in detail:
http://c-faq.com/expr/index.html

Tom

Carl Daniel [VC++ MVP]

unread,
Mar 24, 2006, 10:09:50 AM3/24/06
to
Frederico Pissarra wrote:
> I agree that the code used as example is a bit confusing - after all,
> i is incremented after each function call or after all funcions?. But
> this is not "undefined" behavior!
> In every compiler I tested this code (GCC, Borland C++, Visual C++ 6,
> 7 and 8 and Watcom C++) the result is the same... Because structure
> member operator, function calls and array offset operator have higher
> precedence than pos or pre increment, those are made AFTER the entire
> sentence... this is the expected behavior in C language!

Not so. You're guaranteed that all of the evaluation of the parameters of a
function call complete before the function call begins, but when you have a
"full expression" (everything up to the semicolon) that contains multiple
function calls, there are no guarantees at all about the order of evaluation
of the parameters of a given function nor the order in which functions will
be called (except as necessary to obey precedence rules).

Are you sure you tried this with VC8? The following program:

#include <string>
#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
char* data[] = { "one ", "two ", "three " };
int i = 0;
string s;


s.assign(data[i++]).append(data[i++]).append(data[i++]);

cout << s << endl;

i = 0;
s.assign(data[i++]);
s.append(data[i++]);
s.append(data[i++]);

cout << s << endl;
}


compiled with VC8 with out without optimization produces

one one one
one two three

as it's output. If precedence alone dictated behavior, the two lines of
output would be the same. That is undefined behavior.

-cd


Carl Daniel [VC++ MVP]

unread,
Mar 24, 2006, 11:19:38 AM3/24/06
to
Here's an even better one:

#include <string>
#include <iostream>
#include <iomanip>

using namespace std;

template <class T>
T inc(T& i)
{
T t = i;
++i;
return t;
}

void f1()


{
char* data[] = { "one ", "two ", "three " };
int i = 0;
string s;
s.assign(data[i++]).append(data[i++]).append(data[i++]);
cout << s << endl;
}

void f2()


{
char* data[] = { "one ", "two ", "three " };
int i = 0;
string s;

s.assign(data[inc(i)]).append(data[inc(i)]).append(data[inc(i)]);
cout << s << endl;
}

void f3()


{
char* data[] = { "one ", "two ", "three " };
int i = 0;
string s;

s.assign(data[i++]);
s.append(data[i++]);
s.append(data[i++]);
cout << s << endl;
}

int main()
{
f1();
f2();
f3();
}

compiled with or without optimization, with VC8 or VC7.1 outputs

one one one
three two one
one two three

If precendence alone were enough, all three lines would be the same.
Undefined behavior in action! Only f3 has well-defined behavior - it must
produce "one two three " according to the C++ standard. f2 has partially
defined behavior as the only valid outputs from it consist of the strings
"one ", "two " and "three " once each in some undefined order. f1
has.completely undefined behavior since i is modified twice between sequence
points.

-cd


Frederico Pissarra

unread,
Mar 24, 2006, 12:22:51 PM3/24/06
to

"Carl Daniel [VC++ MVP]" <cpdaniel_remove...@mvps.org.nospam>
escreveu na mensagem news:O1z0A71T...@TK2MSFTNGP14.phx.gbl...

The behavior described can be explained easily with operator precedence:

Note that [] has higher precedence than () - funcion call. So 3
post-increments will be performed AFTER the whole expression is evaluated.
That's why "one one one" is the result... Is almost like if f1() is the same
thing as f3().

In f2() the template function inc() will calculate the values and put them
on stack (anonymous object)... since [] has higher precedence! That's why
the inverse order occurs...
Looking another way: [] has higher precedence than (), but inside the data[]
we have funcion calls that must be evaluated... so, all "inc(i)" are
evaluated before anything...

f3() uses simple calls...

The same to the function calls... they are always called in order from left
to right because "." has higher precedence then [] or ().

In this particular example there is no undefined behavior!

[]s
Fred


Frederico Pissarra

unread,
Mar 24, 2006, 12:26:33 PM3/24/06
to

"Carl Daniel [VC++ MVP]" <cpdaniel_remove...@mvps.org.nospam>
escreveu na mensagem news:O1z0A71T...@TK2MSFTNGP14.phx.gbl...

And think about the standard strcpy() function... It is implemented in C
like this:

char *strcpy(char *dest, char *src)
{
char *sTemp = *dest;
while (*dest++ = *src++);
return dest;
}

if the post increments are supposed to be evaluated sequentially the routine
will never work!

[]s
Fred


Carl Daniel [VC++ MVP]

unread,
Mar 24, 2006, 1:06:31 PM3/24/06
to
"Frederico Pissarra" <fred...@vgainfo.com> wrote in message
news:ujwJxd2T...@TK2MSFTNGP09.phx.gbl...

I'm sorry, but that's not right.

Try this (really - try it). If you put the bodies of f1, f2 and f3 all
inline in main, then the results of f1 vary depending on whether f2 is
included or not (i.e. commenting out f2 causes f1 to produce a different
result). The results vary because the compile is not constrained to produce
any particualr behavior for f1, and is constrained to produce one of 6
different results for f2. Only f3 has well-defined behavior.

> In f2() the template function inc() will calculate the values and put them
> on stack (anonymous object)... since [] has higher precedence! That's why
> the inverse order occurs...

Not so. use of the inc() function guarantees that i is incremented three
times but does not guarantee the relative order of the three occurrences of
data[inc(i)]. The code in f2() could have just as easily produced "one two
three " or even "two three one ". All would be legal results according to
the c++ standard.

> Looking another way: [] has higher precedence than (), but inside the
> data[] we have funcion calls that must be evaluated... so, all "inc(i)"
> are evaluated before anything...

Maybe, but the the compiler isn't required to do it. Precedence absolutely
does NOT define total ordering of operations. Please re-read the eariler
messages in this thread - you're simply overlooking possibilities that are
allowed by the C++ standard (which defines what is and is not undefined
behavior).

>
> f3() uses simple calls...
>
> The same to the function calls... they are always called in order from
> left to right because "." has higher precedence then [] or ().

Yes, in f1 and f2 the functions are called left to right - they must be due
to precedence. However, their arguments may be evaluated in any order -
left to right, right to left, or even interleaved (which is what happens
with f1).

>
> In this particular example there is no undefined behavior!

In f3 there is no undefined behavior, but f2 and f1 both have undefined
behavior.

I'd suggest that you get a copy of the C++ standard and read up on sequence
points and undefined behavior. You're missing the concept entirely.

-cd

Tom Widmer [VC++ MVP]

unread,
Mar 24, 2006, 1:23:34 PM3/24/06
to
Frederico Pissarra wrote:

>
> And think about the standard strcpy() function... It is implemented in C
> like this:
>
> char *strcpy(char *dest, char *src)
> {
> char *sTemp = *dest;
> while (*dest++ = *src++);
> return dest;
> }
>
> if the post increments are supposed to be evaluated sequentially the routine
> will never work!

Of course it will! dest and src are different objects. We're talking
about modifying the same object multiple times without intervening
sequence points.

What do you think should be printed in the following two cases:

#include <iostream>
int main()
{
std::cout << "post\n";
int i = 0;
std::cout << i++ << ' ' << i++ << ' '<< i++ << '\n';

std::cout << "pre\n";
i = 0;
std::cout << ++i << ' ' << ++i << ' '<< ++i << '\n';
}

I get different results with and without optimization on VC7.1! The
point is that precedence only imposes a *partial* ordering - beyond that
ordering (that the first thing be output before the second), everything
else can happen in any order, and because the same object is being
modified, it is undefined (as opposed to unspecified) behaviour in any
case. Again, read the C faq links I posted.

If you still don't understand after everything you've seen, never mind!

Tom

Igor Tandetnik

unread,
Mar 24, 2006, 1:50:12 PM3/24/06
to
Carl Daniel [VC++ MVP]

<cpdaniel_remove...@mvps.org.nospam> wrote:
> In f3 there is no undefined behavior, but f2 and f1 both have
> undefined behavior.

To be pedantically precise, f1 exhibits undefined behavior while f2
exhibits unspecified behavior.

Carl Daniel [VC++ MVP]

unread,
Mar 24, 2006, 8:23:43 PM3/24/06
to
"Igor Tandetnik" <itand...@mvps.org> wrote in message
news:e04iJP3T...@TK2MSFTNGP10.phx.gbl...

> Carl Daniel [VC++ MVP]
> <cpdaniel_remove...@mvps.org.nospam> wrote:
>> In f3 there is no undefined behavior, but f2 and f1 both have
>> undefined behavior.
>
> To be pedantically precise, f1 exhibits undefined behavior while f2
> exhibits unspecified behavior.

Pedantic, yes, but true!

-cd


Tim Roberts

unread,
Mar 24, 2006, 11:57:33 PM3/24/06
to
"Igor Tandetnik" <itand...@mvps.org> wrote:

>"Tim Roberts" <ti...@probo.com> wrote:
>> "Igor Tandetnik" <itand...@mvps.org> wrote:
>>> "Cheng" <cheng...@hotmail.com> wrote:
>>>>
>>>> s.assign(data[i++]).append(data[i++]).append(data[i++]);
>>>
>>> Not only would it be better - you must do so. The statement shown
>>> above exhibits undefined behavior. There exists a possible legal
>>> order of execution of various subexpressions that results in the
>>> variable 'i' being modified more than once between sequence points.
>>
>> Is it? I mean, I shudder in horror at that line of code, but I
>> thought a function call represented a sequence point. Not so?
>
>A function call itself introduces a sequence point. But before the call
>can be made, parameters need to be evaluated. They can be evaluated in
>any order, and there is no sequence point between evaluation of two
>parameters.
>
>Further, in class member invocation of the form expr.method(params),
>expr and params are evaluated in arbitrary order, again with no sequence
>point.
>
>Thus, in the example above, all three (i++) subexpressions may be
>evaluated first with no intervening sequence points, before any calls
>are made that may introduce said sequence points.

OK, I think I see the subtlety that I was missing.

what you're saying is that this:

s.assign(data[i++]).append(data[i++]).append(data[i++]);

is compiled as if it had been written:

string::append(
string::append(
string::assign( s, data[i++] ), data[i++], data[i++] ) );

So, even though it looks like three independent functions calls, it really
is three nested function calls.

That, I can believe.

Doug Harrison [MVP]

unread,
Mar 25, 2006, 12:05:00 PM3/25/06
to

For a continuation of this sort of analysis, see:

http://groups.google.com/group/microsoft.public.vc.language/msg/a5d3b3f8a4e3797c

--
Doug Harrison
Visual C++ MVP

Frederico Pissarra

unread,
Mar 25, 2006, 3:50:46 PM3/25/06
to

"Tom Widmer [VC++ MVP]" <tom_u...@hotmail.com> wrote in message
news:u5s2PA3T...@tk2msftngp13.phx.gbl...

My intent isn't to make anyone angry at me here... hehe...
I'm testing every scenario you guys are posting here and trying to
understand why you consider those codes having "undefined" behaviour... What
I am seeing, as an experienced C/C++ developer, is that precedence and
associativity can explain everything here...

In the last piece of code I expected to see 0 0 0 and 3 3 3... and it is
what happen!

I can't see what is "undefined" here...

expressions using ++ or -- can be very confusing sometimes, but to me they
aren't "undefined"...

Until now all of you failed to define what "undefined" is... Igor Tandetnik
gave a nice view of "undefined" calling sequence (and that explanation I can
understand and accept)...

Please, don't be angry! :)

[]s
Fred


Frederico Pissarra

unread,
Mar 25, 2006, 3:54:34 PM3/25/06
to

"Tom Widmer [VC++ MVP]" <tom_u...@hotmail.com> wrote in message
news:u5s2PA3T...@tk2msftngp13.phx.gbl...

In strcpy example I shown that both pointers are incremented AFTER the
entire expression is evaluated... the same occurs when i++ is used in a
single expression (or statement)... So:

s.assign(data[i++]).append(data[i++]).append(data[i++]);

will always be the same as

s.assign(data[i]).append(data[i]).append(data[i]);
i += 3;

Because i++ will be evaluated AFTER the entire expression is evaluated...
after all it is post-increment! No "undefined" behavior here!

[]s
Fred

Carl Daniel [VC++ MVP]

unread,
Mar 25, 2006, 5:50:21 PM3/25/06
to
"Frederico Pissarra" <frederic...@gmail.com> wrote in message
news:OFqau3EU...@tk2msftngp13.phx.gbl...

> My intent isn't to make anyone angry at me here... hehe...
> I'm testing every scenario you guys are posting here and trying to
> understand why you consider those codes having "undefined" behaviour...
> What I am seeing, as an experienced C/C++ developer, is that precedence
> and associativity can explain everything here...

Pardon me, but rubbish! Go back and look at my example with f1(), f2() and
f3(), but rewrite it to have all three "functions" inline in main():

#include <string>
#include <iostream>
#include <iomanip>

using namespace std;

template <class T>
T inc(T& i)
{
T t = i;
++i;
return t;
}

int main()


{
char* data[] = { "one ", "two ", "three " };
int i = 0;
string s;

// f1


s.assign(data[i++]).append(data[i++]).append(data[i++]);
cout << s << endl;

i = 0;
// f2 - comment the next two lines out and see the result of f1 change!


s.assign(data[inc(i)]).append(data[inc(i)]).append(data[inc(i)]);
cout << s << endl;

i = 0;
// f3


s.assign(data[i++]);
s.append(data[i++]);
s.append(data[i++]);
cout << s << endl;
}

How do you propose to explain in terms of precedence and associativity the
fact that commenting out "f2" in this program changes the output of f1? It
simply can't be done. Try it! Compile and it with VC7.1 or VC8 with or
without optimization, then comment out (or delete) "f2" and try it again.
I'm sorry, but precedence and associativity simply don't fully define the
order of evaluation for expressions like f1 or f2.

>
> In the last piece of code I expected to see 0 0 0 and 3 3 3... and it is
> what happen!

Your expectations have been tuned to match what your compiler does!
According to the C++ standard, the results of both are undefined.

>
> I can't see what is "undefined" here...
>
> expressions using ++ or -- can be very confusing sometimes, but to me they
> aren't "undefined"...

Again, you're missing the point. No single increment is undefined. What's
undefined is the relative order in which they execute in a single expression
that modifies the same variable more than once.

>
> Until now all of you failed to define what "undefined" is... Igor
> Tandetnik gave a nice view of "undefined" calling sequence (and that
> explanation I can understand and accept)...

Undefined behavior is behavior that is not defined by the C++ standard. The
behavior of any particular compiler has absolutely nothing to do with what
is or is not undefined behavior.

Relevant to this discussion, undefined behavior occurs when a single object
is modified twice between sequence points. In the original expression

s.assign(data[i++]).append(data[i++]).append(data[i++])

the variable i is modified three times and there ARE NO SEQUENCE POINTS
between those increments. That fact that you can make an argument why the
compiler generated code a certain way based on precedence and associativity
doesn't make the behavior well-defined according to the C++ standard.

Get the C++ standard and understand what we're talking about! As an
experienced developer, you should know what is and is not defined according
to the standard, and the values of expressions like int j = i++ + i++; are
simply not defined. Dressing it up with function calls and object
references may convince you that the result of
s.assign(data[i++]).append(data[i++]).append(data[i++]) is well defined, but
at its core it's no different than i++ + i++. Relying on the observable
behavior of the compiler to define your concept of "defined behavior" is
risky because a future version of the compiler, or another company's
compiler might produce a different result if you've relied on behavior
that's not defined by the standard.

>
> Please, don't be angry! :)

No worries!

-cd


Frederico Pissarra

unread,
Mar 25, 2006, 6:24:40 PM3/25/06
to
"Carl Daniel [VC++ MVP]" <cpdaniel_remove...@mvps.org.nospam>
wrote in message news:O4Xba3FU...@TK2MSFTNGP11.phx.gbl...

A-ha!! Now you convince me!
Very interesting this behaviour when deleting those 2 lines...

BTW... Do you know where I can get C++/CLI standard on the net? I have a
very old book of C++ standard here...

Thanks for the explanation
Fred


Carl Daniel [VC++ MVP]

unread,
Mar 25, 2006, 9:25:11 PM3/25/06
to
Frederico Pissarra wrote:
>
> A-ha!! Now you convince me! Very interesting this behaviour when deleting
> those 2 lines...

Isn't it though :)

> BTW... Do you know where I can get C++/CLI standard on the net? I
> have a very old book of C++ standard here...

Keep in mind that there are two standards - the ISO C++ standard (which is
what we've been talking about here), and the C++/CLI standard, which covers
managed C++ (where there is considerably less undefined behavior).

The C++/CLI standard can be obtained on the web for free:

http://www.ecma-international.org/publications/standards/Ecma-372.htm

The ISO/C++ standard is not available for free. You can buy it through your
national standards body that's a member of ISO (ANSI here in the USA). You
can also buy a hard-copy edition printed by Wiley through the BSA (the
Brittish equivalent of ANSI) from Amazon or other book dealers:

http://www.amazon.com/gp/product/0470846747

>
> Thanks for the explanation
> Fred

No problem!

-cd


Abdo Haji-Ali

unread,
Mar 26, 2006, 5:56:01 PM3/26/06
to
Interesting... any logical explanation for this behavior. I know that the
result is "undefined" but at least I know too that evaluating a statement
doesn't start before all previous ones are evaluated, isn't it?

> Get the C++ standard and understand what we're talking about! As an
> experienced developer, you should know what is and is not defined
> according to the standard, and the values of expressions like int j = i++
> + i++; are simply not defined. Dressing it up with function calls and
> object references may convince you that the result of
> s.assign(data[i++]).append(data[i++]).append(data[i++]) is well defined,
> but at its core it's no different than i++ + i++. Relying on the
> observable behavior of the compiler to define your concept of "defined
> behavior" is risky because a future version of the compiler, or another
> company's compiler might produce a different result if you've relied on
> behavior that's not defined by the standard.

So why is the statement j=i++ + i++; undefined? I scratched my head
searching for valid combinations of operator evaluation order that produces
different result, but couldn’t find any.

Thanks,
Abdo Haji-Ali
Programmer
In|Framez


Igor Tandetnik

unread,
Mar 26, 2006, 6:59:13 PM3/26/06
to
"Abdo Haji-Ali" <ah...@inframez.org_use_com_instead> wrote in message
news:%23KlvOiS...@TK2MSFTNGP10.phx.gbl

> So why is the statement j=i++ + i++; undefined?

By definition. Because the C++ standard says so.

> I scratched my head
> searching for valid combinations of operator evaluation order that
> produces different result, but couldn’t find any.

Remember, side effects are only required to be complete at the sequence
point. Let's assume that i == 1 at the beginning of the statement. The
compiler has at least two options:

1. Take current value of i for both instances of i++, perform an
addition and assign to j, then execute both side effects. At the end, j
== 2
2. Take current value of i (1) for one of the subexpressions (does not
matter which one), then perform increment. Take the new "current" value
(2), and again perform an increment. Finally, perform the addition. In
the end, j == 3.

However, having two possible results would make the behavior merely
unspecified, rather than undefined. Something in modifying the same
variable more than once makes it so difficult for optimizers that the
standard committee decided not to restrict the compiler in any way at
all in this case. Honestly, I'm not sure what it is.

Alexander Grigoriev

unread,
Mar 27, 2006, 12:56:52 AM3/27/06
to
Unspecified is what the particular vendor may _define_ as they like and
implement in a particular compiler.

OTOH, _undefined_ is behavior that cannot be guaranteed even for a
particular compiler. It may behave differently in different cases.

"Igor Tandetnik" <itand...@mvps.org> wrote in message

news:eUBCHFTU...@TK2MSFTNGP10.phx.gbl...

Tim Roberts

unread,
Mar 27, 2006, 2:10:46 AM3/27/06
to
"Frederico Pissarra" <frederic...@gmail.com> wrote:
>
>In the last piece of code I expected to see 0 0 0 and 3 3 3... and it is
>what happen!
>
>I can't see what is "undefined" here...

The fact that you HAPPEN to get the results you expect with one particular
version of one particular compiler does NOT mean that the program is
required to behave that way. "Undefined" has a VERY specific meaning in
the C and C++ standards. When a program uses a capability that is
identified as "undefined" in the standard, then you MIGHT get proper
results, or you might get Anchors Aweigh coming out of the speakers. This
IS such a case.

>expressions using ++ or -- can be very confusing sometimes, but to me they
>aren't "undefined"...

But, you see, it doesn't matter one whit whether the behavior is
"undefined" to you or not. The C++ standard says that the behavior IS
undefined.

>Until now all of you failed to define what "undefined" is...

No, that's not true, but you seem to be choosing to ignore it. In the C++
standard, when a construct is specified as having "undefined" behavior, it
means that compiler writers may do whatever they want. It means program
writers CANNOT rely on that construct to do anything useful.

In the case of this:

s.assign( x[i++] ).append( x[i++] ).append( x[i++] );

what we have is a single variable being modified three times without what
the standard defined as a "sequence point". Doing THAT is specifically
identified in the C++ standard as having "undefined" behavior. What that
means is that, even though YOU think you can see a reasonable
interpretation for what that statement means, you cannot go to the C++
standard to verify that. The standard says that the behavior of that
statement is "undefined". So, a compiler COULD bump "i" three times before
calling any of the function, and still conform to the standard. It could
bump "i" between each function, and still conform to the standard. It
could forget to bump "i" at all, and STILL conform to the standard.

THAT'S the problem. Visual C++ 7.1 happens to do what YOU expect, but I
hope you realize that it is not the only compiler in the world. Further,
there are NO promises that the next version of Visual C++ will interpret it
the same way.

Tim Roberts

unread,
Mar 27, 2006, 2:19:50 AM3/27/06
to
"Frederico Pissarra" <frederic...@gmail.com> wrote:
>
>In strcpy example I shown that both pointers are incremented AFTER the
>entire expression is evaluated... the same occurs when i++ is used in a
>single expression (or statement)... So:
>
>s.assign(data[i++]).append(data[i++]).append(data[i++]);
>
>will always be the same as
>
>s.assign(data[i]).append(data[i]).append(data[i]);
>i += 3;
>
>Because i++ will be evaluated AFTER the entire expression is evaluated...
>after all it is post-increment! No "undefined" behavior here!

No, no, no, no, no! Aren't you reading any of the the other posts in this
thread? This is exactly the point that I clarified (to myself) two days
ago.

That statement is NOT three separate and independent function calls. The
result of the first function is passed as a parameter ("this") to the
second function, and the result of THAT is passed as a parameter ("this")
to the third function. So, the compiler is required to treat it as though
you wrote:

string::append(
string::append(
string::assign( s, data[i++] ),

data[i++]),
data[i++] );

If you look at the outer function call, "i" is modified three times during
the evaluation of the parameters for that function. THAT, according to the
standard, has "undefined" behavior.

Again, YOU might see a sensible way to interpret that, but the standard
does NOT require a compiler to agree with your interpretation.

Tom Widmer [VC++ MVP]

unread,
Mar 28, 2006, 4:04:31 AM3/28/06
to
Frederico Pissarra wrote:

> My intent isn't to make anyone angry at me here... hehe...

haha...

> I'm testing every scenario you guys are posting here and trying to
> understand why you consider those codes having "undefined" behaviour... What
> I am seeing, as an experienced C/C++ developer, is that precedence and
> associativity can explain everything here...
>
> In the last piece of code I expected to see 0 0 0 and 3 3 3... and it is
> what happen!

That's what I get for a release build.

> I can't see what is "undefined" here...

The fact that when I run it compiled in Debug mode, I don't get 0 0 0
and 3 3 3, but rather
post
2 1 0
pre
3 3 3

You're clearly not trying to understand, since I said that you get
different results in debug and release, and yet you obviously didn't try
it, nor do you appear to have read the C faq link I posted.

Tom

Frederico Pissarra

unread,
Mar 28, 2006, 11:54:19 PM3/28/06
to
I already agreed with this concept...
Living and learning... :)

Fred

"Tom Widmer [VC++ MVP]" <tom_u...@hotmail.com> wrote in message

news:emoaiakU...@TK2MSFTNGP11.phx.gbl...

0 new messages