1

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.

Community
  • 1
  • 1
jensa
  • 2,792
  • 2
  • 21
  • 36
  • C and C++ are different languages. If you are interested in gettng an answer for both, you should mention them explicitly in you question. As for now, you are only asking about C++. – too honest for this site Jul 13 '15 at 19:49
  • 1
    "Arguments are passed from right to left, and placed on the stack": Think about the difference between "passing" and "evaluation". You might want to read the only authoritative source: the [standard](http://port70.net/~nsz/c/c11/n1570.html) - well, its the final draft, but that makes no difference. A good serach-term would be _sequence points_ – too honest for this site Jul 13 '15 at 19:50
  • 1
    As you said, it doesn't matter in this case, because the argument szBuffer is just a pointer. This pointer doesn't change, even if the snprintf expression would be evaluated later. Just the buffer to where szBuffer points does change. – nv3 Jul 13 '15 at 19:53
  • Thanks for your comments, I understand my confusion boiled down to distinguishing between "passing" and "evaluation". Order of evaluation: unspecified, argument passing order: right-to-left. – jensa Jul 13 '15 at 20:13
  • @jensa Argument passing order not necessarily right-to-left. On 32 bit Windows with cdecl yes. On 64 bit Windows, the calling convention is a register based convention. – David Heffernan Jul 13 '15 at 20:16
  • @DavidHeffernan Sorry, I wrote my comment without specifying that I had __cdecl or __stdcall in mind, I should have been more explicit. If I explicitly declare my functions with __cdecl even on a 64 bit windows, I "force" right-to-left argument passing? – jensa Jul 13 '15 at 20:18
  • No. Because on x64 arguments are not passed on the stack. They are passed in registers. And there is only one calling convention anyway. When registers have been exhausted, then the stack is used. Calling conventions are well documented. – David Heffernan Jul 13 '15 at 20:26

2 Answers2

6

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

cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • Thank you, a very helpful answer! They way you described the char* was exactly how I thought it might be, which would make it not matter. – jensa Jul 13 '15 at 20:10
-3

I would agree that it depends on the calling convention, because the standard does not specify the order. See also: Compilers and argument order of evaluation in C++

And I would also agree that is does not matter in this case, because the snprintf is always evaluated before the TextOut - and the buffer gets filled.