Expressions


Expression Subcontexts · Classes of Expressions · Class Conversions · Type Conversions · Promoting · Balancing · Assigning and Type Casting · Changing Representations · Pointer Arithmetic · Reading Expressions · Grouping · Operator Summary · Order of Evaluation

Add Assign · Add · Address of · Assignment · Bitwise AND Assign · Bitwise AND · Bitwise Exclusive OR Assign · Bitwise Exclusive OR · Bitwise Inclusive OR Assign · Bitwise Inclusive OR · Bitwise NOT · Comma · Conditional · Divide Assign · Divide · Equals · Function Call · Greater Than Or Equal · Greater Than · Indirection · Left Shift Assign · Left Shift · Less Than Or Equal · Less Than · Logical AND · Logical NOT · Logical OR · · Multiply Assign · Multiply · Not Equals · Plus · Point at · Postdecrement · Postincrement · Predecrement · Preincrement · Remainder Assign · Remainder · Right Shift Assign · Right Shift · Select · sizeof · Subscript · Subtract Assign · Subtract · Type Cast


You write expressions to determine values, to alter values stored in objects, and to call functions that perform input and output. In fact, you express all computations in the program by writing expressions.

The translator itself must evaluate some of the expressions you write to determine properties of the program. The translator or the target environment must evaluate other expressions prior to program startup to determine the initial values stored in objects with static duration. The program evaluates the remaining expressions when it executes.

This document describes the different classes of expressions and the restrictions on each class. It presents the common rules for writing all expressions, determining their types, and computing their values. It also discusses the constraints on the flow of control through an expression.

Expression Subcontexts

Within a statement or declaration, every full expression that you write inhabits one of three contexts:

More generally, however, every full expression or subexpression that you write inhabits one of four expression subcontexts, depending on its goal:

An rvalue subcontext specifies a value that has an object type other than an array type. You create an rvalue subcontext wherever you need to specify a test or value to the translator, determine an initial value prior to program startup or compute a value when the program executes.

An lvalue subcontext designates an object, but its expression can have either an object type or an incomplete type. You create an lvalue subcontext wherever you need to access the value stored in an object, alter the stored value, or determine the address of the object. (If the type is incomplete, you can determine only the address of the object.)

A function-designator subcontext designates a function. Hence, its expression has a function type. You create a function-designator subcontext wherever you need to call a function or determine its address.

A side-effects subcontext specifies no value and designates no object or function. Hence, its expression can have a void type. You create a side-effects subcontext when you need to cause only side effects.

For example, consider the following code sequence:

void f(int);
int x;
f(x = 3);

In the last line:

Classes of Expressions

Every expression that you write belongs to one of several expression classes, depending upon its form. Four of these classes are closely associated with the four expression subcontexts:

The first two of these classes have a number of subclasses. For instance, an arbitrary rvalue expression can be evaluated only by the program when it executes. One connotation of the term ``rvalue expression'' is that you cannot write such an expression where the translator must be able to determine its value before the program executes. Four subclasses of rvalue expressions, however, have a value that the translator or the target environment can determine prior to program startup:

An address constant expression specifies a value that has a pointer type and that the translator or target environment can determine prior to program startup. Therefore, the expression must not cause side effects. You must not write subexpressions with a void type. (You cannot write a function call, assigning, or comma operator, except as part of the operand of a sizeof operator.) You write address constant expressions to specify the initial values stored in objects of pointer type with static duration.

For example:

extern int first;
static int *pf = &first;  &first is address constant

An arithmetic constant expression specifies a value that has an arithmetic type and that the translator or target environment can determine prior to program startup. Therefore, the expression must not cause side effects. You must write only subexpressions that have arithmetic type. (You cannot write a function call, assiging, or comma operator, except as part of the operand of a sizeof operator.) You write arithmetic constant expressions to specify the initial values stored in objects of arithmetic type with static duration.

For example:

extern int counter = 0;
static int flt_bits = 6 / 0.30103 + 0.5;
static int ret_bytes = sizeof f();

An integer constant expression specifies a value that has an integer type and that the translator can determine at the point in the program where you write the expression. The same restrictions apply as for arithmetic constant expressions. In addition, you must write only subexpressions that have integer type. You can, however, write a floating-point constant as the operand of an integer type cast operator. You write integer constant expressions to specify:

For example:

extern int a[20], a_copy[sizeof a / sizeof a[0]];
enum {red = 1, green = 4, blue = 16} color;

A #if expression specifies a value that can be determined by an if or elif directive. After preprocessing replaces all names within the expression, the same restrictions apply as for integer constant expressions.

For example:

#if __STDC__ && 32767 < INT_MAX

Lvalue expressions fall into one of four subclasses:

An accessible lvalue expression designates an object that has an object type other than an array type. Hence, you can access the value stored in the object.

For example:

static const struct complex imag = {0, 1};
return (imag);            imag is accessible lvalue

A modifiable lvalue expression designates an object that has object type other than an array type or a const type. Hence, you can alter the value stored in the object.

For example:

static int next_no = 0;
return ++next_no;         next_no is modifiable lvalue

An array lvalue expression designates an object that has an array type. The type can be incomplete. You often write expressions that implicitly convert an array lvalue expression to an rvalue expression of a pointer type. You can also take the address of an array lvalue expression. For example:

static int bmask[] = {1, 8, 2, 4};
int (*pb)[] = &bmask;     &bmask is pointer to array
y = arg & bmask[i];       bmask is array lvalue
scan_it(bmask);           bmask becomes pointer to int

An incomplete non-array lvalue expression designates an object that has a void type or an incomplete structure or union type. You can only take the address of such an expression. For example:

extern struct who_knows rom;
static struct who_knows *rom_base = &rom;

Class Conversions

Many of the expression subclasses are proper subsets of other subclasses. In other cases, the translator can convert an expression of one class to another when the context demands it. The following diagram illustrates all classes and subclasses of expressions and how they relate. Each box denotes a different class or subclass. It contains a subset of all expressions that you can write. An unlabeled arrow connects each subset to its containing set.

For example, an integer constant expression is a subset of all arithmetic constant expressions, which in turn is a subset of all rvalue expressions. An incomplete non-array lvalue expression is not a subset of any other. A label on an arrow tells you that a conversion occurs if you write an expression of one class where the context requires a result of another class. For example, an rvalue expression that you write in a side-effects subcontext becomes a void result by dropping the value associated with the rvalue.

The translator can make four conversions to satisfy context:

If you write an rvalue expression in a side-effects subcontext, the translator discards the value of the expression and converts it to a void type. For example:

int y;
y = 3;                    rvalue y = 3 becomes void

If you write an accessible lvalue expression in an rvalue subcontext (such as a test or value context), the translator accesses the value stored in the object to determine the result. A qualified type (const or volatile) becomes its corresponding unqualified type. For example:

const int x;
int y;
y = x;                    const int lvalue x becomes int rvalue

If you write an array lvalue expression in an rvalue subcontext, the translator converts the type array of T to pointer to T. The value of the expression is the address of the first element of the array. For example:

int a[10], *pi;
pi = a;                   array a becomes pointer rvalue

If you write a function-designator expression in an rvalue subcontext, the translator converts the type function returning T to pointer to function returning T. The value of the expression is the address of the function. For example:

int f(void);
int (*pf)(void);
pf = f;                   function f becomes pointer rvalue

Type Conversions

Within several contexts the translator converts the type of a scalar expression (or subexpression). The conversions are called:

This section describes each of these conversions and the context in which it occurs. It also shows how the translator determines the value of the converted type from the value of the original type.

Promoting

Except when it is the operand of the sizeof operator, an integer rvalue expression has one of four types:

When you write an expression in an rvalue subcontext and the expression has an integer type that is not one of these types, the translator promotes its type. If all of the values representable in the original type are also representable as type int, then the promoted type is int. Otherwise, the promoted type is unsigned int.

Thus, for signed char, short, and any signed bitfield type, the promoted type is int. For each of the remaining integer types (char, unsigned char, unsigned short, any plain bitfield type, or any unsigned bitfield type), the effect of these rules is to favor promoting to int wherever possible, but to promote to unsigned int if necessary to preserve the original value in all possible cases.

For example:

signed char ch;
unsigned short us, f(char *, ...);
f("%i%x", ch,             ch becomes int
    us);                  us becomes int or unsigned int

Balancing

When you write an infix operator that has two arithmetic rvalue operands, the translator frequently determines the type of the result by balancing the types of the two operands. To balance two types, the translator applies the following rules to the promoted types of the operands:

Each of the operands is converted to the balanced type, the arithmetic operation occurs between the now identical types, and the result of the operation has the balanced type. For example:

int i;
long lo;
double d;
return ((i + lo)          i becomes long
        + d);             (i + lo) becomes double

Assigning and Type Casting

You store a value in an object by writing an expression that contains an assigning operator. The assigning operators are =, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, and |=.

If the type of the aalue to be stored by an assigning operator is compatible with the type of the object, the program stores the value unmodified. Otherwise, the translator determines the appropriate conversion to perform before storing the new value.

You can also specify a type conversion by writing a type cast operator. You can specify any type conversion permitted for an assigning operator, plus several other conversions between scalar types.

The translator defines a number of conversions between scalar types that you can specify by assigning or type casting. It does not define all possible conversions, however:

int i;
char *pc = (char *)&i;    valid type cast
*(int *)pc = 3;           also valid

if (*pc == *(int *)pc)    also valid
    printf("int stores l.s. byte first\n");
extern int sum(int, int);
void (*pv)(void) = (void (*)(void))&sum;

if (((int (*)(int, int))pv)(1, 2) == 3)
    printf("sum was called properly\n");

The following table summarizes all possible scalar conversions. Note that you can convert any scalar type to any other scalar type by specifying no more than two conversions. In many cases, however, at least one of the conversions is implementation defined.

                             Pointer to
        From:  Arithmetic    Incomplete  Pointer to
To:            Type          or Object   Function

Arithmetic     any           to integer  to integer
Type                         only        only

Pointer to     from integer  any         ---
Incomplete or  only
Object

Pointer to     from integer  ---         any
Function       only

Changing Representations

When you convert between any two arithmetic types, what happens to the value depends on the number of bits used to represent the original and final types. The following table summarizes all possible conversions between arithmetic types. The table assumes that:

Conversion        N < M               N == M              N > M

signed integer    discard m.s.        same value          same value
  to                M-N bits
signed integer    (can overflow)

unsigned integer  if (X < 2^(N-1))    if (X < 2^(N-1))    same value
  to                same value          same value
signed integer    else impl.-def.     else impl.-def.
                  (can overflow)      (can overflow)

floating-point    if (|X| < 2^(N-1))  if (|X| < 2^(N-1))  if (|X| < 2^(N-1))
  to                trunc(X)            trunc(X)            trunc(X)
signed integer    else impl.-def.     else impl.-def.     else impl.-def.
                  (can overflow)      (can overflow)      (can overflow)

signed integer    if (0 <= X)         if (0 <= X)         if (0 <= X)
  to                X % 2^N             same value          same value
unsigned integer  else impl.-def.     else X + 2^N        else X + 2^N

unsigned integer  X % 2^N             same value          same value
  to
unsigned integer

floating-point    if (0 <= X < 2^N)   if (0 <= X < 2^N)   if (0 <= X < 2^N) 
  to                trunc(X)            trunc(X)            trunc(X)
unsigned integer  else impl.-def.     else impl.-def.     else impl.-def.
                  (can overflow)      (can overflow)      (can overflow)

signed integer    keep sign,          same value          same value
  to              keep m.s.
floating-point    N-1 bits

unsigned integer  + sign,             + sign,             same value
  to              keep m.s.           keep m.s.
floating-point    N-1 bits            N-1 bits

floating-point    keep m.s.           same value          same value
  to              N-1 bits
floating-point    (can overflow)

The table shows what happens when you convert an M-bit representation with value X to an N-bit representation, for the three cases where M is less than, equal to, or greater than N. The abbreviations used in this table are:

Pointer Arithmetic

You can add an integer to a value of type pointer to object. If the value of the pointer is the address of an array element, then adding one to the value yields the address of the next array element. Thus, for a pointer p to any object:

(char *)(p + 1)           is identical to (char *)p + sizeof (*p)

If the value of p is the address of the first element in an array object, then *(p + n) designates element number n (counting from zero). If the value of p is the address of the last element in an array object, then (p + 1) is a valid address, even though *(p + 1) is not an accessible lvalue. You can perform pointer arithmetic on (p + 1) just as you can on the address of any of the elements of the array. If you form any other address that does not designate an element of the array object (by adding an integer to a pointer), the result is undefined.

Reading Expressions

You compose an expression from one or more terms and zero or more operators:

Each term has a well defined type and class. If an expression consists of a single term without operators, then the type and class of the expression are the type and class of the term.

Each operator requires one, two, or three operands. An operand is a subexpression that can itself (generally) contain additional operators as well as terms. If you write an expression with one or more terms and a single operator, then the terms must be the operands of the operator. Each operator accepts operands with only certain combinations of types and classes. The types and classes of the operands determine the type and class of the expression containing the operator.

If you write an expression with one or more terms and two operators, then the translator must determine which terms to group with which operators. You can enclose any subexpression in parentheses to make clear that it groups as a single operand. Such parentheses have no other effect than to control grouping. If you do not write such parentheses, however, the translator applies a number of precedence rules to determine how the expression groups. Every expression you write groups in only one way.

This section describes how to determine the type and class of any term. Later sections in this documents explain the rules for grouping operands in the presence of two or more operators, the effect of each operator, what operands it accepts, and what result it produces.

A term takes one of several forms:

A name in this context be declared as one of three entities:

For a function, the name is a function-designator expression with the declared type. For an object, the name is an lvalue expression with the declared type. For an enumeration constant, the name is an rvalue expression with type int. (You can write a type definition or tag in an expression only as part of a type-name declaration enclosed in parentheses, to make a type cast operator, or as the operand of a sizeof operator.)

If no declaration is visible for one of these entities and if you write a left parenthesis immediately following the name, then the translator implicitly declares the name in the current name space as a function returning int without any argument information.

An integer constant is an rvalue expression whose type depends on the value, the base, and any suffix you write. In all cases, the value is the sequence of digits taken as a number to the specified base. Each base and suffix combination determines a sequence of possible types. The translator selects the earliest type in the sequence that can represent the value of the particular integer constant.

For a decimal integer constant, the sequences are:

For an octal or hexadecimal integer constant, the sequences are:

For example, if type int has a 16-bit representation:

70     070    0x70        all type int
7000U  070U   0x7000      all type unsigned
70000  070L   0x700L      all type long

A floating-point constant is an rvalue expression whose type depends on any suffix you write:

Its value is the integer plus fraction parts, taken as a decimal number, times ten raised to the exponent part, taken as a signed decimal number.

A character constant is an rvalue expression. If you specify just one character in the character constant, its type depends on any prefix you write:

Its value is the code for the character you specify in the target character set. If you specify other than one character in a character constant, the type and value are implementation defined.

A string literal is an lvalue expression whose type depends on the number of characters you specify and any prefix you write:

N is one more than the number of characters you specify when you write the string (for the terminating null character). Its stored value is the sequence of codes for the character(s) you specify in the target character set, followed by the value zero. For example:

"hello"                   type is array of 6 char
L"hai"                    type is array of 4 wchar_t

The term sizeof (declaration) is an rvalue expression of type size_t, with value as described for that operator.

Any expression you write enclosed in parentheses is a term whose type, class, and value are the same as for the expression. Enclosing an expression in parentheses has no effect other than to control grouping.

Grouping

In the absence of parentheses, the translator groups operators with operands in the following order:

1. The translator applies a postfix operator immediately following a term before it applies any other operator. It then applies any postfix operators to the right of that operator, grouping from left to right.

2. The translator applies a prefix operator immediately preceding a term and any postfix operators. It then applies any prefix operators to the left of that operator, grouping from right to left.

3. The translator applies infix operators in descending order of precedence. Operators at the same order of precedence group either from left to right or from right to left, as indicated for the particular precedence level.

The translator resolves two ambiguities by:

In either case, you can use parentheses to obtain the alternate grouping.

The following table shows all operators grouped by precedence level in descending order.

Operator                     Notation    Grouping

postincrement                X++         from left to right
postdecrement                X--
subscript                    X[Y]
function call                X(Y)
select member                X.Y
point at member              X->Y

sizeof                       sizeof X    from right to left
preincrement                 ++X
predecrement                 --X
address of                   &X
indirection                  *X
plus                         +X
minus                        -X
bitwise NOT                  ~X
logical NOT                  !X
type cast                    (declaration)X

multiply                     X * Y       from left to right
divide                       X / Y
remainder                    X % Y

add                          X + Y       from left to right
subtract                     X - Y

left shift                   X << Y      from left to right
right shift                  X >> Y

less than                    X < Y       from left to right
less than or equal           X <= Y
greater than                 X > Y
greater than or equal        X >= Y

equals                       X == Y      from left to right
not equals                   X != Y

bitwise AND                  X & Y       from left to right

bitwise exclusive OR         X ^ Y       from left to right

bitwise inclusive OR         X | Y       from left to right

logical AND                  X && Y      from left to right

logical OR                   X || Y      from left to right

conditional                  Z ? X : Y   from right to left

assignment                   X = Y       from right to left
multiply assign              X *= Y
divide assign                X /= Y
remainder assign             X %= Y
add assign                   X += Y
subtract assign              X -= Y
left shift assign            X <<= Y
right shift assign           X >>= Y
bitwise AND assign           X &= Y
bitwise exclusive OR assign  X ^= Y
bitwise inclusive OR assign  X |= Y

comma                        X, Y        from left to right

The table also shows how operators group within a given precedence level.

For example:

y = m * x + b             is    y = ((m * x) + b)
*p++ = -x->y              is    (*(p++)) = (-(x->y))

Operator Summary

This section describes every operator. It lists the operators alphabetically by name, showing how to write each one with operands X, Y, and Z (as needed). Following a description of what the operator does is a table of all permissible combinations of operand types and classes, with the type and class of the result for each combination.

Some expressions produce a result that has an integer type that varies among implementations. Each of these types has a type definition that you can include in the program by including the standard header <stddef.h>. The type definitions are:

You do not have to include these type definitions in the program to use the subtract or sizeof operators, for example, or to write wide-character string literals.

If the result of an operation cannot be represented by a value of the result type, then an exceptional result occurs:

If any form of exceptional result occurs, the program behavior is undefined.

A type described below as Q qualified is an optionally qualified type. It can be:

For pointer arithmetic, every object is considered an array object (with perhaps only one element). If the array a has N elements, then a[N] is the element immediately beyond the array.

Add Assign

X += Y

You write X += Y to access the value stored in the object designated by X, add the value of Y to the stored value, and store the new value back in the object.


Result                    X                       Y
type of X                 arithmetic              arithmetic
rvalue                    modifiable lvalue       rvalue

type of X                 pointer to object       integer
rvalue                    modifiable lvalue       rvalue

Add

X + Y

You write X + Y to add the value of Y to the value of X. You can add an integer to a pointer value only if the result is the address of an element within (or just beyond) the same array object.


Result                    X                       Y
balanced type of X and Y  arithmetic              arithmetic
rvalue                    rvalue                  rvalue

type of X                 pointer to object       integer
rvalue                    rvalue                  rvalue

type of Y                 integer                 pointer to object
rvalue                    rvalue                  rvalue

Address of

&X

You write &X to obtain the address of the function or object designated by X. You cannot obtain the address of an object declared with storage class register.


Result                    X
pointer to T              any object type T
rvalue                      except bitfield
                          lvalue

pointer to T              incomplete type T
rvalue                    lvalue

pointer to T              function type T
rvalue                    function designator

Assignment

X = Y

You write X = Y to store the value of Y in the object designated by X. If Y is an lvalue expression (that is converted to an rvalue expression to obtain its stored value), then the object it designates either must have no bytes in common with the object designated by X or must overlap exactly, and the objects must have compatible types.


Result                    X                       Y
type of X                 arithmetic              arithmetic
rvalue                    modifiable lvalue       rvalue

type of X                 pointer, structure, or  type of X
rvalue                      union type T          rvalue
                          modifiable lvalue

type of X                 pointer to qualified T  pointer to same or
rvalue                    modifiable lvalue         less qualified type
                                                    compatible with T
                                                  rvalue

type of X                 pointer to void         pointer to object
rvalue                    modifiable lvalue         or incomplete type
                                                  rvalue

type of X                 pointer to object       pointer to void
rvalue                      or incomplete type    rvalue
                          modifiable lvalue

type of X                 any pointer type        integer zero
rvalue                    modifiable lvalue       rvalue

Bitwise AND Assign

X &= Y

You write X &= Y to access the value stored in the object designated by X, form the bitwise AND of the value of Y with the stored value, and store the new value back in the object.


Result                    X                       Y
type of X                 integer                 integer
rvalue                    modifiable lvalue       rvalue

Bitwise AND

X & Y

You write X & Y to form the bitwise AND of the values of X and Y. Each bit of the result is set if the corresponding bits in both X and Y are set.


Result                    X                       Y
balanced type of X and Y  integer                 integer
rvalue                    rvalue                  rvalue

Bitwise Exclusive OR Assign

X ^= Y

You write X ^= Y to access the value stored in the object designated by X, form the bitwise exclusive OR of the value of Y with the stored value, and store the new value back in the object.


Result                    X                       Y
type of X                 integer                 integer
rvalue                    modifiable lvalue       rvalue

Bitwise Exclusive OR

X ^ Y

You write X ^ Y to form the bitwise exclusive OR of the values of X and Y. Each bit of the result is set if the corresponding bits in X and Y differ.


Result                    X                       Y
balanced type of X and Y  integer                 integer
rvalue                    rvalue                  rvalue

Bitwise Inclusive OR Assign

X |= Y

You write X |= Y to access the value stored in the object designated by X, form the bitwise inclusive OR of the value of Y with the stored value, and store the new value back in the object.


Result                    X                       Y
type of X                 integer                 integer
rvalue                    modifiable lvalue       rvalue

Bitwise Inclusive OR

X | Y

You write X | Y to form the bitwise inclusive OR of the values of X and Y. Each bit of the result is set if either of the corresponding bits in X or Y is set.


Result                    X                       Y
balanced type of X and Y  integer                 integer
rvalue                    rvalue                  rvalue

Bitwise NOT

~X

You write ~X to form the bitwise NOT of the value of X. Each bit of the result is set if the corresponding bit in X is not set.


Result                    X
type of X                 integer
rvalue                    rvalue

Comma

X, Y

You write X, Y to first evaluate X as a side-effects context expression and then to evaluate Y. There is a sequence point between the evaluation of the two operands.


Result                    X                       Y
type of Y                 void expression         any
rvalue                    rvalue

Conditional

Z ? X : Y

You write Z ? X : Y to evaluate one of the operands X or Y, depending on the value of the test-context expression Z, which must be a scalar rvalue. If Z has a nonzero value, then only X is evaluated; otherwise, only Y is evaluated. The value of the expression is the value of the operand that is evaluated, converted to the result type. A sequence point occurs between the evaluation of Z and the evaluation of X or Y.


Result                    X                       Y
balanced type of X and Y  arithmetic              arithmetic
rvalue                    rvalue                  rvalue

type of X                 structure or union      type of X
rvalue                    rvalue                  rvalue

type of X                 pointer to T            integer zero
rvalue                    rvalue                  rvalue

type of Y                 integer zero            pointer to T
rvalue                    rvalue                  rvalue

pointer to                pointer to              pointer to
  Q and Q' qualified        Q qualified             Q' qualified
  composite of T and T'     type T                  compatible type T'
rvalue                    rvalue                  rvalue

pointer to                pointer to              pointer to
  Q and Q' qualified        Q qualified             Q' qualified
  void                      void                    object or
rvalue                    rvalue                    incomplete type
                                                  rvalue

pointer to                pointer to              pointer to
  Q and Q' qualified        Q qualified             Q' qualified
  void                      object or               void
rvalue                      incomplete type       rvalue
                          rvalue

void expression           void expression         void expression

Divide Assign

X /= Y

You write X /= Y to access the value stored in the object designated by X, divide that value by the value of Y, and store the new value back in the object. (See the Divide operator following.)


Result                    X                       Y
type of X                 arithmetic              arithmetic
rvalue                    modifiable lvalue       rvalue

Divide

X / Y

You write X / Y to divide the value of X by the value of Y. Dividing by by zero yields an exceptional result. For integer types, a positive quotient truncates toward zero.


Result                    X                       Y
balanced type of X and Y  arithmetic              arithmetic
rvalue                    rvalue                  rvalue

Equals

X == Y

You write X == Y to test whether the value of X equals the value of Y. The result is an int rvalue whose value is 1 if the test is successful; otherwise, the value is zero. Eachoof the operands is converted to a common test type for the comparison. The table below shows the test type, rather than the result type.


Test                      X                       Y
balanced type of X and Y  arithmetic              arithmetic
rvalue                    rvalue                  rvalue

type of X                 pointer to T            integer zero
rvalue                    rvalue                  rvalue

type of Y                 integer zero            pointer to T
rvalue                    rvalue                  rvalue

pointer to                pointer to              pointer to
  Q and Q' qualified        Q qualified             Q' qualified
  composite of T and T'     type T                  compatible type T'
rvalue                    rvalue                  rvalue

pointer to                pointer to              pointer to
  Q and Q' qualified        Q qualified             Q' qualified
  void                      void                    object or
rvalue                    rvalue                    incomplete type
                                                  rvalue

pointer to                pointer to              pointer to
  Q and Q' qualified        Q qualified             Q' qualified
  void                      object or               void
rvalue                      incomplete type       rvalue
                          rvalue

Function Call

X(Y)

You write X(Y) to call a function. The value of the expression (if any) is the value that the function returns. A sequence point occurs after the program evaluates X and Y and before it calls the function.


Result                    X                       Y
object type T             function returning T    zero or more
rvalue                    function designator       argument rvalues

object type T             pointer to function     zero or more
rvalue                      returning T             argument rvalues
                          rvalue

void expression           function returnigg      zero or more
                            void                    argument rvalues
                          function designator
void expression           pointer to function     zerooor more
                            returning void          argument rvalues
                          rvalue

Greater Than Or Equal

X >= Y

You write X >= Y to test whether the value of X is greater than or equal to the value of Y. The result is an int rvalue whose value is 1 if the test is successful; otherwise, the value is zero. Each of the operands is converted to a common test type for the comparison. You can compare two pointer values only if they are the addresses of elements within (or just beyond) the same array object. The table below shows the test type, rather than the result type.


Test                      X                       Y
balanced type of X and Y  arithmetic              arithmetic
rvalue                    rvalue                  rvalue

pointer to                pointer to              pointer to
  Q and Q' qualified        Q qualified             Q' qualified
  composite of T and T'     type T                  compatible type T'
rvalue                    rvalue                  rvalue

Greater Than

X > Y

You write X > Y to test whether the value of X is greater than the value of Y. The result is an int rvalue whose value is 1 if the test is successful; otherwise, the value is zero. Each of the operands is converted to a common test type for the comparison. You can compare two pointer values only if they are the addresses of elements within (or just beyond) the same array object. The table below shows the test type, rather than the result type.


Test                      X                       Y
balanced type of X and Y  arithmetic              arithmetic
rvalue                    rvalue                  rvalue

pointer to                pointer to              pointer to
  Q and Q' qualified        Q qualified             Q' qualified
  composite of T and T'     type T                  compatible type T'
rvalue                    rvalue                  rvalue

Indirection

*X

You write *X to use the value of the pointer X to designate an entity. The address of the entity is the value of the pointer.


Result                    X
type T                    pointer to object type T
lvalue                    rvalue

type T                    pointer to function type T
function designator       rvalue

void expression           pointer to void
                          rvalue

Left Shift Assign

X <<= Y

You write X <<= Y to access the value stored in the object designated by X, shift that value to the left by the number of bit positions specified by the value of Y, and store the new value back in the object.


Result                    X                       Y
type of X                 integer                 integer
rvalue                    modifiable lvalue       rvalue

Left Shift

X << Y

You write X << Y to shift the value of X to the left by the number of bit positions specified by the value of Y. For an N-bit representation for the promoted value of X, the value of Y must be in the interval [0, N). Zeros fill the vacated bit positions.


Result                    X                       Y
type of X                 integer                 integer
rvalue                    rvalue                  rvalue

Less Than Or Equal

X <= Y

You write X <= Y to test whether the value of X is less than or equal to the value of Y. The result is an int rvalue whose value is 1 if the test is successful; otherwise, the value is zero. Each of the operands is converted to a common test type for the comparison. You can compare two pointer values only if they are the addresses of elements within (or just beyond) the same array object. The table below shows the test type, rather than the result type.


Test                      X                       Y
balanced type of X and Y  arithmetic              arithmetic
rvalue                    rvalue                  rvalue

pointer to                pointer to              pointer to
  Q and Q' qualified        Q qualified             Q' qualified
  composite of T and T'     type T                  compatible type T'
rvalue                    rvalue                  rvalue

Less Than

X < Y

You write X < Y to test whether the value of X is less than the value of Y. The result is an int rvalue whose value is 1 if the test is successful; otherwise, the value is zero. Each of the operands is converted to a common test type for the comparison. You can compare two pointer values only if they are the addresses of elements within (or just beyond) the same array object. The table below shows the test type, rather than the result type.


Test                      X                       Y
balanced type of X and Y  arithmetic              arithmetic
rvalue                    rvalue                  rvalue

pointer to                pointer to              pointer to
  Q and Q' qualified        Q qualified             Q' qualified
  composite of T and T'     type T                  compatible type T'
rvalue                    rvalue                  rvalue

Logical AND

X && Y

You write X && Y to test whether both of the operands X and Y are nonzero. If X is zero, then only X is evaluated and the value of the expression is zero; otherwise, Y is evaluated and the value of the expression is 1 if Y is nonzero or zero if Y is zero. A sequence point occurs between the evaluation of X and the evaluation of Y.


Result                    X                       Y
int                       scalar                  scalar
rvalue                    rvalue                  rvalue

Logical NOT

!X

You write !X to test whether X is zero. If X is zero, the value of the expression is 1; otherwise, the value is zero.


Result                    X
int                       scalar
rvalue                    rvalue

Logical OR

X || Y

You write X || Y to test whether either of the operands X or Y is nonzero. If X has a nonzero value, then only X is evaluated and the value of the expression is 1; otherwise, Y is evaluated and the value of the expression is 1 if Y is nonzero or zero if Y is zero. A sequence point occurs between the evaluation of X and the evaluation of Y.


Result                    X                       Y
int                       scalar                  scalar
rvalue                    rvalue                  rvalue

-X

You write -X to negate the value of X.


Result                    X
type of X                 arithmetic
rvalue                    rvalue

Multiply Assign

X *= Y

You write X *= Y to access the value stored in the object designated by X, multiply that value by the value of Y, and store the new value back in the object.


Result                    X                       Y
type of X                 arithmetic              arithmetic
rvalue                    modifiable lvalue       rvalue

Multiply

You write X * Y to multiply the value of X by the value of Y.


Result                    X                       Y
balanced type of X and Y  arithmetic              arithmetic
rvalue                    rvalue                  rvalue

Not Equals

X != Y

You write X != Y to test whether the value of X does not equal the value of Y. The result is an int rvalue whose value is 1 if the test is successful; otherwise, the value is zero. Each of the operands is converted to a common test type for the comparison. The table below shows the test type, rather than the result type.


Test                      X                       Y
balanced type of X and Y  arithmetic              arithmetic
rvalue                    rvalue                  rvalue

type of X                 pointer to T            integer zero
rvalue                    rvalue                  rvalue

type of Y                 integer zero            pointer to T
rvalue                    rvalue                  rvalue

pointer to                pointer to              pointer to
  Q and Q' qualified        Q qualified             Q' qualified
  composite of T and T'     type T                  compatible type T'
rvalue                    rvalue                  rvalue

pointer to                pointer to              pointer to
  Q and Q' qualified        Q qualified             Q' qualified
  void                      void                    object or
rvalue                    rvalue                    incomplete type
                                                  rvalue

pointer to                pointer to              pointer to
  Q and Q' qualified        Q qualified             Q' qualified
  void                      object or               void
rvalue                      incomplete type       rvalue
                          rvalue

Plus

+X

You write +X to leave the value of X unchanged. (You use this operator primarily to emphasize that a term is not negated.)


Result                    X
type of X                 arithmetic
rvalue                    rvalue

Point at

X->Y

You write X->Y to select the member whose name is Y from the structure or union whose address is the value of X.


Result                    X                       Y
type of member Y          pointer to              member name within
lvalue                      structure or union      structure or union
                          rvalue

Postdecrement

X--

You write X-- to access the value stored in the object designated by X, subtract 1 from the value, and store the new value back in the object. The value of the expression is the original value stored in the object.


Result                    X
type T                    scalar type T
rvalue                    modifiable lvalue

Postincrement

X++

You write X++ to access the value stored in the object designated by X, add 1 to the value, and store the new value back in the object. The value of the expression is the original value stored in the object.


Result                    X
type T                    scalar type T
rvalue                    modifiable lvalue

Predecrement

--X

You write --X to access the value stored in the object designated by X, subtract 1 from the value, and store the new value back in the object. The value of the expression is the final value stored in the object.


Result                    X
type T                    scalar type T
rvalue                    modifiable lvalue

Preincrement

++X

You write ++X to access the value stored in the object designated by X, add 1 to the value, and store the new value back in the object. The value of the expression is the final value stored in the object.


Result                    X
type T                    scalar type T
rvalue                    modifiable lvalue

Remainder Assign

X %= Y

You write X %= Y to access the value stored in the object designated by X, divide that value by the value of Y, and store the remainder back in the object.


Result                    X                       Y
type of X                 integer                 integer
rvalue                    modifiable lvalue       rvalue

Remainder

X % Y

You write X % Y to compute the remainder of the value of X divided by the value of Y. Dividing by by zero yields an exceptional result. Barring overflow or division by zero, it is always true that X = (X / Y) * Y + (X % Y).


Result                    X                       Y
balanced type of X and Y  integer                 integer
rvalue                    rvalue                  rvalue

Right Shift Assign

X >>= Y

You write X >>= Y to access the value stored in the object designated by X, shift that value to the right by the number of bit positions specified by the value of Y, and store the new value back in the object.


Result                    X                       Y
type of X                 integer                 integer
rvalue                    modifiable lvalue       rvalue

Right Shift

X >> Y

You write X >> Y to shift the value of X to the right by the number of bit positions specified by the value of Y. For an N-bit representation for the value of X, the (promoted) value of Y must be in the interval [0, N). If X is nonnegative, then zeros fill the vacated bit positions; otherwise, the result is implementation defined.


Result                    X                       Y
balanced type of X and Y  integer                 integer
rvalue                    rvalue                  rvalue

Select

X.Y

You write X.Y to select the member Y from the structure or union X. The result is an lvalue expression only if X is an lvalue expression.


Result                    X                       Y
type of member Y          structure or union      member name within
lvalue                    lvalue                  the structure or union

type of member Y          structure or union      member name within
rvalue                    rvalue                  the structure or union

sizeof

sizeof X

You write sizeof X to determine the size in bytes of an object whose type is the type of X. Do not write a function-designator expression for X. The translator uses the expression you write for X only to determine a type; it is not evaluated. The operand X is otherwise not considered a part of the expression containing the sizeof operator. Therefore, prohibitions on what can be in an expression (such as an arithmetic constant expression) do not apply to any part of X.


Result                    X
size_t                    object type
rvalue                    lvalue

size_t                    object type
rvalue                    rvalue

Subscript

X[Y]

You write X[Y] to designate an array element. The operator is identical in effect to *((X)+(Y)). Typically, X is an array lvalue expression (which becomes a pointer rvalue expression) or an rvalue expression of some pointer type whose value is the address of an array element. In this case, Y is an integer rvalue. The designated array element is Y elements away from the element designated by X. Because of the symmetry of the two operands, however, you can write them in either order.


Result                    X                       Y
object type T             pointer to T            integer
lvalue                    rvalue                  rvalue

object type T             integer                 pointer to T
lvalue                    rvalue                  rvalue

Subtract Assign

X -= Y

You write X -= Y to access the value stored in the object designated by X, subtract the value of Y from the value, and store the new value back in the object.


Result                    X                       Y
type of X                 arithmetic              arithmetic
rvalue                    modifiable lvalue       rvalue

type of X                 pointer to object       integer
rvalue                    modifiable lvalue       rvalue

Subtract

X - Y

You write X - Y to subtract the value of Y from the value of X. You can subtract two pointer values only if they are the addresses of elements within (or just beyond) the same array object. The result tells you how many elements lie between the two addresses.


Result                    X                       Y
balanced type of X and Y  arithmetic              arithmetic
rvalue                    rvalue                  rvalue

type of X                 pointer to object       integer
rvalue                    rvalue                  rvalue

ptrdiff_t                 pointer to Q qualified  pointer to Q' qualified
rvalue                      object type T           compatible type T'
                          rvalue                  rvalue

Type Cast

(decl)X

You write (decl)X to convert the value of X to the scalar (or void) type T that you specify in the type-name declaration decl enclosed in parentheses. The table below shows valid combinations of declared type and operand type.


Result                    Type T                  X 
type T                    integer                 scalar
rvalue                                            rvalue

type T                    floating-point          arithmetic
rvalue                                            rvalue

type T                    pointer to              integer
rvalue                      any type              rvalue

type T                    pointer to object       pointer to object
rvalue                      or incomplete type      or incomplete type
                                                  rvalue

type T                    pointer to              pointer to
rvalue                      function                function
                                                  rvalue

void expression           void                    scalar rvalue or
                                                    void expression

Order of Evaluation

When the program evaluates an expression, it has considerable latitude in choosing the order in which it evaluates subexpressions. For example, the translator can alter:

y = *p++;

either to:

temp = p; p += 1; y = *temp;

or to:

y = *p; p += 1;

As another example, the program can evaluate the expression

f() + g()

by calling the functions in either order.

The order of evaluation is important in understanding when side effects occur, such as storing a value in an object.

A sequence point is a point in the program at which you can determine which side effects have occurred and which have yet to take place. Each of the expressions you write as part of a statement, for example, has a sequence point at the end of it. You can be sure that for:

y = 37;
x += y;

the program stores the value 37 in y before it accesses the value stored in y to add it to the value stored in x.

Sequence points can also occur within expressions. The comma, conditional, function call, logical AND, and logical OR operators each contain a sequence point. For example, you can write:

if ((c = getchar()) != EOF && isprint(c))

and know that the program evaluates isprint(c) only after a new value is stored in c from the call to getchar.

Between two sequence points, you must access the value stored in an object whose contents you are altering only to determine the new value to store, and store a value in an object no more than once.

For example:

val = 10 * val
    + (c - '0');          well defined
i = ++i + 2;              NOT well defined

An expression can contain sequence points and still not have a definite order of evaluation. In the example above, the expression f() + g() contains a sequence point before each function call, but the add operator imposes no ordering on the evaluation of its operands.


See also the Table of Contents and the Index.

Copyright © 1989-1996 by P.J. Plauger and Jim Brodie. All rights reserved.