Parameter evaluation order before a function calling in C

CParametersEvaluation

C Problem Overview


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

}

C Solutions


Solution 1 - C

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

Solution 2 - C

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

Solution 3 - C

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

Solution 4 - C

>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 http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf">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.

Solution 5 - C

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.

Solution 6 - C

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.

Solution 7 - C

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestioncortoView Question on Stackoverflow
Solution 1 - CGrant WagnerView Answer on Stackoverflow
Solution 2 - CRobert GambleView Answer on Stackoverflow
Solution 3 - CPitje PuckView Answer on Stackoverflow
Solution 4 - CShafik YaghmourView Answer on Stackoverflow
Solution 5 - CJohannes Schaub - litbView Answer on Stackoverflow
Solution 6 - CPankaj MahatoView Answer on Stackoverflow
Solution 7 - CBrananView Answer on Stackoverflow