tags:

views:

1222

answers:

11

Here is a test case:


void foo(int i, int j)
{
   printf("%d %d", i, j);
}
...
test = 0;
foo(test++, test);

I would expect to get a "0 1" output, but I get "0 0" What gives??

+44  A: 

This is an example of unspecified behavior. The standard does not say what order arguments should be evaluated in. This is a compiler implementation decision. The compiler is free to evaluate the arguments to the function in any order.

In this case, it looks like actually processes the arguments right to left instead of the expected left to right.

In general, doing side-effects in arguments is bad programming practice.

Instead of foo(test++, test); you should write foo(test, test+1); test++;

It would be semantically equivalent to what you are trying to accomplish.

Edit: As Anthony correctly points out, it is undefined to both read and modify a single variable without an intervening sequence point. So in this case, the behavior is indeed undefined. So the compiler is free to generate whatever code it wants.

Benoit
As added emphasis, to avoid these sorts of problems I always have increments as a separate statement.
David L Morris
I wouldn't emphasize that. Maybe if you're new to the language you may want to take it slow, but this syntax exists for a reason, and it's not only because i += 1 is 3 more characters.
Loren Segal
Is it just me or the evaluation order for operands has nothing to do with what you see? test++ is *post* increment, so test is incremented *after* being passed, as Mike Thompson says below.
agnul
If I use (++t, t), I'm not sure if would be evaluated *after*. THAT would be wrong semantically, as it should be incremented BEFORE the function call i.e. bar(++t) IS valid. with bar(t++), I say "use t then increment"
Benoit
+1  A: 

The compiler might not be evaluating the arguments in the order you'd expect.

yjerem
+14  A: 

Everything I said originally is WRONG! The point in time at which the side-affect is calculated is unspecified. Visual C++ will perform the increment after the call to foo() if test is a local variable, but if test is declared as static or global it will be incremented before the call to foo() and produce different results, although the final value of test will be correct.

The increment should really be done in a separate statement after the call to foo(). Even if the behaviour was specified in the C/C++ standard it would be confusing. You would think that C++ compilers would flag this as a potential error.

Here is a good description of sequence points and unspecified behaviour.

<----START OF WRONG WRONG WRONG---->

The "++" bit of "test++" gets executed after the call to foo. So you pass in (0,0) to foo, not (1,0)

Here is the assembler output from Visual Studio 2002:

mov ecx, DWORD PTR _i$[ebp]
push ecx
mov edx, DWORD PTR tv66[ebp]
push edx
call _foo
add esp, 8
mov eax, DWORD PTR _i$[ebp]
add eax, 1
mov DWORD PTR _i$[ebp], eax

The increment is done AFTER the call to foo(). While this behavior is by design, it is certainly confusing to the casual reader and should probably be avoided. The increment should really be done in a separate statement after the call to foo()

<----END OF WRONG WRONG WRONG ---->

Mike Thompson
No, it doesn't, but you can't see that in this example because test isn't global and VS2002 evaluated the last argument first (legal and somewhat logical for a stack)
MSalters
+1  A: 

The order of evaluation for arguments to a function is undefined. In this case it appears that it did them right-to-left.

(Modifying variables between sequence points basically allows a compiler to do anything it wants.)

wnoise
+2  A: 

C doesn't guarantee the order of evaluation of parameters in a function call, so with this you might get the results "0 1" or "0 0". The order can change from compiler to compiler, and the same compiler could choose different orders based on optimization parameters.

It's safer to write foo(test, test + 1) and then do ++test in the next line. Anyway, the compiler should optimize it if possible.

Fabio Ceconello
+6  A: 

It's "unspecified behavior", but in practice with the way the C call stack is specified it almost always guarantees that you will see it as 0, 0 and never 1, 0.

As someone noted, the assembler output by VC pushes the right most parameter on the stack first. This is how C function calls are implemented in assembler. This is to accommodate C's "endless parameter list" feature. By pushing parameters in a right-to-left order, the first parameter is guaranteed to be the top item on the stack.

Take printf's signature:

int printf(const char *format, ...);

Those ellipses denote an unknown number of parameters. If parameters were pushed left-to-right, the format would be at the bottom of a stack of which we don't know the size.

Knowing that in C (and C++) that parameters are processed left-to-right, we can determine the simplest way of parsing and interpreting a function call. Get to the end of the parameter list, and start pushing, evaluating any complex statements as you go.

However, even this can't save you as most C compilers have an option to parse functions "Pascal style". And all this means is that the function parameters are pushed on the stack in a left-to-right fashion. If, for instance, printf was compiled with the Pascal option, then the output would most likely be 1, 0 (however, since printf uses the ellipse, I don't think it can be compiled Pascal style).

toast
A: 

Question is confused between the title and the code example.

title has ++n
example has test++
itj
+25  A: 

This is not just unspecified behaviour, it is actually undefined behaviour .

Yes, the order of argument evaluation is unspecified, but it is undefined to both read and modify a single variable without an intervening sequence point unless the read is solely for the purpose of computing the new value. There is no sequence point between the evaluations of function arguments, so f(test,test++) is undefined behaviour: test is being read for one argument and modified for the other. If you move the modification into a function then you're fine:

int preincrement(int* p)
{
    return ++(*p);
}

int test;
printf("%d %d\n",preincrement(&test),test);

This is because there is a sequence point on entry and exit to preincrement, so the call must be evaluated either before or after the simple read. Now the order is just unspecified.

Note also that the comma operator provides a sequence point, so

int dummy;
dummy=test++,test;

is fine --- the increment happens before the read, so dummy is set to the new value.

Anthony Williams
For prosperity, I think it worth while adding the standard reference for these. ISO C++ 5p4.
Richard Corden
Don't you mean posterity? Or do you mean to help make programmers more wealthy?
James A. Rosen
@Anthony, i think you missed the parens around the initializer of `dummy` :)
Johannes Schaub - litb
+1  A: 

Um, now that the OP has been edited for consistency, it is out of sync with the answers. The fundamental answer about order of evaluation is correct. However the specific possible values are different for the foo(++test, test); case.

++test will be incremented before being passed, so the first argument will always be 1. The second argument will be 0, or 1 depending on evaluation order.

Steve Fallows
+1  A: 

According to the C standard, it is undefined behaviour to have more than one references to a variable in a single sequence point (here you can think of that as being a statement, or parameters to a function) where one of more of those references includes a pre/post modification. So: foo(f++,f) <--undefined as to when f increments. And likewise (I see this all the time in user code): *p = p++ + p;

Typically a compiler will not change its behaviour for this type of thing (except for major revisions).

Avoid it by turning on warnings and paying attention to them.

A: 

To repeat what others have said, this is not unspecified behavior, but rather undefined. This program can legally output anything or nothing, leave n at any value, or send insulting email to your boss.

As a matter of practice, compiler writers will usually just do what's easiest for them to write, which generally means that the program will fetch n once or twice, call the function, and increment sometime. This, like any other conceivable behavior, is just fine according to the standard. There is no reason to expect the same behavior between compilers, or versions, or with different compiler options. There is no reason why two different but similar-looking examples in the same program have to be compiled consistently, although that's the way I'd bet.

In short, don't do this. Test it under different circumstances if you're curious, but don't pretend that there is a single correct or even predictable result.

David Thornley