(Last Mod: 27 November 2010 21:38:37 )
An "expression" is defined by the C Language Standard as "a sequence of operators and operands that specifies computation of a value, or that designates an object or a function, or that generates side effects, or that performs a combination thereof."
An "object" is defined as a region of data storage the contents of which can represent values. We may or may not be able to change the contents of the object - if we can the object is said to be "modifiable".
From a practical standpoint, objects have addresses that can be computed - values that indicate where in memory the object is stored. This makes them different from constants which are (or at least might be) stored as literal values within the body of the executable code itself.
It's important to note that the definition and use of the term "object" in Standard C is unrelated to the definition and use of the same term in either C++ or the broader field of Object Oriented Programming (OOP). While there is some common ground, the concepts are noticeably different.
Modifiable objects can be the target of assignment operators. The target of an assignment operator is simply the object where the new value is to be stored. Since the target is determined by the expression on the left of an assignment operator, the expression must produce, after it is evaluated, an "lvalue". Simply put, an lvalue is any value that can legally be used on the left side of an assignment operator, hence it must be a modifiable object.
An "operator" is a symbol (may consist of one or more than one character) that specifies some operation to be performed. The entity on which the operator acts are it's operands. All operators in C can be grouped into one of three categories, depending on how many operands they act on. Unary operators act on a single operand, binary operators act on exactly two operands, and one operator, the conditional operator, is a ternary operator because it acts on three operands.
Of the 45 operators in C, 35 are represented by a unique set of symbols. For instance, two ampersand characters together are the symbol that represents the logical AND operator. Anytime the compiler encounters this symbol (unless it is in a comment, a character constant, or a string literal), it will interpret it as being the logical AND operator.
The remaining ten operators are represented by a five symbols, each of which can represent one of two possible operators. These are the set consisting of {*,+,-,&,()}. Any given instance represents just one of the possibilities and which it is depends on the context of usage. In the first four cases, one potential operator is a unary operator and the other is binary operator. The compiler simply determines how many operands are present and uses the corresponding operator.
Symbol | Unary | Binary |
* | Pointer Dereference | Multiplication |
- | Negative Sign | Subtraction |
+ | Positive Sign | Addition |
& | Address | Bitwise AND |
In the case of the parentheses there are actually four possible interpretations: The first is that they indicate the control expression for one of the flow-control statements. This is the case if the token immediately preceding the left-paren is one of the keywords {if, for, while, switch}. The second is that the contents within the parentheses is the argument list for a function call. This is the case if the token immediately preceding the left-paren is an identifier. The third is that they represent a type cast operator. This is the case if the contents within the parentheses is an object type. If none of these three apply, then they are the expression evaluation operator which merely forces complete evaluation of the expression within the parentheses before that value is used in the larger expression of which it may be a part. This is the same use that you are familiar with from normal usage in mathematics.
Operands are the values and/or objects that an operator acts on. Depending on the operator, it may retrieve a value from an object in order to perform some computation to yield the expression's value or it might modify the contents of the operand.
If the contents of an object is modified (even if the new value happens to be the same as the old value) as a result of evaluating an expression, then the expression is said to have produced a side effect. In the case of several operators, most notably the assignment operators and the increment/decrement operators, we generally use the operator not for the value of the expression that is produced, but for the side effect that results from evaluating the expression that contains them.
It is important to keep in mind there there are two distinct things occurring whenever there is a side effect - the first is determining what the value of the expression is and the second is modifying the value stored in the object. We don't know when the value of the modified object will actually get modified - the compiler is given wide latitude in how it handles this. It may alter the contents as it executes the operator or it may simply keep track of what the new value needs to be and, after evaluating the entire expression, update all modified object before proceeding to the next statement. Furthermore, it is given a great deal of latitude regarding the order in which operands themselves are evaluated.
As a result, it is never safe to use the value of a modified object anywhere else within the expression that modifies it or to modify the same object more than once within the same expression.