0

Okay so I was writing some code earlier. This was the line specifically:

EnterNode( FindNode( terrain_X, terrain_Y, travel_dir ), travel_dir );

I noticed after testing my program something weird was happening. The value being received by the outer function was not the value I was reading when I inspected the stack.

I made an example program: https://ideone.com/wNjJrE

#include <iostream>

int modifyRef(int& A)
{
    A = 0;
    std::cout << "\nint& A = 0";
    return A;
}

void TakeValues(int X, int Y)
{
    std::cout << "\nX = " << X;
    std::cout << "\nY = " << Y;
}

int main()
{
    int Q = 9;
    TakeValues(modifyRef(Q), Q);
    std::cout << std::endl;
    system("pause");
    return 0;
}

This is the output I receive:

int& A = 0
X = 0
Y = 9

I would expect Y to also be 0. How is the order of operations defined for parameter binding to function calls?

(My apologies if I am not using the correct terminology.)

Josh C
  • 1,035
  • 2
  • 14
  • 27
  • Possible duplicate of [Order of Function arguments in C++](http://stackoverflow.com/questions/4772646/order-of-function-arguments-in-c) – Chris Dodd Feb 02 '16 at 04:06
  • As a trivial search will tell you, order of evaluation of arguments to a function is undefined in either C or C++, so having code that depends on it (as yours does) is undefined behavior – Chris Dodd Feb 02 '16 at 04:07
  • 5
    @ChrisDodd This one is unspecified, not undefined - it is not allowed to make your cat pregnant. – T.C. Feb 02 '16 at 05:43
  • Just a note on style: since the code correctly uses `std::` as part of the names of things from the standard library, `using namespace std;` isn't needed. Besides, it's a serious troublemaker. – Pete Becker Feb 02 '16 at 15:24
  • Worded differently, but duplicate of https://stackoverflow.com/questions/2934904/order-of-evaluation-in-c-function-parameters – Josh C Jul 30 '19 at 21:29

2 Answers2

2

The evaluation order of function arguments is unspecified. When you write:

TakeValues(modifyRef(Q), Q);

you are relying upon the fact that modifyRef(Q) to be evaluated before Q. But the evaluation order of function arguments is unspecified - it is not necessarily the case that modifyRef(Q) will be sequenced before Q nor vice versa.

In this case, Q (the second argument) gets evaluated first. So we read 9, and initialize the parameter Y with it. Then we evaluate modifyRef(Q), which zeros out Q and returns it, which leads to initializing X with 0.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • couldn't it be that Q is bound to modifyRef first, but the call isn't invoked until other instances of Q are also bound? That is what is happening right? Arguments are being bound, similar to when you start up a thread using parameters? – Josh C Feb 02 '16 at 14:55
  • @JoshC The only guarantee you have is that all the arguments will be evaluated before the function body is entered. You know that the first `Q` will be read before `modifyRef(Q)` is run, but there's no guarantee between the first and second `Q`s and between `modifyRef()` and the second `Q`. – Barry Feb 02 '16 at 16:36
  • 1
    @JoshC For instance, gcc evaluates right-to-left (so you get the "unexpected" result of 0/9) whereas clang evaluates left-to-right (so you get the "expected" result of 0/0). Both are perfectly valid, conforming results. You should never rely on either (as either compiler could change from version to version). – Barry Feb 02 '16 at 16:38
  • @Barry I agree; as for Visual Studio, I believe it is right to left in most cases. Considering the OPS original problem `TakeValues( ModifyRef(q), q );` Here the 2nd param `Y` is being set by a stack copy of `q` to whatever `q` is initialized with. However if we reverse the order to: `TakeValues( q, ModifyRef(q) );` pay attention to what happens here; both `X` & `Y` are set to what ever value `ModifyRef()` returns back from its parameter list by reference. So in this case the parameter list is being read from right to left... – Francis Cugler Feb 03 '16 at 03:04
  • (...continued) Now consider changing both function signatures to where `ModifyRef()` now has the signature `int& ModifyRef( int& A );` and `TakeValues()` now has the signature `void TakeValues( int& X, int& Y );` Then call `TakeValues()` the same as above where the parameters are in reverse order; here it is not Right to Left nor Left to Right or at least it is not obvious because the inner function regardless of its location is being evaluated first, and once it returns the modified reference `q` is now setting both `X` & `Y` in `TakeValues()`. – Francis Cugler Feb 03 '16 at 03:11
  • @FrancisCugler Order of evaluation of arguments in functions is **unspecified**. I don't know what you're talking about or why you're trying to determine the order. There is no order, and you should not write code that relies on there being one. – Barry Feb 03 '16 at 06:49
  • @Barry I'm not it is compiler dependent, I'm just stating what to be aware of and the fact that I was agreeing with you. I was just explaining the results of what appears to be the order when using Visual Studio in my case. There is no defined standard order; it completely depends on the compiler and can change from version to version. – Francis Cugler Feb 03 '16 at 07:24
  • Well to be fair here. I did ask how is it defined, I didn't ask about the standard but rather practical knowledge(at least that was my intent). Unspecified or not, there needs to be an order. – Josh C Feb 04 '16 at 00:52
  • @JoshC And I answered that question. You should write code without any ordering assumptions, because you can't make any. – Barry Feb 04 '16 at 07:40
  • @Barry: There is a difference between there being an Unspecified order and there being no order (Unsequenced). If a global `unsigned` variable `i` is initially 4, the expressions `i++;` and `i--` are evaluated in Unspecified order, and nothing else modifies `i`, `i` will end up with its original value. If the operations are Unsequenced, however, anything could happen. – supercat Mar 29 '16 at 21:24
  • @supercat I don't understand the purpose of your comment? – Barry Mar 29 '16 at 21:41
0

Try changing the signature of your functions:

int ModifyRef( int& A );

void TakeValues( int X, int Y );

to the following...

int& ModifyRef( int& A );

void TakeValues( int& X, int& Y );

and see what your output will be in main when you call this line of code:

int q = 9;

TakeValues( ModifyRef(q), q );

then reverse the order of the parameters as such

TakeValues( q, ModifyRef(q) );

and compare the results.

When I did this on my machine Win 7 64bit with VS2015 Community on an Intel Core2 Quad Extreme the results I was given for both situations when using references were the same and the output I am getting is:

I changed the value of A in ModifyRef() from 0 to 7 for testing purposes.

int& A = 7
X = 7
Y = 7

for both situations where either the inner function is the first parameter and the stand alone variable is the second or the inner function is the second parameter with the stand alone variable as the first.

Since I am receiving the same results it appears to me that do to how the compiler creates and handles the stack pointer the ModifyRef() appears to be evaluated first and once that function is finished since q is now being referenced instead of being a stack copy it is being overwritten by whatever value it is being set to within the ModifyRef function.

I also modified your ModifyRef() function slightly so that I can see what its passed in parameter is instead of having a number hard coded into its print out statement.

int& ModifyRef( int& A ) {
    A = 7; // Changed Value From 0 To 7 For Testing Purposes
    std::cout << "n\int& A = " << A;
    return A;
}

However this effect may only apply when using only references.

When I revert back to your original function signatures and I call it in this order:

q = 9;
TakeValues( ModifyRef( q ), q );

As is your original code was presented the output I am getting is:

int& A = 7
X = 7
Y = 9

However when I reverse the parameters to:

q = 9;
TakeValues( q, ModifyRef( q ) );

My output is:

int& A = 7
X = 7
Y = 7

So what I am seeing in these two situations is slightly different.

In the first order of parameters, Y or the second parameter is being set to 9 as q is initialized to 9 and Y within TakeValues() is printing out a stack copy with a value of 9. Then X is being valuated by ModifyRef() where q has a value of 9 but then it is being modified since it is a reference so when TakeValues() sets X from q, q was already changed from 9 to 7 so X is now being set to 7.

In the second order of parameters it appears that ModifyRef() is being called first and changes q from 9 to 7 so TakeValues() is setting Y to 7 and since this function is using a reference q was also changed from 9 to 7, so when the first parameter is taking q to set X q was already changed from 9 to 7.

I do not know if this is compiler dependent but at least on my machine it appears that the call stack for the parameters is happening from farthest right to left. This also makes sense when you think about it due to when functions have default values.

Example:

class foo {
    void bar( int a, int b, int c = 3 ) {
        std::cout << a << ", " << b << ", " << c << std::endl;
    }
};

All default values in a function declaration must be to the far right for you can not have:

class foo {
   void bar( int a = 1, int b, int c ) { ... } // Compile Error
};

I hope this helps to clarify what is going on within your code when you begin to call a function within a function as a parameter either if you are using passing by value(stack copy) or by reference.

Francis Cugler
  • 7,788
  • 2
  • 28
  • 59
  • Great answer! I didn't even consider changing the order of parameters could have that effect. I had started using const & for my travel_dir in my original problem. I may go back and change the order of the parameters. – Josh C Feb 02 '16 at 16:11
  • There are times when you should use const and when you shouldn't. If you know that the value should not change because it will always be constant such as PI or e then it should be a const value. If the value is to be manipulated by any mathematical operator then it should either be passed by value or by reference. Also if the function only needs a specified data set to make a decision then const ref can work, but if it needs to pass back values through its parameter list after doing calculations that other functions depend on, const ref will not work. – Francis Cugler Feb 03 '16 at 02:50
  • Another thing that will help when writing functions that belong to class objects is if you know that the function is not supposed to change values you should append the function with const: let's say this belongs to some class `A`, `float getValue() const;` usually with getters that simply return a member variable the function is declared as const as to not change any internal values. There are several different uses for const. – Francis Cugler Feb 03 '16 at 02:57