order of function arguments

Started by
9 comments, last by setiz1 14 years, 7 months ago
Hi folks Are there any set order in which arguments to a function are executed? Shared_ptr's document says: === Avoid using unnamed shared_ptr temporaries to save typing; to see why this is dangerous, consider this example:

void f(shared_ptr<int>, int);
int g();
void ok()
{
    shared_ptr<int> p(new int(2));
    f(p, g());
}
void bad()
{
    f(shared_ptr<int>(new int(2)), g());
}

The function ok follows the guideline to the letter, whereas bad constructs the temporary shared_ptr in place, admitting the possibility of a memory leak. Since function arguments are evaluated in unspecified order, it is possible for new int(2) to be evaluated first, g() second, and we may never get to the shared_ptr constructor if g throws an exception. See Herb Sutter's treatment (also here) of the issue for more information." === Particularly interesting was Herb Sutter's treatment which I read briefly just re-iterates NOT to use new within a function call (like the function bad( ) above). However, going through TICPP code on auto_ptr, there's a section on description of auto_ptr such:

auto_ptr<TraceHeap> pMyObject(new TraceHeap(5),2); //I added the second argument for the sake of discussion (1)

Now if and only if allocation of TraceHeap(5) fails (and subsequent exception raised, wouldn't that imply that the second argument "2" is never called? i.e., its arbritrary and possibly dangerous to use (1) above? Thanks folks! :-)
Advertisement
From the holy standard:
Quote:ISO/IEC 14882:2003(E), Chapter 8: Declarators.
9. [...]The order of evaluation of function arguments is unspecified[...]


More information (Preliminary Note: Sequence points).



In this context, do not confuse the argument seperator with the sequence operator, which both happen to look the same (","), but are distinct. I mention this because according to Section 1.9, Item 18, the sequence operator as in
    int a = foo(), bar();

forms a sequence point and it is defined which one is evaluated first, but in
    foo(foo(), bar())

there is none, in one environment, foo() is evaluated first, in another it will be bar(), and on some, they might be exectued simultaneously.

Btw, Item 17 of 1.9 says this:
Quote:When calling a function (whether or not the function is inline), there is a sequence point after the evaluation of all function arguments (if any) which takes place before execution of any expressions or statements in the function body.


This basically means that in ...
    void foo (int, int) {        cout << "foo!\n";    }    int main () {        foo (frob(), y=3);    }
... the expressions "frob()" and "y=3" are guaranteed to be evaulated before "cout << "foo!\n";" is reached. But the order of evaluation of "frob()" and "y=3" is not defined.


Here's the rest of item 17, in case you are interested:
Quote:There is also a sequence point after the copying of a returned value and before the execution
of any expressions outside the function11). Several contexts in C + + cause evaluation of a function
call, even though no corresponding function call syntax appears in the translation unit. [Example: evaluation
of a new expression invokes one or more allocation and constructor functions; see 5.3.4. For another
example, invocation of a conversion function (12.3.2) can arise in contexts in which no function call syntax
appears. ] The sequence points at function-entry and function-exit (as described above) are features of the
function calls as evaluated, whatever the syntax of the expression that calls the function might be.
Thanks phresnel. Makes sense as a programmer to me but what doesn't make sense is why the specification people put such a restriction. Is it due to differing way in which stacks are laid out on each architecture (assuming that the arguments are pushed onto the stack)? Or, is it because of something else which I am unfamiliar with?

Quote:Original post by setiz1
Now if and only if allocation of TraceHeap(5) fails (and subsequent exception raised), wouldn't that imply that the second argument "2" is never called?

I don't see your point. It does not matter if "2" is evaluated first, last or inbetween, because it doesn't have any side effects. Can you elaborate?
Quote:Original post by DevFred
Quote:Original post by setiz1
Now if and only if allocation of TraceHeap(5) fails (and subsequent exception raised), wouldn't that imply that the second argument "2" is never called?

I don't see your point. It does not matter if "2" is evaluated first, last or inbetween, because it doesn't have any side effects. Can you elaborate?

Consider:
struct Foo{    Foo(const int&)    {        throw 0;    }};void f(int* x, Foo y);void g(int* x, int y);int main(){    f(new int(2), 2);    g(new int(2), 2);}


Now what happens when you call f and the first parameter is evaluated first?
NextWar: The Quest for Earth available now for Windows Phone 7.
"Is it due to differing way in which stacks are laid out on each architecture (assuming that the arguments are pushed onto the stack)?"

The would be one reason; another might be the complexity of the parser/code generator in the compiler.

It might make sense to evaluate the arguments left-to-right, for example, if you're backing out of a tree having accepted the expression and you generate the code for the evaluation parts as you back out.

Apologies if I haven't been clearer.

What I mean is if allocation of new TraceHeap(5) fails, passing the value "2" to a function will fail right? So, if "2" isn't called first(since the order of arguments being passed is not guaranteed by the C++ spec itself) and if exception is raised while dynamically allocating the first argument(new TraceHeap(5) above) then what is the expected outcome? Is it architecturally dependent, is it compiler dependent? This is what I mean.

I got the point that new TraceHeap(5) statement should be self-contained in an object itself but what I don't understand is how if
auto_ptr<TraceHeap> pMyObject(new TraceHeap(5),2);

is used, the run-time system deals with the above discussed scenario. :-)

http://en.wikipedia.org/wiki/Sequence_point has this to say:
Quote:
Before a function is entered in a function call. The order in which the arguments are evaluated is not specified, but this sequence point means that all of their side effects are complete before the function is entered. In the expression f(i++) + g(j++) + h(k++), f is called with a parameter of the original value of i, but i is incremented before entering the body of f. Similarly, j and k are updated before entering g and h respectively. However, it is not specified in which order f(), g(), h() are executed, nor in which order i, j, k are incremented. The values of j and k in the body of f are therefore undefined.[3] Note that a function call f(a,b,c) is not a use of the comma operator and the order of evaluation for a, b, and c is unspecified.


Thanks Devfred! :-)
Quote:Original post by setiz1
such a restriction.


Note that there's a lot of undefined behaviour in C++ so that the compiler has more freedom to mutate your code, producing faster code. What you call a restriction (on your side) is a freedom on the compiler side.

Quote:Original post by Sc4Freak
Quote:Original post by DevFred
Quote:Original post by setiz1
Now if and only if allocation of TraceHeap(5) fails (and subsequent exception raised), wouldn't that imply that the second argument "2" is never called?

I don't see your point. It does not matter if "2" is evaluated first, last or inbetween, because it doesn't have any side effects. Can you elaborate?

Consider:
struct Foo{    Foo(const int&)    {        throw 0;    }};void f(int* x, Foo y);void g(int* x, int y);int main(){    f(new int(2), 2);    g(new int(2), 2);}


Now what happens when you call f and the first parameter is evaluated first?



Heh Sc4Freak :-)
This one's just your modification heh! :-)
struct Foo{    Foo(const int&)    {        throw 0;    }	int a;};void f(int* x, Foo y){	std::cout << y.a << std::endl;}//void g(Foo* x, int y){}int main(){    f(new int(2), 2); // 2 != typeof(Foo)!! Happily compiles+links on MSVC and halts.    //g(new Foo(2), 2);}

Thanks guys. This discussion isn't leading anywhere productive. But thanks genuinely to you all :-)
Quote:Original post by setiz1
if allocation of new TraceHeap(5) fails, passing the value "2" to a function will fail right?

I'm still now following you. Do you mean the number 2 of type int, or is 2 just a placeholder for some complex expression? As soon as evaluating an argument fails, an exception is thrown and evaluation of the arguments won't continue.

Quote:Original post by setiz1
if "2" isn't called first [...] and if exception is raised while dynamically allocating the first argument [...] then what is the expected outcome?

std::bad_alloc is thrown.

This topic is closed to new replies.

Advertisement