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.

Post History

50%
+0 −0
Q&A What is the difference between operator precedence and order of evaluation?

When parsing a program, a compiler creates a tree of nodes. The operator precedence plays a role in how those nodes get organized. Once the tree is complete and considered valid, it walks the tree ...

posted 1d ago by Alexis Wilke‭  ·  edited 1d ago by Alexis Wilke‭

Answer
#3: Post edited by user avatar Alexis Wilke‭ · 2024-11-26T21:31:19Z (1 day ago)
  • When parsing a program, a compiler creates a tree of nodes. The operator precedence plays a role in how those nodes get organized. Once the tree is complete and considered valid, it walks the tree and outputs the corresponding instructions (in case of a C/C++ compiler, the instructions are assembly code; for other types of languages, such as Java, it may be "byte code"; for JavaScript it used to be kept in a tree of nodes, now we have basic assembly code in most cases).
  • So for an operation such as `a + b * c`, you get:
  • +
  • / \
  • / *
  • a / \
  • b c
  • Then we walk the tree from left to right. That means we find a `+` and go toward the left side where we find `a`. We get that value (which in your case means calling the `a()` function).
  • Then we walk back up to the `+` and walk down toward the `*`. The `*` has two branches, we first follow the one of the left and find the `b`.
  • Once `b` was saved, we come back to the `*` and go down the other side and find `c`. Again, we evaluate `c`.
  • Now that we have all the branch of the `*`, it means we have all the values and we can therefore perform the `*` evaluation and save that temporary result (i.e. `temp = b * c`). Save that result and go up again.
  • Finally, we are back on the `+` node, we have `a` and `temp` and we can compute the addition and return that as the result of the expression.
  • In assembly language, we most often use the stack to save the temporary values. With Intel processor and an ABI that returns the result in register `RAX`, we would get something more or less like this:
  • CALL a
  • PUSH RAX
  • CALL b
  • PUSH RAX
  • CALL c
  • PUSH RAX ; assuming no optimizations
  • POP RAX
  • POP RCX
  • IMUL RAX, RCX
  • PUSH RAX ; again, assuming no optimizations
  • POP RAX
  • POP RCX
  • ADD RAX, RCX
  • RET
  • From all the compilers I know of, none of them would reorder the `CALL` because that's part of the definition of the language.
  • There is one weird one, the cl compiler on MS-Windows will compute the parameters to a function call from right to left. This can be a gotcha if you want to make that same code work on other systems (Linux... are there others these days?!) since the convention is to do it left to right.
  • call(p1(), p2(), p3(), p4());
  • // Microsoft compiler:
  • _p4 = p4();
  • _p3 = p3();
  • _p2 = p2();
  • _p1 = p1();
  • call(p1, p2, p3, p4);
  • // Linux compiler:
  • _p1 = p1();
  • _p2 = p2();
  • _p3 = p3();
  • _p4 = p4();
  • []() call(p1, p2, p3, p4);
  • I'd bet there are other exceptions. This is the one I know of.
  • When parsing a program, a compiler creates a tree of nodes. The operator precedence plays a role in how those nodes get organized. Once the tree is complete and considered valid, it walks the tree and outputs the corresponding instructions (in case of a C/C++ compiler, the instructions are assembly code; for other types of languages, such as Java, it may be "byte code"; for JavaScript it used to be kept in a tree of nodes, now we have basic assembly code in most cases).
  • So for an operation such as `a + b * c`, you get:
  • +
  • / \
  • / *
  • a / \
  • b c
  • Then we walk the tree from left to right. That means we find a `+` and go toward the left side where we find `a`. We get that value (which in your case means calling the `a()` function).
  • Then we walk back up to the `+` and walk down toward the `*`. The `*` has two branches, we first follow the one of the left and find the `b`.
  • Once `b` was saved, we come back to the `*` and go down the other side and find `c`. Again, we evaluate `c`.
  • Now that we have all the branch of the `*`, it means we have all the values and we can therefore perform the `*` evaluation and save that temporary result (i.e. `temp = b * c`). Save that result and go up again.
  • Finally, we are back on the `+` node, we have `a` and `temp` and we can compute the addition and return that as the result of the expression.
  • In assembly language, we most often use the stack to save the temporary values. With Intel processor and an ABI that returns the result in register `RAX`, we would get something more or less like this:
  • CALL a
  • PUSH RAX
  • CALL b
  • PUSH RAX
  • CALL c
  • PUSH RAX ; assuming no optimizations
  • POP RAX
  • POP RCX
  • IMUL RAX, RCX
  • PUSH RAX ; again, assuming no optimizations
  • POP RAX
  • POP RCX
  • ADD RAX, RCX
  • RET
  • From all the compilers I know of, none of them would reorder the `CALL` because that's part of the definition of the language.
  • There is one weird one, the cl compiler on MS-Windows will compute the parameters to a function call from right to left. This can be a gotcha if you want to make that same code work on other systems (Linux... are there others these days?!) since the convention is to do it left to right.
  • call(p1(), p2(), p3(), p4());
  • // Microsoft compiler:
  • _p4 = p4();
  • _p3 = p3();
  • _p2 = p2();
  • _p1 = p1();
  • call(p1, p2, p3, p4);
  • // Linux compiler:
  • _p1 = p1();
  • _p2 = p2();
  • _p3 = p3();
  • _p4 = p4();
  • call(p1, p2, p3, p4);
  • I'd bet there are other exceptions. This is the one I know of.
#2: Post edited by user avatar Alexis Wilke‭ · 2024-11-26T21:30:33Z (1 day ago)
Fixed formatting.
  • When parsing a program, a compiler creates a tree of nodes. The operator precedence plays a role in how those nodes get organized. Once the tree is complete and considered valid, it walks the tree and outputs the corresponding instructions (in case of a C/C++ compiler, the instructions are assembly code; for other types of languages, such as Java, it may be "byte code"; for JavaScript it used to be kept in a tree of nodes, now we have basic assembly code in most cases).
  • So for an operation such as `a + b * c`, you get:
  • +
  • / \
  • / *
  • a / \
  • b c
  • Then we walk the tree from left to right. That means we find a `+` and go toward the left side where we find `a`. We get that value (which in your case means calling the `a()` function).
  • Then we walk back up to the `+` and walk down toward the `*`. The `*` has two branches, we first follow the one of the left and find the `b`.
  • Once `b` was saved, we come back to the `*` and go down the other side and find `c`. Again, we evaluate `c`.
  • Now that we have all the branch of the `*`, it means we have all the values and we can therefore perform the `*` evaluation and save that temporary result (i.e. `temp = b * c`). Save that result and go up again.
  • Finally, we are back on the `+` node, we have `a` and `temp` and we can compute the addition and return that as the result of the expression.
  • In assembly language, we most often use the stack to save the temporary values. With Intel processor and an ABI that returns the result in register `RAX`, we would get something more or less like this:
  • CALL a
  • PUSH RAX
  • CALL b
  • PUSH RAX
  • CALL c
  • PUSH RAX ; assuming no optimizations
  • POP RAX
  • POP RCX
  • IMUL RAX, RCX
  • PUSH RAX ; again, assuming no optimizations
  • POP RAX
  • POP RCX
  • ADD RAX, RCX
  • RET
  • From all the compilers I know of, none of them would reorder the `CALL` because that's part of the definition of the language.
  • There is one weird one, the cl compiler on MS-Windows will compute the parameters to a function call from right to left. This can be a gotcha if you want to make that same code work on other systems (Linux... are there others these days?!) since the convention is to do it left to right.
  • call(p1(), p2(), p3(), p4());
  • // Microsoft compiler:
  • _p4 = p4();
  • _p3 = p3();
  • _p2 = p2();
  • _p1 = p1();
  • call(p1, p2, p3, p4);
  • // Linux compiler:
  • _p1 = p1();
  • _p2 = p2();
  • _p3 = p3();
  • _p4 = p4();
  • call(p1, p2, p3, p4);
  • I'd bet there are other exceptions. This is the one I know of.
  • When parsing a program, a compiler creates a tree of nodes. The operator precedence plays a role in how those nodes get organized. Once the tree is complete and considered valid, it walks the tree and outputs the corresponding instructions (in case of a C/C++ compiler, the instructions are assembly code; for other types of languages, such as Java, it may be "byte code"; for JavaScript it used to be kept in a tree of nodes, now we have basic assembly code in most cases).
  • So for an operation such as `a + b * c`, you get:
  • +
  • / \
  • / *
  • a / \
  • b c
  • Then we walk the tree from left to right. That means we find a `+` and go toward the left side where we find `a`. We get that value (which in your case means calling the `a()` function).
  • Then we walk back up to the `+` and walk down toward the `*`. The `*` has two branches, we first follow the one of the left and find the `b`.
  • Once `b` was saved, we come back to the `*` and go down the other side and find `c`. Again, we evaluate `c`.
  • Now that we have all the branch of the `*`, it means we have all the values and we can therefore perform the `*` evaluation and save that temporary result (i.e. `temp = b * c`). Save that result and go up again.
  • Finally, we are back on the `+` node, we have `a` and `temp` and we can compute the addition and return that as the result of the expression.
  • In assembly language, we most often use the stack to save the temporary values. With Intel processor and an ABI that returns the result in register `RAX`, we would get something more or less like this:
  • CALL a
  • PUSH RAX
  • CALL b
  • PUSH RAX
  • CALL c
  • PUSH RAX ; assuming no optimizations
  • POP RAX
  • POP RCX
  • IMUL RAX, RCX
  • PUSH RAX ; again, assuming no optimizations
  • POP RAX
  • POP RCX
  • ADD RAX, RCX
  • RET
  • From all the compilers I know of, none of them would reorder the `CALL` because that's part of the definition of the language.
  • There is one weird one, the cl compiler on MS-Windows will compute the parameters to a function call from right to left. This can be a gotcha if you want to make that same code work on other systems (Linux... are there others these days?!) since the convention is to do it left to right.
  • call(p1(), p2(), p3(), p4());
  • // Microsoft compiler:
  • _p4 = p4();
  • _p3 = p3();
  • _p2 = p2();
  • _p1 = p1();
  • call(p1, p2, p3, p4);
  • // Linux compiler:
  • _p1 = p1();
  • _p2 = p2();
  • _p3 = p3();
  • _p4 = p4();
  • []() call(p1, p2, p3, p4);
  • I'd bet there are other exceptions. This is the one I know of.
#1: Initial revision by user avatar Alexis Wilke‭ · 2024-11-26T21:29:11Z (1 day ago)
When parsing a program, a compiler creates a tree of nodes. The operator precedence plays a role in how those nodes get organized. Once the tree is complete and considered valid, it walks the tree and outputs the corresponding instructions (in case of a C/C++ compiler, the instructions are assembly code; for other types of languages, such as Java, it may be "byte code"; for JavaScript it used to be kept in a tree of nodes, now we have basic assembly code in most cases).

So for an operation such as `a + b * c`, you get:

       +
      / \
     /  *
    a  / \
      b   c

Then we walk the tree from left to right. That means we find a `+` and go toward the left side where we find `a`. We get that value (which in your case means calling the `a()` function).

Then we walk back up to the `+` and walk down toward the `*`. The `*` has two branches, we first follow the one of the left and find the `b`.

Once `b` was saved, we come back to the `*` and go down the other side and find `c`. Again, we evaluate `c`.

Now that we have all the branch of the `*`, it means we have all the values and we can therefore perform the `*` evaluation and save that temporary result (i.e. `temp = b * c`). Save that result and go up again.

Finally, we are back on the `+` node, we have `a` and `temp` and we can compute the addition and return that as the result of the expression.

In assembly language, we most often use the stack to save the temporary values. With Intel processor and an ABI that returns the result in register `RAX`, we would get something more or less like this:

    CALL a
    PUSH RAX
    CALL b
    PUSH RAX
    CALL c
    PUSH RAX  ; assuming no optimizations
    POP RAX
    POP RCX
    IMUL RAX, RCX
    PUSH RAX  ; again, assuming no optimizations
    POP RAX
    POP RCX
    ADD RAX, RCX
    RET

From all the compilers I know of, none of them would reorder the `CALL` because that's part of the definition of the language.

There is one weird one, the cl compiler on MS-Windows will compute the parameters to a function call from right to left. This can be a gotcha if you want to make that same code work on other systems (Linux... are there others these days?!) since the convention is to do it left to right.

   call(p1(), p2(), p3(), p4());

   // Microsoft compiler:
   _p4 = p4();
   _p3 = p3();
   _p2 = p2();
   _p1 = p1();
   call(p1, p2, p3, p4);

   // Linux compiler:
   _p1 = p1();
   _p2 = p2();
   _p3 = p3();
   _p4 = p4();
   call(p1, p2, p3, p4);

I'd bet there are other exceptions. This is the one I know of.