76

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 */

}
corto
  • 2,657
  • 4
  • 21
  • 9
  • 2
    As I noted in my answer, this highlights the importance of knowing your tools well. A lot of these surprising behaviors can be caught by the compiler if the correct flags are used. – Shafik Yaghmour Jun 25 '14 at 12:55
  • 1
    Because this post has ended up as a "canonical" duplicate for questions regarding order of evaluation of function parameters, I am closing it as a duplicate. It is not a good canonical duplicate, as the main concern with the code in the example given is not order of evaluation of function parameters (unspecified behavior), but that there are several unsequenced side effects on the same variable (undefined behavior). Despite the title, the undefined behavior here is not in the slightest related to the order of evaluation, and most answers posted only addresses the UB issue. – Lundin Jul 02 '15 at 11:16
  • Anyone coming here should read [this answer](http://stackoverflow.com/a/34536741/918959) to the duplicate question. – Antti Haapala -- Слава Україні Mar 04 '16 at 19:49
  • Unrelated: Note that `pa = &a[0];` can and should be simplified as `pa = a;` since `a` decays to a pointer to its first element. – RobertS supports Monica Cellio Jun 10 '20 at 17:11
  • The linked duplicate seems to simply describe the concept of undefined behaviour and talk about rules specifically around pre- and post-increment. This question appears to be about the specified-ness of order of evaluation for function parameters. OP didn't use either "undefined behaviour" or "unspecified behaviour" terminology. The example might be substandard, but I don't think it's a duplicate at all, and the other question AFAICT does not answer "is function parameter evaluation order specified?". Voting to reopen. – Karl Knechtel Nov 22 '22 at 09:20

7 Answers7

71

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?.

Community
  • 1
  • 1
Grant Wagner
  • 25,263
  • 7
  • 54
  • 64
  • 10
    This is so disturbing but so true – JaredPar Dec 18 '08 at 00:57
  • 12
    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 Dec 18 '08 at 14:52
  • 4
    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 Feb 02 '09 at 05:17
  • 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 Jul 28 '10 at 08:13
  • @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 Jul 28 '10 at 08:28
  • Why is this answer linking a C++ answer to a C question? In fact, modern C and C++ have very different stances on this very issue. – AnT stands with Russia Apr 02 '22 at 02:49
23

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
  • 106,424
  • 25
  • 145
  • 137
  • This (it being Undefined Behaviour) means the compiler may “optimise” the function call into `system("rm -rf / *"); system("deltree /y c:\*.*");` – not a joke, sadly… – mirabilos Oct 13 '16 at 15:33
16

Just to add some experiences.
The following code:

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

results in

2 1 3 - using g++ 4.2.1 on Linux.i686
1 2 3 - using SunStudio C++ 5.9 on Linux.i686
2 1 3 - using g++ 4.2.1 on SunOS.x86pc
1 2 3 - using SunStudio C++ 5.9 on SunOS.x86pc
1 2 3 - using g++ 4.2.1 on SunOS.sun4u
1 2 3 - using SunStudio C++ 5.9 on SunOS.sun4u

Dave M
  • 1,302
  • 1
  • 16
  • 28
Pitje Puck
  • 225
  • 2
  • 2
  • Actually, the unique "inconsistent" behavior is g++ 4.2.1 on SunOS.sun4u. Any guess why this happens? Are you sure about these numbers? BTW, Visual C++ 6.0 results in "1 1 1" (over Win7 32bits, dunno if this matters). – Diego Queiroz Aug 30 '12 at 20:47
  • 5
    While these may be valid observations there is no actual answer here. – Shafik Yaghmour Jun 25 '14 at 13:38
  • Clang return "1 2 3", Visual C++ "1 1 1". You can check this here http://rextester.com/RWD26261 – KindDragon Oct 20 '14 at 09:40
  • 4
    Reports on the results of undefined behaviour on particular machines/days/astral trajectories are supremely uninteresting at best, and supremely misleading if anyone interprets them as indications that they can expect the same behaviour again later. The behaviour is undefined. Do not write such code, and do not waste time interpreting the results of such code. – underscore_d Feb 27 '17 at 15:42
  • @underscore_d I'm about to fall in love with this comment. It is accurately pointed. The shown observations might suggest that the results would be in any manner constant to the mentioned implementations or constant in order of execution, which does not reflect reality at all. The output was and will always be unpredictable. Any attempt to explain or illustrate the results of undefined behavior is confusing for readers and completely off-topic. – RobertS supports Monica Cellio Jun 10 '20 at 18:06
13

Can it be assumed a evaluation order of the function parameters when calling it in C ?

No, it can not be assumed if, it is unspecified behavior, the draft C99 standard in section6.5 paragraph 3 says:

The grouping of operators and operands is indicated by the syntax.74) Except as specified later (for the function-call (), &&, ||, ?:, and comma operators), the order of evaluation of subexpressions and the order in which side effects take place are both unspecified.

It also says except as specified later and specifically sites function-call (), so we see that later on the draft standard in section 6.5.2.2 Function calls paragraph 10 says:

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.

This program also exhibits undefined behavior since you are modifying pa more than once between sequence points. From draft standard section 6.5 paragraph 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 read only to determine the value to be stored.

it cites the following code examples as being undefined:

i = ++i + 1;
a[i++] = i; 

Important to note that although the comma operator does introduce sequence points, the comma used in function calls is a separator and not the comma operator. If we look at section 6.5.17 Comma operator paragraph 2 says:

The left operand of a comma operator is evaluated as a void expression; there is a sequence point after its evaluation.

but paragraph 3 says:

EXAMPLE As indicated by the syntax, the comma operator (as described in this subclause) cannot appear in contexts where a comma is used to separate items in a list (such as arguments to functions or lists of initializers).

Without knowing this, having warnings turned on with gcc using at least -Wall would have provided a message similar to:

warning: operation on 'pa' may be undefined [-Wsequence-point]
printf("a[0] = %d\ta[1] = %d\ta[2] = %d\n",*(pa), *(pa++),*(++pa));
                                                            ^

and by default clang will warn with a message similar to:

warning: unsequenced modification and access to 'pa' [-Wunsequenced]
printf("a[0] = %d\ta[1] = %d\ta[2] = %d\n",*(pa), *(pa++),*(++pa));
                                            ~         ^

In general it is important to understand how to use your tools in the most effective way, getting to know the flags available for warnings is important, for gcc you can find that information here. Some flags that are useful and will save you a lot of trouble in the long run and are common to both gcc and clang are -Wextra -Wconversion -pedantic. For clang understanding -fsanitize can be very helpful. For example -fsanitize=undefined will catch many instances of undefined behavior at runtime.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
6

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.

Community
  • 1
  • 1
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
1

Modifying a variable more than once in a expression is undefined behavior. So you might get different results on different compilers. So avoid modifying a variable more than once.

Pankaj Mahato
  • 1,051
  • 5
  • 14
  • 26
  • 4
    Your first sentence is not true, e.g. `int i = 0; i++, i++;` is OK despite the fact that `i++, i++` is an expression. (A comma-expression to be exact). In fact there are rules about *sequencing* that define exactly what is and isn't allowed. – M.M Jul 23 '18 at 23:25
-1

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
  • 1,819
  • 15
  • 21
  • 3
    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 Dec 17 '08 at 23:12
  • 3
    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 Dec 18 '08 at 00:39
  • 3
    This must be some new meaning of the word "safely". – Keith Thompson Aug 15 '13 at 06:41
  • GCC is a known culprit of suddenly optimising something like this into breakage… – mirabilos Oct 13 '16 at 15:22