Previous Page
Next Page

5.2. Operators in Detail

This section describes in detail the individual operators, and indicates what kinds of operands are permissible. The descriptions are arranged according to the customary usage of the operators, beginning with the usual arithmetic and assignment operators.

5.2.1. Arithmetic Operators

Table 5-5 lists the arithmetic operators .

Table 5-5. Arithmetic operators

Operator

Meaning

Example

Result

*

Multiplication

x * y

The product of x and y

/

Division

x / y

The quotient of x by y

%

The modulo operation

x % y

The remainder of x divided by y

+

Addition

x + y

The sum of x and y

-

Subtraction

x - y

The difference of x and y

+ (unary)

Positive sign

+x

The value of x

- (unary)

Negative sign

-x

The arithmetic negation of x


The operands of the arithmetic operators are subject to the following rules:

  • Only the % operator requires integer operands.

  • The operands of all other operators may have any arithmetic type.

Furthermore, addition and subtraction operations may also be performed on pointers in the following cases:

  • In an addition, one addend can be an object pointer while the other has an integer type.

  • In a subtraction, either both operands can be pointers to objects of the same type (without regard to type qualifiers), or the minuend (the left operand) can be an object pointer, while the subtrahend (the right operand) has an integer type.

5.2.1.1. Standard arithmetic

The operands are subject to the usual arithmetic conversions (see "Conversion of Arithmetic Types" in Chapter 4). The result of division with two integer operands is also an integer! To obtain the remainder of an integer division, use the modulo operation (the % operator). Implicit type conversion takes place in the evaluation of the following expressions, as shown in Table 5-6 (assume n is declared by short n = -5;).

Table 5-6. Implicit type conversions in arithmetic expressions

Expression

Implicit type conversion

The expression's type

The expression's value

-n

Integer promotion.

int

5

n * -2L

Integer promotion: the value of n is promoted to long, because the constant -2L has the type long.

long

10

8/n

Integer promotion.

int

-1

8%n

Integer promotion.

int

3

8.0/n

The value of n is converted to the type double, because 8.0 has the type double.

double

-1.6

8.0%n

Error: the modulo operation (%) requires integer operands.

  


If both operands in a multiplication or a division have the same sign, the result is positive; otherwise, it is negative. However, the result of a modulo operation always has the same sign as the left operand. For this reason, the expression 8%n in Table 5-6 yields the value 3. If a program attempts to divide by zero, its behavior is undefined.

5.2.1.2. Pointer arithmetic

You can use the binary operators + and - to perform arithmetic operations on pointers. For example, you can modify a pointer to refer to another object a certain number of object sizes away from the object originally referenced. Such pointer arithmetic is generally useful only to refer to the elements of an array.

Adding an integer to or subtracting an integer from a pointer yields a pointer value with the same type as the pointer operand. The compiler automatically multiplies the integer by the size of the object referred to by the pointer type, as Example 5-1 illustrates.

Example 5-1. Pointer arithmetic
double dArr[5] = { 0.0, 1.1, 2.2, 3.3, 4.4 },  // Initialize an array and
       *dPtr = dArr;                           // a pointer to its first element.
int i = 0;                 // An index variable.

dPtr = dPtr + 1;           // Advance dPtr to the second element. Addends
dPtr = 2 + dPtr;           // can be in either order. dPtr now points to dArr[3].

printf( "%.1f\n", *dPtr );       // Print the element referenced by dPtr.
printf( "%.1f\n", *(dPtr -1) );  // Print the element before that, without
                                 // modifying the pointer dPtr.

i = dPtr - dArr;   // Result: the index of the array element that dPtr points to.

Figure 5-1 illustrates the effects of the two assignment expressions using the pointer dPtr.

Figure 5-1. Using a pointer to move through the elements in an array


The statement dPtr = dPtr + 1; adds the size of one array element to the pointer, so that dPtr points to the next array element, dArr[1]. Because dPtr is declared as a pointer to double, its value is increased by sizeof(double).

The statement dPtr = dPtr + 1; in Example 5-1 has the same effect as any of the following statements (see the sections "Assignment Operators" and "Increment and Decrement Operators," later in this chapter):

dPtr += 1;
++dPtr;
dPtr++;

Subtracting one pointer from another yields an integer value with the type ptrdiff_t. The value is the number of objects that fit between the two pointer values. In the last statement in Example 5-1, the expression dPtr - dArr yields the value 3. This is also the index of the element that dPtr points to, because dArr represents the address of the first array element (with the index 0). The type ptrdiff_t is defined in the header file stddef.h, usually as int.

For more information on pointer arithmetic, see Chapter 9.

5.2.2. Assignment Operators

In an assignment operation, the left operand must be a modifiable lvalue; in other words, it must be an expression that designates an object whose value can be changed. In a simple assignment (that is, one performed using the operator =), the assignment operation stores the value of the right operand in this object.

There are also compound assignments , which combine an arithmetic or a bitwise operation in the same step with the assignment. Table 5-7 lists all the assignment operators .

Table 5-7. Assignment operators

Operator

Meaning

Example

Result

=

Simple assignment

x = y

Assign x the value of y.

+=  -=
*=  /=  %=
&=  ^=  |=
<<=  >>=

Compound assignment

x *= y

For each binary arithmetic or binary bitwise operator op, x op= y is equivalent to x = x op (y).


5.2.2.1. Simple assignment

The operands of a simple assignment must fulfill one of the following conditions:

  • Both operands have arithmetic types.

  • The left operand has the type _Bool and the right operand is a pointer.

  • Both operands have the same structure or union type.

  • Both operands are pointers to the same type, or the left operand is a pointer to a qualified version of the common typethat is, the type pointed to by the left operand is declared with one or more additional type qualifiers (see Chapter 11).

  • One operand is an object pointer and the other is a pointer to void (here again, the type pointed to by the left operand may have additional type qualifiers).

  • The left operand is a pointer and the right is a null pointer constant.

If the two operands have different types, the value of the right operand is converted to the type of the left operand (see the sections "The Results of Arithmetic Type Conversions" and "Implicit Pointer Conversions" in Chapter 4).

The modification of the left operand is a side effect of an assignment expression. The value of the entire assignment expression is the same as the value assigned to the left operand, and the assignment expression has the type of the left operand. However, unlike its left operand, the assignment expression itself is not an lvalue. If you use the value of an assignment expression in a larger expression, pay careful attention to implicit type conversions. Avoid errors such as that illustrated in the following example. This code is supposed to read characters from the standard input stream until the end-of-file is reached or an error occurs:

#include <stdio.h>
char c = 0;

/* ... */

while ( (c = getchar( )) != EOF )
  { /* ... Process the character stored in c ... */ }

In the controlling expression of the while statement in this example, getchar( ) returns a value with type int, which is implicitly converted to char for assignment to c. Then the value of the entire assignment expression c = getchar( ), which is the same char value, is promoted to int for comparison with the constant EOF, which is usually defined as -1 in the header file stdio.h. However, if the type char is equivalent to unsigned char, then the conversion to int always yields a non-negative value. In this case, the loop condition is always true.

As Table 5-4 shows, assignment operators have a low precedence, and are grouped with their operators from right to left. As a result, no parentheses are needed around the expression to the right of the assignment operator, and multiple assignments can be combined in one expression, as in this example:

double x = 0.5, y1, y2;      // Declarations
y1 = y2 = 10.0 * x;          // Equivalent to  y1 = (y2 = (10.0 * x));

This expression assigns the result of the multiplication to y1 and to y2.

5.2.2.2. Compound assignments

A compound assignment is performed by any of the following operators:

 *=  /= %= += -=  (arithmetic operation and assignment)
<<= >>= &= ^= |=  (bitwise operation and assignment)

In evaluating a compound assignment expression, the program combines the two operands with the specified operation and assigns the result to the left operand. Two examples:

long var = 1234L ;
var *= 3;        // Triple the value of var.
var <<= 2;       // Shift the bit pattern in var two bit-positions to the
                 // left (i.e., multiply the value by four).

The only difference between a compound assignment x op= y and the corresponding expression x = x op (y) is that in the compound assignment, the left operand x is evaluated only once. In the following example, the left operand of the compound assignment operator is an expression with a side effect, so that the two expressions are not equivalent:

x[++i] *= 2;              // Increment i once, then double the indexed
                          // array element.
x[++i] = x[++i] * (2);    // Oops: you probably didn't want to increment i
                          // twice.

In the equivalent form x = x op (y), the parentheses around the right operand y are significant, as the following example illustrates:

double var1 = 2.5, var2 = 0.5;
var1 /= var2 + 1;         // Equivalent to var1 = var1 / (var2 + 1);

Without the parentheses, the expression var1 = var1 / var2 + 1 would yield a different result, because simple division, unlike the compound assignment, has higher precedence than addition.

The operands of a compound assignment can have any types that are permissible for the operands of the corresponding binary operator. The only additional restriction is that when you add a pointer to an integer, the pointer must be the left operand, as the result of the addition is a pointer. Example:

short *sPtr;
/* ... */
sPtr += 2;         // Equivalent to  sPtr = sPtr + 2;
                   // or  sPtr = 2 + sPtr;

5.2.3. Increment and Decrement Operators

Each of the tokens ++ and -- represents both a postfix and a prefix operator. Table 5-8 describes both forms of both operators.

Table 5-8. Increment and decrement operators

Operator

Meaning

Side effect

Value of the expression

Postfix:

x++

Prefix:

++x

Increment

Increases the value of x by one (like x = x + 1).

The value of x++ is the value that x had before it was incremented.

The value of ++x is the value that x has after it has been incremented.

Postfix:

x--

Prefix:

--x

Decrement

Decreases the value of x by one (like x = x - 1).

The value of x-- is the value that x had before it was decremented.

The value of --x is the value that x has after it has been decremented.


These operators require a modifiable lvalue as their operand. More specifically, the operand must have a real arithmetic type (not a complex type), or an object pointer type. The expressions ++x and --x are equivalent to (x += 1) and (x -= 1).

The following examples demonstrate the use of the increment operators, along with the subscript operator [ ] and the indirection operator *:

char a[10] = "Jim";
int i = 0;
printf( "%c\n", a[i++] );      // Output: J
printf( "%c\n", a[++i] );      // Output: m

The character argument in the first printf( ) call is the character J from the array element a[0]. After the call, i has the value 1. Thus in the next statement, the expression ++i yields the value 2, so that a[++i] is the character m.

The operator ++ can also be applied to the array element itself:

i = 0;
printf( "%c\n", a[i]++ );      // Output: J
printf( "%c\n", ++a[i] );      // Output: L

According to the operator precedences and associativity in Table 5-4, the expressions a[i]++ and ++a[i] are equivalent to (a[i])++ and ++(a[i]). Thus each of these expressions increases the value of the array element a[0] by one, while leaving the index variable i unchanged. After the statements in this example, the value of i is still 0, and the character array contains the string "Lim", as the first element has been incremented twice.

The operators ++ and -- are often used in expressions with pointers that are dereferenced by the * operator. For example, the following while loop copies a string from the array a to a second char array, a2:

char a2[10], *p1 = a,  *p2 = a2;
// Copy string to a2:
while ( (*p2++ = *p1++) != '\0' )
   ;

Because the postfix operator ++ has precedence over the indirection operator * (see Table 5-4), the expression *p1++ is equivalent to *(p1++). In other words, the value of the expression *p1++ is the array element referenced by p1, and as a side effect, the value of p1 is one greater after the expression has been evaluated. When the end of the string is reached, the assignment *p2++ = *p1++ copies the terminator character '\0', and the loop ends, because the assignment expression yields the value '\0'.

By contrast, the expression (*p1)++ or ++(*p1) would increment the element referenced by p1, leaving the pointer's value unchanged. However, the parentheses in the expression ++(*p) are unnecessary: this expression is equivalent to ++*p1, because the unary operators are associated with operands from right to left (see Table 5-4). For the same reason, the expression *++p1 is equivalent to *(++p1), and its value is the array element that p1 points to after p1 has been incremented.

5.2.4. Comparative Operators

The comparative operators , also called the relational operators and the equality operators , compare two operands and yield a value of type int. The value is 1 if the specified relation holds, and 0 if it does not. C defines the comparative operators listed in Table 5-9.

Table 5-9. Comparative operators

Operator

Meaning

Example

Result (1 = true, 0 = false)

<

Less than

x < y

1 if x is less than y, otherwise 0

<=

Less than or equal to

x <= y

1 if x is less than or equal to y, otherwise 0

>

Greater than

x > y

1 if x is greater than y, otherwise 0

>=

Greater than or equal to

x >= y

1 if x is greater than or equal to y, otherwise 0

==

Equal to

x == y

1 if x is equal to y, otherwise 0

!=

Not equal to

x != y

1 if x is not equal to y, otherwise 0


For all comparative operators, the operands must meet one of the following conditions:

  • Both operands have real arithmetic types.

  • Both operands are pointers to objects of the same type, which may be declared with different type qualifiers.

With the equality operators, == and !=, operands that meet any of the following conditions are also permitted:

  • The two operands have any arithmetic types, including complex types.

  • Both operands are pointers to functions of the same type.

  • One operand is an object pointer, while the other is a pointer to void. The two may be declared with different type qualifiers (the operand that is not a pointer to void is implicitly converted to the type void* for the comparison).

  • One operand is a pointer and the other is a null pointer constant. The null pointer constant is converted to the other operand's type for the comparison.

The operands of all comparison operators are subject to the usual arithmetic conversions (see "Conversion of Arithmetic Types" in Chapter 4). Two complex numbers are considered equal if their real parts are equal and their imaginary parts are equal.

When you compare two object pointers, the result depends on the relative positions of the objects in memory. Elements of an array are objects with fixed relative positions: a pointer that references an element with a greater index is greater than any pointer that references an element with a lesser index. A pointer can also contain the address of the first memory location after the last element of an array. In this case, that pointer's value is greater than that of any pointer to an element of the array.

The function in Example 5-2 illustrates some expressions with pointers as operands.

Example 5-2. Operations with pointers
/* The function average( ) calculates the arithmetic mean of the
 * numbers passed to it in an array.
 * Arguments: An array of float, and its length.
 * Return value: The arithmetic mean of the array elements, with type double.
 */
double average( const float *array, int length )
{
  double sum = 0.0;
  float *end = array + length;    // Points one past the last element.

  if ( length <= 0 )              // The average of no elements is zero.
    return 0.0;
                                          // Accumulate the sum by
  for ( float *p = array; p < end; ++p )  // walking a pointer through the array.
    sum += *p;

  return sum/length;              // The average of the element values.
}

Two pointers are equal if they point to the same location in memory, or if they are both null pointers. In particular, pointers to members of the same union are always equal, because all members of a union begin at the same address. The rule for members of the same structure, however, is that a pointer to member2 is larger than a pointer to member1 if and only if member2 is declared after member1 in the structure type's definition.

The comparative operators have lower precedence than the arithmetic operators, but higher precedence than the logical operators . As a result, the following two expressions are equivalent:

 a < b  &&  b <  c + 1
(a < b) && (b < (c + 1))

Furthermore, the equality operators, == and !=, have lower precedence than the other comparative operators. Thus the following two expressions are also equivalent:

 a < b  !=  b < c
(a < b) != (b < c)

This expression is true (that is, it yields the value 1) if and only if one of the two operand expressions, (a < b) and (b < c), is true and the other false.

5.2.5. Logical Operators

You can connect expressions using logical operators to form compound conditions, such as those often used in jump and loop statements to control the program flow. C uses the symbols described in Table 5-10 for the boolean operations AND, OR, and NOT.

Table 5-10. Logical operators

Operator

Meaning

Example

Result (1 = true, 0 = false)

&&

logical AND

x && y

1 if each of the operands x and y is not equal to zero, otherwise 0

||

logical OR

x || y

0 if each of x and y is equal to zero, otherwise 1

!

logical NOT

!x

1 if x is equal to zero, otherwise 0


Like comparative expressions, logical expressions have the type int. The result has the value 1 if the logical expression is true, and the value 0 if it is false.

The operands may have any scalar type desiredin other words, any arithmetic or pointer type. Any operand with a value of 0 is interpreted as false; any value other than 0 is treated as true. Most often, the operands are comparative expressions, as in the following example. Assuming the variable deviation has the type double, all three of the expressions that follow are equivalent:

 (deviation <  -0.2) || (deviation >  0.2)
  deviation <  -0.2  ||  deviation >  0.2
!(deviation >= -0.2  &&  deviation <= 0.2)

Each of these logical expressions yields the value 1, or true, whenever the value of the variable deviation is outside the interval [-0.2, 0.2]. The parentheses in the first expression are unnecessary since comparative operators have a higher precedence than the logical operators && and ||. However, the unary operator ! has a higher precedence. Furthermore, as Table 5-4 shows, the operator && has a higher precedence than ||. As a result, parentheses are necessary in the following expression:

( deviation < -0.2 || deviation > 0.2 ) && status == 1

Without the parentheses, that expression would be equivalent to this:

deviation < -0.2 || ( deviation > 0.2 && status == 1 )

These expressions yield different results if, for example, deviation is less than -0.2 and status is not equal to 1.

The operators && and || have an important peculiarity: their operands are evaluated in order from left to right, and if the value of the left operand is sufficient to determine the result of the operation, then the right operand is not evaluated at all. There is a sequence point after the evaluation of the left operand. The operator && evaluates the right operand only if the left operand yields 1; the operator || evaluates the right operand only if the left operand yields 0. The following example shows how programs can use these conditional-evaluation characteristics of the && and || operators:

double x;
_Bool get_x(double *x), check_x(double);   // Function prototype
                                           // declarations.
/* ... */
while ( get_x(&x) && check_x(x) )          // Read and test a number.
  { /* ... Process x ... */  }

In the controlling expression of the while loop, the function get_x(&x) is called first to read a floating-point number into the variable x. Assuming that get_x( ) returns a TRue value on success, the check_x( ) function is called only if there is a new value in x to be tested. If check_x( ) also returns true, then the loop body is executed to process x.

5.2.6. Bitwise Operators

For more compact data, C programs can store information in individual bits or groups of bits. File access permissions are a common example. The bitwise operators allow you to manipulate individual bits in a byte or in a larger data unit: you can clear, set, or invert any bit or group of bits. You can also shift the bit pattern of an integer to the left or right.

The bit pattern of an integer type consists of bit positions numbered from right to left, beginning with position 0 for the least significant bit. For example, consider the char value '*', which in ASCII encoding is equal to 42, or binary 101010:

Bit pattern

0

0

1

0

1

0

1

0

Bit positions

7

6

5

4

3

2

1

0

In this example, the value 101010 is shown in the context of an 8-bit byte; hence the two leading zeros.

5.2.6.1. Boolean bitwise operators

The operators listed in Table 5-11 perform Boolean operations on each bit position of their operands. The binary operators connect the bit in each position in one operand with the bit in the same position in the other operand. A bit that is set, or 1, is interpreted as true, and a bit that is cleared, or 0, is considered false.

In addition to the operators for boolean AND, OR, and NOT, there is also a bitwise exclusive-OR operator. These are all described in Table 5-11.

Table 5-11. Boolean bitwise operators

Operator

Meaning

Example

Result (for each bit position)

(1 = set, 0 = cleared)

&

Bitwise AND

x & y

1, if 1 in both x and y

0, if 0 in x or y, or both

|

Bitwise OR

x | y

1, if 1 in x or y, or both

0, if 0 in both x and y

^

Bitwise

exclusive OR

x ^ y

1, if 1 either in x or in y, but not in both

0, if either value in both x and y

~

Bitwise NOT

(one's complement )

~x

1, if 0 in x

0, if 1 in x


The operands of the bitwise operators must have integer types, and are subject to the usual arithmetic conversions. The resulting common type of the operands is the type of the result. Table 5-12 illustrates the effects of these operators.

Table 5-12. Effects of the bitwise operators

Expression (or declaration)

Bit pattern

int a = 6;

0 ... 0 0 1 1 0

int b = 11;

0 ... 0 1 0 1 1

a & b

0 ... 0 0 0 1 0

a | b

0 ... 0 1 1 1 1

a ^ b

0 ... 0 1 1 0 1

~a

1 ... 1 1 0 0 1


You can clear certain bits in an integer variable a by performing a bitwise AND with an integer in which only the bits to be cleared contain zeroes, and assigning the result to the variable a. The bits that were set in the second operandcalled a bit maskhave the same value in the result as they had in the first operand. For example, an AND with the bit mask 0xFF clears all bits except the lowest eight:

a &= 0xFF;       // Equivalent notation: a = a & 0xFF;

As this example illustrates, the compound assignment operator &= also performs the & operation. The compound assignments with the other binary bitwise operators work similarly.

The bitwise operators are also useful in making bit masks to use in further bit operations. For example, in the bit pattern of 0x20, only bit 5 is set. The expression ~0x20 therefore yields a bit mask in which all bits are set except bit 5:

a &= ~0x20;    // Clear bit 5 in a.

The bit mask ~0x20 is preferable to 0xFFFFFFDF because it is more portable: it gives the desired result regardless of the machine's word size. (It also makes the statement more readable for humans.)

You can also use the operators | (OR) and ^ (exclusive OR) to set and clear certain bits. Here is an example of each one:

int mask = 0xC;
a |= mask;       // Set bits 2 and 3 in a.
a ^= mask;       // Invert bits 2 and 3 in a.

A second inversion using the same bit mask reverses the first inversion. In other words, b^mask^mask yields the original value of b. This behavior can be used to swap the values of two integers without using a third, temporary variable:

a ^= b;          // Equivalent to a = a ^ b;
b ^= a;          // Assign b the original value of a.
a ^= b;          // Assign a the original value of b.

The first two expressions in this example are equivalent to b = b^(a^b) or b = (a^b)^b. The result is like b = a, with the side effect that a is also modified, and now equals a^b. At this point, the third expression has the effect of (using the original values of a and b) a = (a^b)^a, or a = b.

5.2.6.2. Shift operators

The shift operators transpose the bit pattern of the left operand by the number of bit positions indicated by the right operand. They are listed in Table 5-13.

Table 5-13. Shift operators

Operator

Meaning

Example

Result

<<

Shift left

x << y

Each bit value in x is moved y positions to the left.

>>

Shift right

x >> y

Each bit value in x is moved y positions to the right.


The operands of the shift operators must be integers. Before the actual bit-shift, the integer promotions are performed on both operands. The value of the right operand must not be negative, and must be less than the width of the left operand after integer promotion. If it does not meet these conditions, the program's behavior is undefined.

The result has the type of the left operand after integer promotion. The shift expressions in the following example have the type unsigned long.

unsigned long n = 0xB,    // Bit pattern:  0 ... 0 0 0 1 0 1 1
         result = 0;
result = n << 2;          //               0 ... 0 1 0 1 1 0 0
result = n >> 2;          //               0 ... 0 0 0 0 0 1 0

In a left shift, the bit positions that are vacated on the right are always cleared. Bit values shifted beyond the leftmost bit position are lost. A left shift through y bit positions is equivalent to multiplying the left operand by 2y: If the left operand x has an unsigned type, then the expression x << y yields the value of x x 2y. Thus in the previous example, the expression n << 2 yields the value of n x 4, or 44.

On a right shift, the vacated bit positions on the left are cleared if the left operand has an unsigned type, or if it has a signed type and a non-negative value. In this case, the expression has x >> y yields the same value as the integer division x/2y. If the left operand has a negative value, then the fill value depends on the compiler: it may be either zero or the value of the sign bit.

The shift operators are useful in generating certain bit masks. For example, the expression 1 << 8 yields a word with only bit 8 set, and the expression ~(3<<4) produces a bit pattern in which all bits are set except bits 4 and 5. The function setBit( ) in Example 5-3 uses the bit operations to manipulate a bit mask.

Example 5-3. Using a shift operation to manipulate a bit mask
// Function setBit( )
// Sets the bit at position p in the mask m.
// Uses CHAR_BIT, defined in limits.h, for the number of bits in a byte.
// Return value: The new mask with the bit set, or the original mask
//               if p is not a valid bit position.
unsigned int setBit( unsigned int mask, unsigned int p )
{
  if ( p >= CHAR_BIT * sizeof(int) )
    return mask;
  else
    return mask | (1 << p);
}

The shift operators have lower precedence than the arithmetic operators, but higher precedence than the comparative operators and the other bitwise operators. The parentheses in the expression mask | (1 << p) in Example 5-3 are thus actually unnecessary, but they make the code more readable.

5.2.7. Memory Addressing Operators

The five operators listed in Table 5-14 are used in addressing array elements and members of structures, and in using pointers to access objects and functions.

Table 5-14. Memory addressing operators

Operator

Meaning

Example

Result

&

Address of

&x

Pointer to x

*

Indirection operator

*p

The object or function that p points to

[ ]

Subscripting

x[y]

The element with the index y in the array x (or the element with the index x in the array y: the [ ] operator works either way)

.

Structure or union member designator

x.y

The member named y in the structure or union x

->

Structure or union member designator by reference

p->y

The member named y in the structure or union that p points to


5.2.7.1. The & and * operators

The address operator & yields the address of its operand. If the operand x has the type T, then the expression &x has the type "pointer to T."

The operand of the address operator must have an addressable location in memory. In other words, the operand must designate either a function or an object (i.e., an lvalue) that is not a bit-field, and has not been declared with the storage class register (see "Storage Class Specifiers" in Chapter 11).

You need to obtain the addresses of objects and functions when you want to initialize pointers to them:

float x, *ptr;
ptr = &x;         // OK: Make ptr point to x.
ptr = &(x+1);     // Error: (x+1) is not an lvalue.

Conversely, when you have a pointer and want to access the object it references, use the indirection operator *, which is sometimes called the dereferencing operator. Its operand must have a pointer type. If ptr is a pointer, then *ptr designates the object or function that ptr points to. If ptr is an object pointer, then *ptr is an lvalue, and you can use it as the left operand of an assignment operator:

float x, *ptr = &x;
*ptr = 1.7;          // Assign the value 1.7 to the variable x
++(*ptr);            // and add 1 to it.

In the final statement of this example, the value of ptr remains unchanged. The value of x is now 2.7.

The behavior of the indirection operator * is undefined if the value of the pointer operand is not the address of an object or a function.

Like the other unary operators, the operators & and * have the second highest precedence. They are grouped with operands from right to left. The parentheses in the expression ++(*ptr) are thus superfluous.

The operators & and * are complementary: if x is an expression that designates an object or a function, then the expression *&x is equivalent to x. Conversely, in an expression of the form &*ptr, the operators cancel each other out, so that the type and value of the expression are equivalent to ptr. However, &*ptr is never an lvalue, even if ptr is.

5.2.7.2. Elements of arrays

The subscript operator [ ] allows you to access individual elements of an array. It takes two operands. In the simplest case, one operand is an array name and the other operand designates an integer. In the following example, assume that myarray is the name of an array, and i is a variable with an integer type. The expression myarray[i] then designates element number i in the array, where the first element is element number zero (see Chapter 8).

The left operand of [ ] need not be an array name. One operand must be an expression whose type is "pointer to an object type"an array name is a special case of such an expressionwhile the other operand must have an integer type. An expression of the form x[y] is always equivalent to (*((x)+(y))) (see also "Pointer arithmetic," earlier in this chapter). Example 5-4 uses the subscript operator in initializing a dynamically generated array.

Example 5-4. Initializing an array
#include <stdlib.h>
#define ARRAY_SIZE 100
/* ... */
double *pArray = NULL; int i = 0:
pArray = malloc( ARRAY_SIZE * sizeof(double) ); // Generate the array
if ( pArray != NULL ) {
   for ( i = 0; i < ARRAY_SIZE; ++i )           // and initialize it.
     pArray[i] = (double)rand( )/RAND_MAX;
/* ... */
}

In Example 5-4, the expression pArray[i] in the loop body is equivalent to *(pArray+i). The notation i[pArray] is also correct, and yields the same array element.

5.2.7.3. Members of structures and unions

The binary operators . and ->, most often called the dot operator and the arrow operator, allow you to select a member of a structure or a union.

As Example 5-5 illustrates, the left operand of the dot operator . must have a structure or union type, and the right operand must be the name of a member of that type.

Example 5-5. The dot operator
struct Article { long number;      // The part number of an article
                 char name[32];    // The article's name
                 long price;       // The unit price in cents
                 /* ... */
               };
struct Article sw = { 102030L, "Heroes", 5995L };
sw.price = 4995L;                  // Change the price to 49.95

The result of the dot operator has the value and type of the selected member. If the left operand is an lvalue, then the operation also yields an lvalue. If the left operand has a qualified type (such as one declared with const), then the result is likewise qualified.

The left operand of the dot operator is not always an lvalue, as the following example shows:

struct Article getArticle( );       // Function prototype
printf( "name: %s\n", getArticle( ).name );

The function getArticle( ) returns an object of type struct Article. As a result, getArticle( ).name is a valid expression, but not an lvalue, as the return value of a function is not an lvalue.

The operator -> also selects a member of a structure or union, but its left operand must be a pointer to a structure or union type. The right operand is the name of a member of the structure or union. Example 5-6 illustrates the use of the -> operator, again using the Article structure defined in Example 5-5.

Example 5-6. The arrow operator
struct Article *pArticle = &sw,         // A pointer to struct Article.
       const *pcArticle = &sw;          // A "read-only pointer" to struct
                                        // Article.
++(pArticle->number);                   // Increment the part number.
if ( pcArticle->number == 102031L )     // Correct usage: read-only access.
  pcArticle->price += 50;               // Error: can't use a const-qualified
                                        // pointer to modify the object.

The result of the arrow operator is always an lvalue. It has the type of the selected member, as well as any type qualifications of the pointer operand. In Example 5-6, pcArticle is a pointer to const struct Article. As a result, the expression pcArticle->price is constant.

Any expression that contains the arrow operator can be rewritten using the dot operator by dereferencing the pointer separately: an expression of the form p->m is equivalent to (*p).m. Conversely, the expression x.m is equivalent to (&x)->m, as long as x is an lvalue.

The operators . and ->, like [ ], have the highest precedence, and are grouped from left to right. Thus the expression ++p->m for example is equivalent to ++(p->m), and the expression p->m++ is equivalent to (p->m)++. However, the parentheses in the expression (*p).m are necessary, as the dereferencing operator * has a lower precedence. The expression *p.m would be equivalent to *(p.m), and thus makes sense only if the member m is also a pointer.

To conclude this section, we can combine the subscript, dot, and arrow operators to work with an array whose elements are structures:

struct Article arrArticle[10];     // An array with ten elements
                                   // of type struct Article.
arrArticle[2].price = 990L;        // Set the price of the
                                   // array element arrArticle[2].
arrArticle->number = 10100L;       // Set the part number in the
                                   // array element arrArticle[0].

An array name, such as arrArticle in the example, is a constant pointer to the first array element. Hence arrArticle->number designates the member number in the first array element. To put it in more general terms: for any index i, the following three expressions are equivalent:

arrArticle[i].number
(arrArticle+i)->number
(*(arrArticle+i).number

All of them designate the member number in the array element with the index i.

5.2.8. Other Operators

There are six other operators in C that do not fall into any of the categories described in this chapter. Table 5-15 lists these operators in order of precedence.

Table 5-15. Other operators

Operator

Meaning

Example

Result

( )

Function call

log(x)

Passes control to the specified function, with the specified arguments.

(type name) {list}

Compound literal

(int [5]){ 1, 2 }

Defines an unnamed object that has the specified type and the values listed.

sizeof

Storage size of an object or type, in bytes

sizeof x

The number of bytes occupied in memory by x.

(type name)

Explicit type conversion, or "cast"

(short) x

The value of x converted to the type specified.

?:

Conditional evaluation

x ? y : z

The value of y, if x is true (i.e., nonzero); otherwise the value of z.

,

Sequential evaluation

x,y

Evaluates first x, then y. The result of the expression is the value of y.


5.2.8.1. Function calls

A function call is an expression of the form fn_ptr( argument_list ), where the operand fn_ptr is an expression with the type "pointer to a function." If the operand designates a function (as a function name does, for example), then it is automatically converted into a pointer to the function. A function call expression has the value and type of the function's return value. If the function has no return value, the function call has the type void.

Before you can call a function, you must make sure that it has been declared in the same translation unit. Usually a source file includes a header file containing the function declaration, as in this example:

#include <math.h>    // Contains the prototype double pow( double, double );
double x = 0.7, y = 0.0;
/* ... */
y = pow( x+1, 3.0 );    // Type: double

The parentheses enclose the comma-separated list of arguments passed to the function, which can also be an empty list. If the function declaration is in prototype form (as is usually the case), the compiler ensures that each argument is converted to the type of the corresponding parameter, as for an assignment. If this conversion fails, the compiler issues an error message:

  pow( x, 3 );       // The integer constant 3 is converted to type double.
  pow( x );          // Error: incorrect number of arguments.

The order in which the program evaluates the individual expressions that designate the function and its arguments is not defined. As a result, the behavior of a printf statement such as the following is undefined:

int i = 0;
printf( "%d %d\n", i, ++i );     // Behavior undefined

However, there is a sequence point after all of these expressions have been evaluated and before control passes to the function.

Like the other postfix operators, a function call has the highest precedence, and is grouped with operands from left to right. For example, suppose that fn_table is an array of pointers to functions that take no arguments and return a structure that contains a member named price. In this case, the following expression is a valid function call:

fn_table[i++]( ).price

The expression calls the function referenced by the pointer stored in fn_table[i]. The return value is a structure, and the dot operator selects the member price in that structure. The complete expression has the value of the member price in the return value of the function fn_table[i]( ), and the side effect that i is incremented once.

Chapter 7 describes function calls in more detail, including recursive functions and functions that take a variable number of arguments.

5.2.8.2. Compound literals

Compound literals are an extension introduced in the C99 standard. This extension allows you to define literals with any object type desired. A compound literal consists of an object type in parentheses, followed by an initialization list in braces:

( type name ){ list of initializers }

The value of the expression is an unnamed object that has the specified type and the values listed. If you place a compound literal outside of all function blocks, then the initializers must be constant expressions, and the object has static storage duration. Otherwise it has automatic storage duration, determined by the containing block.

Typical compound literals generate objects with array or structure types. Here are a few examples to illustrate their use:

float *fPtr = (float [ ]){ -0.5, 0.0, +0.5 };

This declaration defines a pointer to a nameless array of three float elements.

#include "database.h"   // Contains prototypes and type definitions,
                        // including the structure Pair:
                        // struct Pair { long key; char value[32]; };

insertPair( &db, &(struct Pair){ 1000L, "New York JFK Airport" } );

This statement passes the address of a literal of type struct Pair to the function insertPair( ).

To define a constant compound literal, use the type qualifier const:

(const char [ ]){"A constant string."}

If the previous expression appears outside of all functions, it defines a static array of char, like the following simple string literal:

"A constant string."

In fact, the compiler may store string literals and constant compound literals with the same type and contents at the same location in memory.

Despite their similar appearance, compound literals are not the same as cast expressions. The result of a cast expression has a scalar type or the type void, and is not an lvalue.

5.2.8.3. The sizeof operator

The sizeof operator yields the size of its operand in bytes. Programs need to know the size of objects mainly in order to reserve memory for them dynamically, or to store binary data in files.

The operand of the sizeof operator can be either an object type in parentheses, or an expression that has an object type and is not a bit-field. The result has the type size_t, which is defined in stddef.h and other standard header files as an unsigned integer type.

For example, if i is an int variable and iPtr is a pointer to int, then each of the following expressions yields the size of inton a 32-bit system, the value would be 4:

sizeof(int)  sizeof i  sizeof(i)  sizeof *iPtr  sizeof(*iPtr)

Note the difference to the following expressions, each of which yields the size of a pointer to int:

sizeof(*int)  sizeof &i  sizeof(&i)  sizeof iPtr  sizeof(iPtr)

Like *, &, and the other unary operators, sizeof has the second highest precedence, and is grouped from right to left. For this reason, no parentheses are necessary in the expression sizeof *iPtr.

For an operand with the type char, unsigned char, or signed char, the sizeof operator yields the value 1, because these types have the size of a byte. If the operand has a structure type, the result is the total size that the object occupies in memory, including any gaps that may occur due to the alignment of the structure members. In other words, the size of a structure is sometimes greater than the sum of its individual members' sizes. For example, if variables of the type short are aligned on even byte addresses, the following structure has the size sizeof(short) + 2:

struct gap { char version; short value; };

In the following example, the standard function memset( ) sets every byte in the structure to zero, including any gaps between members:

#include <string.h>
/* ... */
struct gap g;
memset( &g, 0, sizeof g );

If the operand of sizeof is an expression, it is not actually evaluated. The compiler determines the size of the operand by its type, and replaces the sizeof expression with the resulting constant. Variable-length arrays, introduced in the C99 standard, are an exception (see Chapter 8). Their size is determined at run time, as Example 5-7 illustrates.

Example 5-7. Sizing variable-length arrays
void func( float a[ ], int n )
{
  float b[2*n];                    // A variable-length array of float.
  /* ... the value of n may change now ... */
  int m = sizeof(b) / sizeof(*b);  // Yields the number of elements
  /* ... */                        // in the array b.
}

Regardless of the current value of the variable n, the expression sizeof(b) yields the value of 2 x n0 x sizeof(float), where n0 is the value that n had at the beginning of the function block. The expression sizeof(*b) is equivalent to sizeof(b[0]), and in this case has the value of sizeof(float).

The parameter a in the function func( ) in Example 5-7 is a pointer, not an array. The expression sizeof(a) within the function would therefore yield the size of a pointer. See "Array and Function Designators" in Chapter 4.


5.2.8.4. The conditional operator

The conditional operator is sometimes called the ternary or trinary operator, because it is the only one that has three operands:

condition ? expression 1 : expression 2

The operation first evaluates the condition. Then, depending on the result, it evaluates one or the other of the two alternative expressions.

There is a sequence point after the condition has been evaluated. If the result is not equal to 0 (in other words, if the condition is true), then only the second operand, expression 1, is evaluated, and the entire operation yields the value of expression 1. If on the other hand condition does yield 0 (i.e., false), then only the third operand, expression 2, is evaluated, and the entire operation yields the value of expression 2. In this way the conditional operator represents a conditional jump in the program flow, and is therefore an alternative to some if-else statements.

A common example is the following function, which finds the maximum of two numbers:

inline int iMax(int a, int b) { return a >= b ? a : b; }

The function iMax( ) can be rewritten using an if-else statement:

inline int iMax(int a, int b)
{ if ( a >= b ) return a;  else return b; }

The conditional operator has a very low precedence: only the assignment operators and the comma operator are lower. Thus the following statement requires no parentheses:

distance = x < y ? y - x : x - y;

The first operand of the conditional operator, condition, must have a scalar typethat is, an arithmetic type or a pointer type. The second and third operands, expression 1 and expression 2, must fulfill one of the following cases:

  • Both of the alternative expressions have arithmetic types, in which case the result of the complete operation has the type that results from performing the usual arithmetic conversions on these operands.

  • Both of the alternative operands have the same structure or union type, or the type void. The result of the operation also has this type.

  • Both of the alternative operands are pointers, and one of the following is true:

    • Both pointers have the same type. The result of the operation then has this type as well.

    • One operand is a null pointer constant. The result then has the type of the other operand.

    • One operand is an object pointer and the other is a pointer to void. The result then has the type void *.

The two pointers may point to differently qualified types. In this case, the result is a pointer to a type which has all of the type qualifiers of the two alternative operands. For example, suppose that the following pointers have been defined:

const int *cintPtr;           // Declare pointers.
volatile int *vintPtr;
void *voidPtr;

The expressions in the following table then have the type indicated, regardless of the truth value of the variable flag:

Expression

Type

flag ? cintPtr : vintPtr

volatile const int*

flag ? cintPtr : NULL

const int*

flag ? cintPtr : voidPtr

const void*


5.2.8.5. The comma operator

The comma operator is a binary operator:

expression 1 , expression 2

The comma operator ensures sequential processing: first the left operand is evaluated, then the right operand. The result of the complete expression has the type and value of the right operand. The left operand is only evaluated for its side effects; its value is discarded. There is a sequence point after the evaluation of the left operand. Example:

x = 2.7, sqrt( 2*x )

In this expression, the assignment takes place first, before the sqrt( ) function is called. The value of the complete expression is the function's return value.

The comma operator has the lowest precedence of all operators. For this reason, the assignment x = 2.7 in the previous example does not need to be placed in parentheses. However, parentheses are necessary if you want to use the result of the comma operation in another assignment:

y = ( x = 2.7, sqrt( 2*x ));

This statement assigns the square root of 5.4 to y.

A comma in a list of initializers or function arguments is a list separator, not a comma operator. In such contexts, however, you can still use a comma operator by enclosing an expression in parentheses:

y = sqrt( (x=2.7, 2*x) );

This statement is equivalent to the one in the previous example. The comma operator allows you to group several expressions into one. This ability makes it useful for initializing or incrementing multiple variables in the head of a for loop, as in the following example:

int i;  float fArray[10], val;
for ( i=0, val=0.25;  i < 10;  ++i, val *= 2.0 )
   fArray[i] = val;


Previous Page
Next Page