Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

Comments on What is the difference between operator precedence and order of evaluation?

Parent

What is the difference between operator precedence and order of evaluation?

+12
−1

When doing something simple such as this

int a=1;
int b=2;
int c=3;
printf("%d\n", a + b * c);

then I was told that operator precedence guarantees that the code is equivalent to
a + (b * c), since * has higher precedence than +. And so the result is guaranteed to be 7 and not 9.

However, when I modify the above example like this:

#include <stdio.h>

int a (void) { printf("%s ",__func__); return 1; }
int b (void) { printf("%s ",__func__); return 2; }
int c (void) { printf("%s ",__func__); return 3; }

int main (void)
{
  printf("%d\n", a() + b() * c());
  return 0;
}

Then I get the output a b c 7. How is this possible?

Shouldn't operator precedence guarantee that b() is executed before a()?

History
Why does this post require moderator attention?
You might want to add some details to your flag.
Why should this post be closed?

1 comment thread

General comments (10 comments)
Post
+11
−4

It is a common mistake is to mix up the concepts of operator precedence and order of evaluation. Beginner classes and books often address the former in detail, but forget to mention the latter at all.

Operator precedence specifies the order in which an expression should be parsed. It is similar to the use of operators in mathematics and tells us which operand that belongs ("glues") to which operator. In an expression such as a + b * c, operator precedence specifies that the expression must be treated as equivalent to a + (b * c).

Order of evaluation specifies the order in which an expression should be executed. That is the order in which the functions a(), b() and c() are executed. It is obvious that b() and c() must be called before b() * c() can be calculated, but it is less obvious that all three functions a(), b() and c() might be called in advance and that a() might be called first. The order in which they are called/executed is the order of evaluation.

Each operator in C and C++ specifies such an order of evaluation of its operands (given that it has more than 1 operand). In the general case, the order of evaluation is unspecified behavior, a formal term in C and C++ which means that the language standard leaves the order to the compiler. But the compiler need not document it to the programmer and the programmer can't know it and shouldn't rely on it. It can even be different from line to line in the same program.

The rationale1) behind this is to allow compilers to keep their expression parsing algorithm a trade secret, since this affects both program execution speed and time to compile. Internally, compilers go through the expression by building up an expression parser tree, similar to this:

Image alt text

The operator precedence specify how this expression tree should be built up, but the order of evaluation specifies the order this tree is traversed - could either be left-to-right or right-to-left, and it might even make sense to do it in a different order from case to case basis.

Newer C and C++ standards formally states that (example from C17 6.5/3)

The grouping of operators and operands is indicated by the syntax. Except as specified later, side effects and value computations of subexpressions are unsequenced.

This cryptic little text summarizes all of the things above. "The grouping of operators and operands is indicated by the syntax." refers to operator precedence, which is very hard to understand from reading the standard syntax alone. That is why programmers often use informal so-called "precedence tables" to easier summarize the precedence rules and operator groups.

The second sentence "Except as specified later, side effects and value computations of subexpressions are unsequenced." is what specifies that order of evaluation as unspecified behavior for most operators in the language. "Except as specified later" refers to special case operators that guarantee a certain order of evaluation. Those are for example the operators &&, ||, ?: and ,, all which have special left-to-right execution guarantees. C++17 and beyond also have special guarantees for the assignment operators. The majority of operators do not have any such guarantees though.

What all this means in practice is that we should never write code which relies on a certain order of evaluation.

Note that order of evaluation also applies to a variable initalizer lists: int arr[] = {a(), b()}; has no defined order of evaluation. And similarly, function argument evaluation order is also unspecified.


1) Rationale for International Standard - Programming Languages - C. Revision 5.10 April-2003, 3/25:

The terms unspecified behavior, undefined behavior, and implementation-defined behavior are used to categorize the result of writing programs whose properties the Standard does not, or cannot, completely describe. The goal of adopting this categorization is to allow a certain variety among implementations which permits quality of implementation to be an active force in the marketplace as well as to allow certain popular extensions, without removing the cachet of conformance to the Standard.

Unspecified behavior gives the implementor some latitude in translating programs. This latitude does not extend as far as failing to translate the program, however, because all possible behaviors are “correct” in the sense that they don’t cause undefined behavior in any implementation.

History
Why does this post require moderator attention?
You might want to add some details to your flag.

1 comment thread

General comments (6 comments)
General comments
Derek Elkins‭ wrote over 3 years ago

What evidence do you have to support that the reason for this (lack of) specification is "to allow compilers to keep their expression parsing algorithm a trade secret"? There's no reason how you parse expressions would affect execution time of the ultimate program, and I doubt how parsing is implemented (especially for C) is a significant "trade secret". The much more obvious and sensible reason is having a specified evaluation order would inhibit possible optimizations.

Lundin‭ wrote over 3 years ago · edited over 3 years ago

@Derek Elkins That's how the rationale was explained to me by someone who was a member of the ANSI C/C90 committee. ISO in particular is very picky with not giving a particular technology an advantage/disadvantage. (That's also the reason C supports dysfunctional things like one's complement and signed magnitude, etc) I can add a formal source from the C99 rationale for you.

Lundin‭ wrote over 3 years ago

@Derek Elkins Source & quote added.

Lundin‭ wrote over 3 years ago

Btw keep in mind that this was all decided in 1989-1990. Compilers back then were pretty horrible at optimizing code in general. register as a sensible manual optimization still made perfect sense. Other such primitive, manual tricks like inline had not even been considered. Modern C compilers in the year 2020 play in an entirely different league.

EJP‭ wrote over 3 years ago
  1. Operator precedence specifies in what order operators should be executed not just parsed. Order of evaluation specifies in what order operands should be evaluated. There is some interaction between these concepts, but for example it is very useful to know that in Java all operands are evaluated left to right. 2. The assertion that expression parsing algorithms are trade secrets is nonsense. They have all been well-known since 1965, and found in standard textbooks and CS courses.
Lundin‭ wrote over 3 years ago · edited over 3 years ago

@EJP That is evidently wrong. Did you run the code in the question? https://godbolt.org/z/4q74Gh. Since the output is, as proven, a b c 7, the code is executed according to order of evaluation, not according to operator precedence, or the output would be b c a. Formally, ISO 9899:2018 6.8 states "Except as indicated, statements are executed in sequence." Where sequence referes to sequenced before/after - the order of evaluation. Kindly back up your accusations with actual facts and sources.