views:

1868

answers:

10

Hi all,

Can it be assumed a evaluation order of the function parameters when calling it in C ? According to the following program, it seems that there is not a particular order when I executed it.

#include <stdio.h>

int main()
{
   int a[] = {1, 2, 3};
   int * pa; 

   pa = &a[0];
   printf("a[0] = %d\ta[1] = %d\ta[2] = %d\n",*(pa), *(pa++),*(++pa));
   /* Result: a[0] = 3  a[1] = 2    a[2] = 2 */

   pa = &a[0];
   printf("a[0] = %d\ta[1] = %d\ta[2] = %d\n",*(pa++),*(pa),*(++pa));
   /* Result: a[0] = 2  a[1] = 2     a[2] = 2 */

   pa = &a[0];
   printf("a[0] = %d\ta[1] = %d\ta[2] = %d\n",*(pa++),*(++pa), *(pa));
   /* a[0] = 2  a[1] = 2 a[2] = 1 */

}
+11  A: 

No, function parameters are not evaluated in a defined order in C.

See Martin York's answers to What are all the common undefined behaviour that c++ programmer should know about?.

Grant Wagner
This is so disturbing but so true
JaredPar
It's not really disturbing. If the order of evaluation was defined, then you would have some C/C++ compilers generating less-than-optimal code. For example, if the args are pushed on to the stack from back-to-front, then evaluating them front-to-back means more temp storage to get the call right.
Ben Combee
I thought the C-calling convention requires that args be pushed back-to-front, leaving param #0 always as the first item on the stack. The order of evaluation is not defined, but the simplest way is a loop: "Eval-Push-Repeat", moving from right-to-left.
abelenky
There are different calling conventions even just on x86 (http://en.wikipedia.org/wiki/X86_calling_conventions); some of them (e.g. pascal, Borland fastcall) push arguments left to right, without such flexibility permitted by the standard their implementation would be more difficult.
Matteo Italia
@abelenky: calling convention is up to the ABI. Defining the order of evaluation for function parameters would lead to suboptimal code for other-than-cdecl calling conventions (that is, not as pretty as evaluate-push-givemetenmore), at best. It's also insane to do so. :)
Michael Foukarakis
+7  A: 

Order of evaluation of function arguments is unspecified, from C99 §6.5.2.2p10:

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.

Similar wording exists in C89.

Additionally, you are modifying pa multiple times without intervening sequence points which invokes undefined behavior (the comma operator introduces a sequence point but the commas delimiting the function arguments do not). If you turn up the warnings on your compiler it should warn you about this:

$ gcc -Wall -W -ansi -pedantic test.c -o test
test.c: In function ‘main’:
test.c:9: warning: operation on ‘pa’ may be undefined
test.c:9: warning: operation on ‘pa’ may be undefined
test.c:13: warning: operation on ‘pa’ may be undefined
test.c:13: warning: operation on ‘pa’ may be undefined
test.c:17: warning: operation on ‘pa’ may be undefined
test.c:17: warning: operation on ‘pa’ may be undefined
test.c:20: warning: control reaches end of non-void function
Robert Gamble
A: 

Grant's answer is correct, it's undefined.

BUT,,,

By your example, your compiler seems to be evaluating in right-to-left order (unsurprisingly, the order that arguments are pushed onto the stack). If you can do other tests to show that the order is maintained consistently even with optimizations enabled, and if you're only going to stick with that one version of the compiler, you can safely assume right-to-left ordering.

It's totally non-portable and a horrible, horrible thing to do, though.

Branan
You play with fire for when the compiler is upgraded. Don't do it; people who play with fire get burned, sooner or later.
Jonathan Leffler
Not only when the compiler is upgraded - you play with fire because your 'test' will almost certainly leave out something, so the eval order will change when someone adds a comment (or something) to the code next month. If you need the expressions to eval in a particular order, do them separately.
Michael Burr
A: 

To add even more fuel, the behaviour because it is undefined will be compiler-dependent...

Keltia
+1  A: 

As others already said, the order in which function arguments are evaluated is unspecified, and there is no sequence point between evaluating them. Because you change pa subsequently while passing each argument, you change and read pa twice in between two sequence points. That's actually undefined behavior. I found a very nice explanation in the GCC manual, which i think might be helpful:

The C and C++ standards defines the order in which expressions in a C/C++ program are evaluated in terms of sequence points, which represent a partial ordering between the execution of parts of the program: those executed before the sequence point, and those executed after it. These occur after the evaluation of a full expression (one which is not part of a larger expression), after the evaluation of the first operand of a &&, ||, ? : or , (comma) operator, before a function is called (but after the evaluation of its arguments and the expression denoting the called function), and in certain other places. Other than as expressed by the sequence point rules, the order of evaluation of subexpressions of an expression is not specified. All these rules describe only a partial order rather than a total order, since, for example, if two functions are called within one expression with no sequence point between them, the order in which the functions are called is not specified. However, the standards committee have ruled that function calls do not overlap.

It is not specified when between sequence points modifications to the values of objects take effect. Programs whose behavior depends on this have undefined behavior; the C and C++ standards specify that “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 read only to determine the value to be stored.”. If a program breaks these rules, the results on any particular implementation are entirely unpredictable.

Examples of code with undefined behavior are a = a++;, a[n] = b[n++] and a[i++] = i;. Some more complicated cases are not diagnosed by this option, and it may give an occasional false positive result, but in general it has been found fairly effective at detecting this sort of problem in programs.

The standard is worded confusingly, therefore there is some debate over the precise meaning of the sequence point rules in subtle cases. Links to discussions of the problem, including proposed formal definitions, may be found on the GCC readings page, at http://gcc.gnu.org/readings.html.

Johannes Schaub - litb
A: 

If you are executing this code on a GCC compiler then the order of evaluation for printf statement is from right to left. Hence the result (you printed as a comment). I hope you know the incremental rules and diff between a++ and ++a.

+2  A: 

While it is true that the order of parameter evaluation is not defined, compilers I've encountered so far all do it right-to-left. I'd never depend on this behavior, but its interesting to note.

The simplest example I know of is:

int i=1;
printf("%d %d %d", i++, i++, i++);

With a result of "3 2 1"

If someone knows of a compiler/platform that results in "1 2 3", or even "3 3 3", I'd love to know the details (compiler version? OS? hardware? etc).

abelenky
A: 

but this code main() { int a=10; printf("%d %d %d\n",++a, a++,a); }

prints

12 10 12

so order is not right to left.

nowonder
A: 

Aditya, you should have specified which compiler you used, since it's an implementation defined behaviour. All the versions of Gnu C I have used so far use right to left evaluation order.

Actually, it is not 'implementation defined behaviour'; it is 'undefined behaviour'. There is a difference, even if an implementation defines a behaviour for what the standard says is 'undefined behaviour'.
Jonathan Leffler
A: 

Try this:

int i=1;
printf("%d %d %d", i++, i++, i);

I get 2 1 3 with VS2003

DIB
Rather weird - but totally unguaranteed.
Jonathan Leffler