I'm confused about in what order function arguments are evaluated when calling a C++ function. I have probably interepreted something wrong, so please explain if that is the case.
As an example, the legendary book "Programming Windows" by Charles Petzold contains code like this:
// hdc = handle to device context
// x, y = coordinates of where to output text
char szBuffer[64];
TextOut(hdc, x, y, szBuffer, snprintf(szBuffer, 64, "My text goes here"));
Now, the last argument is
snprintf(szBuffer, 64, "My text goes here")
which returns the number of characters written to the char[] szBuffer. It also writes the text "My text goes here" to the char[] szBuffer. The fourth argument is szBuffer, which contains the text to be written. However, we can see that szBuffer is filled in the fifth argument, telling us that somehow is the expression
// argument 5
snprintf(szBuffer, 64, "My text goes here")
evaluated before
// argument 4
szBuffer
Okay, fine. Is this always the case? Evaluation is always done from right to left? Looking at the default calling convention __cdecl:
The main characteristics of __cdecl calling convention are:
Arguments are passed from right to left, and placed on the stack.
Stack cleanup is performed by the caller.
Function name is decorated by prefixing it with an underscore character '_' .
(Source: Calling conventions demystified) (Source: MSDN on __cdecl)
It says "Arguments are passed from right to left, and placed on the stack". Does this mean that the rightmost/last argument in a function call is always evaluated first? Then the next to last etc? The same goes for the calling convention __stdcall, it also specified a right-to-left argument passing order.
At the same time, I came across posts like this:
How are arguments evaluated in a function call?
In that post the answers say (and they're quoting the standard) that the order is unspecified.
Finally, when Charles Petzold writes
TextOut(hdc, x, y, szBuffer, snprintf(szBuffer, 64, "My text goes here"));
maybe it doesn't matter? Because even if
szBuffer
is evaluated before
snprintf(szBuffer, 64, "My text goes here")
the function TextOut is called with a char* (pointing to the first character in szBuffer), and since all arguments are evaluated before the TextOut function proceeds it doesn't matter in this particular case which gets evaluated first.
In this case it does not matter.
By passing szBuffer
to a function that accepts a char *
(or char const *
) argument, the array decays to a pointer. The pointer value is independent of the actual data stored in the array, and the pointer value will be the same in both cases no matter whether the fourth or fifth argument to TextOut()
gets fully evaluated first. Even if the fourth argument is fully evaluated first, it will evaluate as a pointer to data -- the pointed-to data is what gets changed, not the pointer itself.
To answer your posed question: the actual order of argument evaluation is unspecified. For example, in the statement f(g(), h())
, a compliant compiler can execute g()
and h()
in any order. Further, in the statement f(g(h()), i())
, the compiler can execute the three functions g
, h
, and i
in any order with the constraint that h()
gets executed before g()
-- so it could execute h()
, then i()
, then g()
.
It just happens that in this specific case, evaluation order of arguments is wholly irrelevant.
(None of this behavior is dependent on calling convention, which only deals with how the arguments are communicated to the called function. The calling convention does not address in any way the order in which those arguments are evaluated.)