1

It is well known that the evaluation order of actual arguments varies from one C compiler to the other. But as ISO 9899:1999 states in §6.5.2.2.10:

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.

I never came across a compiler which generates code where the function designator is evaluated after or interleaved with the actual arguments since I started using C in the eighties. Is it therefore "safe" to use the following (simplified) code in an application:

void* self;
(self = getPointerToObject())->classPtr->methodX(self);

or is it really necessary to do something like:

void* self;
int (*methodPtr)(void*);
(methodPtr = (self = getPointerToObject())->classPtr->methodX, methodPtr(self));

to get an explicit sequence point between the function designator and argument evaluation (at some performance cost)?

Are there C compilers out there which would generate code where the first code snipped would not work (i.e. feed an undefined self argument to methodX)?

Rochus
  • 69
  • 6
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/240270/discussion-on-question-by-rochus-c-order-of-evaluation-function-designator-vs-a). – Machavity Dec 19 '21 at 16:05
  • @Machavity What do I have to do to see the whole discussion? Parts of the discussion seem to be missing since you move it. – Rochus Dec 19 '21 at 16:50
  • Some comments were removed for being unconstructive. If you feel there was an issue, raise a moderator flag on the post. – Machavity Dec 19 '21 at 20:46
  • Re “I never came across a compiler which… ”: How do you know that? Say you have compiler X, and you compiled programs P0, P1, P2,… Pn with it, and, in none of those programs did X generate code which evaluated a function argument before its associated function designator. That would not give you the knowledge that X does not do that, because it can be that X does that for some untested program P973… – Eric Postpischil Dec 19 '21 at 21:09
  • … E.g., perhaps with some sufficiently complicated function designator expression, in which there are subexpressions substantially identical to or overlapping with an argument, optimization would complete the argument evaluation before completing the rest of the complicated function designator expression. You could not know that does not occur from testing alone, so it would require compiler documentation or examining source code or other documents. Did you do that? If not, how do you know? – Eric Postpischil Dec 19 '21 at 21:10
  • Suppose a program contains code like `if (x) { (self = getPointerToObject())->classPtr->methodX(self); … } else { … }`. When there is undefined behavior on one path, optimizers may collapse it—since any behavior is allowed for the then-path, we can choose it to be the behavior that is on the else-path. Then the two paths are identical, and we can eliminate the `if (x)` and the then-path, leaving just the code inside the `else`. From one perspective, we do this because it is allowed by the C standard and it lets us reduce the program. From another perspective, we can reason… – Eric Postpischil Dec 19 '21 at 21:23
  • … that, if the programmer is writing defined code, then `x` must never be true in this code, so the code always takes the else-path anyway, so we can just throw away the other code. Now, suppose a compiler does happen to be designed so that it evaluates function designators, including side effects, prior to function arguments. Even so, its optimizer may see the above issues and reduce the code. Thus, writing this code with undefined behavior can cause your program not to behave as you desire even in the presence of the hypothesized compiler design. – Eric Postpischil Dec 19 '21 at 21:25
  • @Eric Postpischil "How do you know that?" because I use the pattern of the first example since many years and the applications would have crashed for sure if the compiler would pass an uninitialized $t1. Of course it's still possible (although unlikely) that it was pure luck. I asked the question here because of a recent discussion. I'm aware that even if no one here knows of such a compiler it's still not hundered percent sure - some say, induction is not the method of science ;-) – Rochus Dec 19 '21 at 21:34
  • Concerning complex designators: here is a benchmark application with some rather complex designators: http://software.rochus-keller.ch/Are-we-fast-yet_ObxIDE_0.9.28_cgen.zip. It runs on different platforms/architectures and compilers (gcc, clang, tcc, different versions) with no crash. – Rochus Dec 19 '21 at 21:35
  • Meanwhile I have implemented a version of the code generator which uses the pattern of the second example: http://software.rochus-keller.ch/are-we-fast-yet_ObxIDE_0.9.29_cgen_OBX_FUNC_SEQ_POINT_on.zip. It runs slightly slower, but the difference is very close to the estimated measurement error. – Rochus Dec 19 '21 at 22:02
  • `(self = getPointerToObject())->classPtr` Since `self` is a void pointer how does this even compile? – Lundin Dec 20 '21 at 15:16
  • That's why I wrote "simplified". All the typecasts just clutter the core concept without added value. – Rochus Dec 20 '21 at 15:28

1 Answers1

2

C99 defines the following sequence points:

  • The call to a function, after the arguments have been evaluated (6.5.2.2).
  • The end of the first operand of the following operators: logical AND && (6.5.13); logical OR || (6.5.14); conditional ? (6.5.15); comma , (6.5.17).
  • The end of a full declarator: declarators (6.7.5);
  • The end of a full expression: an initializer (6.7.8); the expression in an expression statement (6.8.3); the controlling expression of a selection statement (if or switch) (6.8.4); the controlling expression of a while or do statement (6.8.5); each of the expressions of a for statement (6.8.5.3); the expression in a return statement (6.8.6.4).
  • Immediately before a library function returns (7.1.4).
  • After the actions associated with each formatted input/output function conversion specifier (7.19.6, 7.24.2).
  • Immediately before and immediately after each call to a comparison function, and also between any call to a comparison function and any movement of the objects passed as arguments to that call (7.20.5).

Conclusion: It is not safe. The fact that it works by accident when you tested it doesn't mean all compilers will always behave like that.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
martinkunev
  • 1,364
  • 18
  • 39
  • That's not the answer to the question; it just repeats what the standard says. – Rochus Dec 19 '21 at 12:05
  • 2
    @Rochus *That's not the answer to the question; it just repeats what the standard says.* Huh!?!?! If a quote from the C standard answers a question regarding the C programming language, there is no more authoritative ***answer***. Such a quote ***is*** the answer. – Andrew Henle Dec 19 '21 at 12:15
  • 1
    @Andrew Henle As you see I'm familiar with the standard myself. The question is not what is inside the standard, but rather how is the standard applied in practical compilers. The wording about the same topic in the C11 standard raises even more such questions. – Rochus Dec 19 '21 at 12:38
  • @Andrew Henle apparently; and you didn't provide an answer to the question yet; thanks anyway for your time. – Rochus Dec 19 '21 at 12:56
  • This answer uses non-normative text from Annex C, so it is not authoritative, and it does not address what the consequences of supplementing the standard with the contemplated premise (that the compiler will evaluate the function designator first) would be. – Eric Postpischil Dec 19 '21 at 21:06