HP C/HP-UX Online Help


Return to the Main HP C Online Help page



Expressions & Operators

Arithmetic Operators (+, -, *, /, %)
Array Subscripting ([ ])
Assignment Operators (=, +=, -=, *=, /=, %=,<<=, >>=, &=, ^=, |=)
Bit Operators (<<, >>, &, ^, |, ~)
Cast Operator
Comma Operator (,)
Conditional Expression Operator (?:)
Function Calls
Increment and Decrement Operators (++, --)
Logical Operators (&&, ||, !)
Pointer Operators (*, ->, &)
Relational Operators (>, >=, <, ==, !=)
sizeof Operator
Structure and Union Members (., ->)
Operator Quick Reference
Constant Expressions
Integral Expressions
Floating-Point Expressions
lvalue Expressions
Pointer Expressions
Evaluation of Expressions

Arithmetic Operators (+, -, *, /, %)

Syntax

Arguments

Description

The addition, subtraction, and multiplication (+, -, and *) operators perform the usual arithmetic operations in C programs. All of the arithmetic operators (except the unary plus and minus operators) bind from left to right. The operands may be any integral or floating-point value, with the following exception: The addition and subtraction operators also accept pointer types as operands. Pointer arithmetic is described in Pointer Operators (*, ->, &).

C"s modulo operator (%) produces the remainder of integer division, which equals 0 if the right operand divides the left operand exactly. This operator can be useful for tasks such as determining whether or not a year is a U.S. presidential election year. For example:

if (year % 4 == 0)
    printf("This is an Olympic Games year.\n");
else
    printf("There will be no Olympic Games this year.\n");
As required by the ANSI/ISO C standard, HP C supports the following relationship between the remainder and division operators:
a equals a%b + (a/b) * b for any integer values of a and b
The result of a division or modulo division is undefined if the right operand is 0.

The sign reversal or unary minus operator (-) multiplies its sole operand by -1. For example, if x is an integer with the value -8, then -x evaluates to 8.

The result of the identity or unary plus operator (+) is simply the value of the operand.

Refer to Operator Precedence for information about how these and other operators evaluate with respect to each other.

Array Subscripting ([ ])

A postfix expression followed by the [ ] operator is a subscripted reference to a single element in an array.

Syntax

 postfix-expression [
expression ]

Description

One of the operands of the subscript operator must be of type pointer to T (T is an object type), the other of integral type. The resulting type is T.

The [ ] operator is defined so that E1[E2] is identical to (*((E1)+(E2))) in every respect. This leads to the (counterintuitive) conclusion that the [ ] operator is commutative. The expression E1[E2] is identical to E2[E1].

C"s subscripts run from 0 to n-1 where n is the array size.

Multidimensional arrays are represented as arrays of arrays. For this reason, the notation is to add subscript operators, not to put multiple expressions within a single set of brackets. For example, int x[3][5] is actually a declaration for an array of three objects. Each object is, in turn, an array of five int. Because of this, all of the following expressions are correct:

x
x[i]
x[i][j]
The first expression refers to the 3 by 5 array of int. The second refers to an array of five int, and the last expression refers to a single int.

The expression x[y] is an lvalue.

There is no arbitrary limit on the number of dimensions that you can declare in an array.

Because of the design of multidimensional C arrays, the individual data objects must be stored in row-major order.

As another example, the expression

  a[i,j] = 0
looks as if array a were doubly subscripted, when actually the comma in the subscript indicates that the value of i should be discarded and that j is the subscript into the a array.

Assignment Operators (=, +=, -=, *=, /=, %=,<<=, >>=, &=, ^=, |=)

Syntax

Arguments

Description

The assignment operators assign new values to variables. The equal sign (=) is the fundamental assignment operator in C. The other assignment operators provide shorthand ways to represent common variable assignments.

The Assignment Operator (=)

When the compiler encounters an equal sign, it processes the statement on the right side of the sign and assigns the result to the variable on the left side. For example:
x = 3;     /*  assigns the value 3 to variable x */
x = y;     /*  assigns the value of y to x  */
x = (y*z); /*  performs the multiplication and
               assigns the result to x      */
An assignment expression itself has a value, which is the same value that is assigned to the left operand.

The assignment operator has right-to-left associativity, so the expression

a = b = c = d = 1;
is interpreted as
(a = (b = (c = (d = 1))));
First 1 is assigned to d, then d is assigned to c, then c is assigned to b, and finally, b is assigned to a. The value of the entire expression is 1. This is a convenient syntax for assigning the same value to more than one variable. However, each assignment may cause quiet conversions, so that
int j;
double f;
f = j = 3.5;
assigns the truncated value 3 to both f and j. Conversely,
j = f = 3.5;
assigns 3.5 to f and 3 to j.

The Other Assignment Operators

C"s assignment operators provide a handy way to avoid some keystrokes. Any statement in which the left side of the equation is repeated on the right is a candidate for an assignment operator. If you have a statement like this:
i = i + 10;
you can use the assignment operator format to shorten the statement to
i += 10;
In other words, any statement of the form
var = var op exp;   /*  traditional form */
can be represented in the following shorthand form:
var op = exp;       /*  shorthand form   */
where var is a variable, op is a binary operator, and exp is an expression.

The only internal difference between the two forms is that var is evaluated only once in the shorthand form. Most of the time this is not important; however, it is important when the left operand has side effects, as in the following example:

int  *ip;
*ip++  += 1;          /* These two statements produce */
*ip++  =  *ip++  + 1; /* different results.           */
The second statement is ambiguous because C does not specify which assignment operand is evaluated first. See Operator Precedence for more information concerning order of evaluation.

Assignment Type Conversions

Whenever you assign a value to a variable, the value is converted to the variable"s data type if possible. In the example below, for instance, the floating-point constant 3.5 is converted to an int so that i gets the integer value 3.
int i;
i = 3.5;
Integer to Character Conversions
Unlike arithmetic conversions, which always expand the expression, assignment conversions can truncate the expression and therefore affect its value. For example, suppose c is a char, and you make the following assignment:
c = 882;
The binary representation of 882 is
00000011 01110010
This number requires two bytes of storage, but the variable c has only one byte allocated for it, so the two upper bits don"t get assigned to c. This is known as overflow, and the result is not defined by the ANSI/ISO C standard or the K&R language definition for signed types. HP C simply ignores the extra byte, so c would be assigned the rightmost byte:
01110010
This would erroneously give c the value of 114. The principle illustrated for chars also applies to shorts, ints, and long ints. For unsigned types, however, C has well-defined rules for dealing with overflow conditions. When an integer value x is converted to a smaller unsigned integer type, the result is the non-negative remainder of
x / (U_MAX+1)
where U_MAX is the largest number that can be represented in the shorter unsigned type. For example, if j is an unsigned short, which is two bytes, then the assignment
j = 71124;
assigns to j the remainder of
71124 / (65535+1)
The remainder is 5588. For non-negative numbers, and for negative numbers represented in two"s complement notation, this is the same result that you would obtain by ignoring the extra bytes.
Integer to Float Conversions
You may assign an integer value to a floating-point variable. In this case, the integer value is implicitly converted to a floating-point type. If the floating-point type is capable of representing the integer, there is no change in value. If f is a double, the assignment
f = 10;
is executed as if it had been written
f = 10.0;
This conversion is invisible. There are cases, however, where a floating-point type is not capable of exactly representing all integer values. Even though the range of floating-point values is generally greater than the range of integer values, the precision may not be as good for large numbers. In these instances, conversion of an integer to a floating-point value may result in a loss of precision. Consider the following example:
include <stdio.h>
int main(void)
{
    long int j = 2147483600;
    float x;
    x = j;
    printf("j is %d\nx is %10f\n", j, x);
    exit(0);
}
If you compile and execute this program, you get:
j is 2147483600
x is 2147483648.000000
Float to Integer Conversions
The most risky mixture of integer and floating-point values is the case where a floating-point value is assigned to an integer variable. First, the fractional part is discarded. Then, if the resulting integer can fit in the integer variable, the assignment is made. In the following statement, assuming j is an int, the double value 2.5 is converted to the int value 2 before it is assigned.
j = 2.5;
This causes a loss of precision which could have a dramatic impact on your program. The same truncation process occurs for negative values. After the assignment
j = -5.8;
the value of j is -5.

An equally serious situation occurs when the floating-point value cannot fit into an integer. For example:

j = 999999999999.0
This causes an overflow condition which will produce unpredictable results. As a general rule, it is a good idea to keep floating-point and integer values separate unless you have a good reason for mixing them.
Double to Float Conversions
As is the case with assigning floating-point values to integer variables, there are also potential problems when assigning double values to float variables. There are two potential problems: loss of precision and an overflow condition. In HP C a double can represent approximately 16 decimal places, and a float can only represent 7 decimal places. If f is a float variable, and you make the assignment
f = 1.0123456789
the computer rounds the double constant value before assigning it to f. The value actually assigned to f, therefore, will be 1.012346 (in double-to-float conversions, HP C always rounds to the nearest float value). The following example shows rounding due to conversions.
/*  Program name is "float_rounding".  It shows how double values
    can be rounded when they are assigned to a float.  */

#include <stdio.h>
int main(void)
{
    float f32;
    double f64;
    int i;
    for (i = 1, f64 = 0; i < 1000; ++i)
        f64 += 1.0/i;
    f32 = f64;
    printf("Value of f64: %1.7f\n", f64);
    printf("Value of f32: %1.7f\n", f32);
}
The output is
Value of f64: 7.4844709
Value of f32: 7.4844708
Floating-Point Overflows
A serious problem occurs when the value being assigned is too large to be represented in the variable. For example, the largest positive number that can be represented by a float is approximately 3e38. However, neither the K&R language definition nor the ANSI/ISO C standard defines what happens if you try to make an assignment outside this range. Suppose, for example, that your program contains the following assignment:
f = 2e40;
In this simple case, the compiler recognizes the problem and reports a compile-time error. In other instances, however, a run-time error could result.

Example

/*  Following are examples of the use of each
 *  assignment operator. In each case, x = 5
 *  and y = 2 before the statement is executed.  */
 
x = y;         x =  2
x += y + 1;    x = 8
x -= y * 3;    x = -1
x *= y + 1;    x = 15
x /= y;        x = 2
x %= y;        x = 1
x <<= y;       x = 20
x >>= y;       x = 1
x &= y;        x = 0
x ^= y;        x = 7
x |= y;        x = 7
x = y = 1      x = 1, y = 1

Bit Operators (<<, >>, &, ^, |, ~)

Syntax

Arguments

Description

The bit operators access specific bits in an object. HP C supports the usual six bit operators, which can be grouped into shift operators and logical operators.

Bit-Shift Operators

The << and >> operators shift an integer left or right respectively. The operands must have integer type, and all automatic promotions are performed for each operand. For example, the program fragment
short int  to_the_left = 53, to_the_right = 53;
short int  left_shifted_result, right_shifted_result;
 
left_shifted_result = to_the_left << 2;
right_shifted_result  = to_the_right >> 2;
sets left_shifted_result to 212 and right_shifted_result to 13. The results are clearer in binary:
   base 2          base 10
0000000000110101     53
0000000011010100    212  /* 53 shifted left 2 bits  */
0000000000001101     13  /* 53 shifted right 2 bits */
Shifting to the left is equivalent to multiplying by powers of two:
  x << y is equivalent to x * 2y.
Shifting non-negative integers to the right is equivalent to dividing by powers of 2:
  x >> y is equivalent to x / 2y.
The << operator always fills the vacated rightmost bits with zeros. If exp1 is unsigned, the >> operator fills the vacated leftmost bits with zeros. If exp1 is signed, then >> fills the leftmost bits with ones (if the sign bit is 1) or zeros (if the sign bit is 0). In other words, if exp1 is signed, the two bit-shift operators preserve its sign.

NOTE  Not all compilers preserve the sign bit when doing bit-shift operations on signed integers. The K&R language definition and the ANSI standard make this behavior implementation-defined.

Make sure that the right operand is not larger than the size of the object being shifted. For example, the following produces unpredictable and nonportable results because ints have fewer than 50 bits:

10 >> 50
You will also get nonportable results if the shift count (the second operand) is a negative value.

Bit Logical Operators

The logical bitwise operators are similar to the Boolean operators, except that they operate on every bit in the operand(s). For instance, the bitwise AND operator (&) compares each bit of the left operand to the corresponding bit in the righthand operand. If both bits are 1, a 1 is placed at that bit position in the result. Otherwise, a 0 is placed at that bit position.
Bitwise AND (&) Operator
The bitwise AND operator performs logical operations on a bit-by-bit level using the following truth table:
 
Table 17: Truth Table for the bitwise AND operator, (&) 
bit x of op1  bit x of op2  bit x of result 
0 0 0
0 1 0
1 0 0
1 1 1

The following table shows an example of the bitwise AND operator:
 
Table 18: The Bitwise AND Operator
Expression  Hexadecimal Value  Binary Representation 
9430

5722

0x24D6

0x165A

00100100 11010110

00010110 01011010

9430 & 5722 0x0452 00000100 01010010

Bitwise Inclusive (|) OR
The bitwise inclusive OR operator performs logical operations on a bit-by-bit level using the following truth table:
 
Table 19: Truth Table for the inclusive OR operator, (|)
bit x of op1  bit x of op2  bit x of result 
0 0 0
0 1 1
1 0 1
1 1 1

The bitwise inclusive OR operator (|) places a 1 in the resulting value"s bit position if either operand has a bit set at the position.
 
Table 20: Example Using the Bitwise Inclusive OR Operator 
Expression  Hexadecimal Value  Binary Representation 
9430

5722

0x24D6

0x165A

00100100 11010110

00010110 01011010

9430 | 5722 0x36DE 00110110 11011110

Bitwise exclusive OR (^)
The bitwise exclusive OR operator performs logical operations on a bit-by-bit level using the following truth table:
 
Table 21: Truth Table for the exclusive OR, ^ 
bit x of op1  bit x of op2  bit x of result 
0 0 0
0 1 1
1 0 1
1 1 0

The bitwise exclusive OR (XOR) operator (^) sets a bit in the resulting value"s bit position if either operand (but not both) has a bit set at the position.
 
Table 22: Example Using the XOR Operator 
Expression  Hexadecimal Value  Binary Representation 
9430

5722

0x24D6

0x165A

00100100 11010110

00010110 01011010

9430 ^ 5722 0x328C 00110010 10001100

Bitwise Complement (~)
The bitwise complement operator (~) performs logical operations on a bit-by-bit level using the following truth table:
 
Table 23: Truth table for the ~, Bitwise Complement 
bit x of op2  result 
0 0
0 1

The bitwise complement operator (~) reverses each bit in the operand:
 
Table 24: Example Using the Bitwise Complement Operator 
Expression  Hexadecimal Value  Binary Representation 
9430 0x24d6 00100100 11010110
~9430 0xdb29 11011011 00101001

Cast Operator

Syntax

(data_type) exp

Arguments

Description

To cast a value means to explicitly convert it to another data type. For example, given the two definitions:
int    y = 5;
float  x;
The following cast operation casts the value of y to float:
x = (float) y;  /* x now equals 5.0 */
Here are four more casts (assume that j is a scalar data type):
i = (float) j;   /* Cast j"s value to float */
i = (char *)j;   /* Cast j"s value to a pointer to a char */
i = ((int *)())j;/* Cast j"s value to a pointer to a function
                    returning an int */
i = (float) (double) j; /* Cast j"s value first to a double
                           and then to a float  */
It is important to note that if exp is a variable, a cast does not change this variable"s data type; it only changes the type of the variable"s value for that one expression. For instance, in the preceding casting examples, the cast does not produce any permanent effect on variable j.

There are no restrictions on casting from one scalar data type to another, except that you may not cast a void object to any other type. You should be careful when casting integers to pointers. If the integer value does not represent a valid address, the results are unpredictable.

A cast expression may not be an lvalue.

Casting Integers to Other Integers

It is possible to cast one integer into an integer of a different size and to convert a floating-point value, enumeration value or pointer to an integer. Conversions from one type of integer to another fall into five cases (A-E) as shown:
 
Table 25: Integer Conversions 
Original Type  char  short  int unsigned char  unsigned short  unsigned int 
char A B B D E E
short C A B C D E
int (long)* C C A* C C D*
unsigned char D B B A B B
unsigned short C D B C A B
unsigned int C C D C C A

* Case C for long in 64-bit mode.

CASE A: Trivial Conversions
It is legal to "convert" a value to its current type by casting it, but this conversion has no effect.
CASE B: Integer Widening
Casting an integer to a larger size is fairly straightforward. The value remains the same, but the storage area is widened. The compiler preserves the sign of the original value by filling the new leftmost bits with ones if the value is negative, or with zeros if the value is positive. When it converts to an unsigned integer, the value is always positive, so the new bits are always filled with zeros. The following table illustrates this principle.
                   hex    dec
char i =            37     55
(short) i  =>     0037     55
(int) i    => 00000037     55
 
char j =            c3    -61
(short) j  =>     ffc3    -61
(int) j    => ffffffc3    -61
 
unsigned char k =   37     55
(short) k  =>     0037     55
(int) k    => 00000037     55
CASE C: Casting Integers to a Smaller Type
When an int value is cast to a narrower type (short or char), the excess bits on the left are discarded. The same is true when a short is cast to a char, or when a long in 64-bit mode is cast to an int. For instance, if an int is cast to a short, the 16 leftmost bits are truncated. The following table of values illustrates these conversions.
                          hex          dec
signed long int   i = cf34bf1    217271281
 
(signed short int)i  =>  4bf1        19441
(signed char)i       =>    f1          -15
(unsigned char)i     =>    f1          241
If, after casting to a signed type, the leftmost bit is 1, then the number is negative. However, if you cast to an unsigned type and after the shortening the leftmost bit is 1, then that 1 is part of the value (it is not the sign bit).
CASE D: Casting from Signed to Unsigned, and Vice Versa
When the original type and the converted type are the same size, a representation change is necessary. That is, the internal representation of the value remains the same, but the sign bit is interpreted differently by the compiler. For instance:
                          hex         dec          hex     dec
signed int  i    =   fffffca9        -855     0000f2a1   62113
 
(unsigned int)i  =>  fffffca9  4294966441     0000f2a1   62113
The hexadecimal notation shows that the numbers are the same internally, but the decimal notation shows that the compiler interprets them differently.
CASE E: Casting Signed to Unsigned and Widening
This case is equivalent to performing two conversions in succession. First, the value is converted to the signed widened type as described in case B, and then it is converted to unsigned as described in case D. In the following assignments, the new leftmost bits are filled with ones to preserve negativeness even though the final value is unsigned.
                             hex         dec
signed short int   i =      ff55        -171
 
(unsigned long int)i =>  fffff55  4294967125
Casting Floating-Point Values to Integers
Casting floating-point values to integers may produce useless values if an overflow condition occurs. The conversion is made simply by truncating the fractional part of the number. For example, the floating-point value 3.712 is converted to the integer 3, and the floating-point value -504.2 is converted to -504.

Here are some more examples:

float f = 3.700, f2 = -502.2, f3 = 7.35e9;
 
(int)f           => 3
(unsigned int)f  => 3
(char)f          => 3
 
(int)f2          => -502       in decimal    fffffe0a in hex
(unsigned int)f2 => 4294966794 in decimal or fffffe0a in hex
(char)f2         => 10         in decimal          0a in hex
 
(int)f3          => run-time error
(unsigned int)f3 => run-time error
(char)f3         => run-time error

NOTE  Converting a large float to a char produces unpredictable results if the rounded value cannot fit in one byte. If the value cannot fit in four bytes, the run-time system issues an overflow error.

Casting Enumerated Values to Integers

When you cast an enumerated expression, the conversion is performed in two steps. First, the enumerated value is converted to an int, and then the int is converted to the final target data type. The sign is preserved during these conversions.

Casting Double to Float and Vice Versa

When you cast a float to a double, the system extends the number"s precision without changing its true value. However, when you cast a double to a float, the system shrinks the number"s precision, and this shrinking may change the number"s value because of rounding. The rounding generally occurs on the sixth or seventh decimal digit. Also, when you cast down from double to float, you run the risk of causing a run-time overflow error caused by a double that is too big or too small to fit in a float.

Casting Pointers to Pointers

You may cast a pointer of one type to a pointer to any other type. For example:
int *int_p;
float *float_p;
struct S *str_p;
extern foo(struct T *);
    . . .
int_p = (int *) float_p;
float_p = (float *) str_p;
foo((struct T *) str_p);
The cast is required whenever you assign a pointer value to a pointer variable that has a different base type, and when you pass a pointer value as a parameter to a function that has been prototyped with a different pointer type. The only exception to this rule concerns the generic pointer. You may assign any pointer value to a generic pointer without casting.

Comma Operator (,)

Syntax

exp1, exp2

Arguments

Description

Use the comma operator to separate two expressions that are to be evaluated one right after the other. The comma operator is popular within for loops, as demonstrated by the following example:
for (i = 10, j = 4; i * j < n; i++, j++);
In the preceding example, the comma operator allows you to initialize both i and j at the beginning of the loop. The comma operator also allows you to increment i and j together at the end of each loop iteration.

All expressions return values. When you use a comma operator, the expression returns the value of the rightmost expression. For example, the following statement sets variable j to 2:

j = (x = 1, y = 2);
Assignments such as these, however, are considered poor programming style. You should confine use of the comma operator to for loops.

Conditional Expression Operator (?:)

Syntax

exp1 ? exp2 : exp3

Arguments

Description

The conditional expression construction provides a shorthand way of coding an ifelse condition. The difference between the expression notation and an ifelse condition is that the ? : notation is an expression and therefore returns a value, while an ifelse condition is a statement and does not return a value. The syntax described above is equivalent to
if (exp1)
    exp2;
else
    exp3;
When a conditional expression is executed, exp1 is evaluated first. If it is true (that is, nonzero) exp2 is evaluated and its result is the value of the conditional expression. If exp1 is false, exp3 is evaluated and its result is the value of the conditional expression.

There is no requirement that you put parentheses around the exp1 portion of the conditional expression, but doing so will improve your code"s readability.

Both exp2 and exp3 must be assignment-compatible. If exp2 and exp3 are pointers to different types, then the compiler issues a warning. The value of a conditional expression is either exp2 or exp3, whichever is selected. The other expression is not evaluated. The type of the result is the type that would be produced if exp2 and exp3 were mixed in an expression. For instance, if exp2 is a char and exp3 is a double, the result type will be double regardless of whether exp2 or exp3 is selected.

Example

/*  Program name is "conditional_exp_op_example".
    This program uses the conditional expression to
    see if the user wants to continue adding
    numbers.  */
include <stdio.h>
 
int main(void)
{
    int a, b, c, d, again, total;
    char answer;
 
    printf("\n");
    again = 1;
    while (again)
    {
        printf("Enter four numbers separated by spaces that\n");
        printf("you want added together: ");
        scanf("%d %d %d %d", &a, &b, &c, &d);
        fflush(stdin);
        total = a + b + c + d;
        printf("\nThe total is: %d\n", total);
        printf("Do you want to continue ? ");
        scanf("%c", &answer);
        again = (answer == "y" || answer == "Y") ? 1 : 0;
    } /*  end while  */
}
If you execute this program, you get the following output:
Enter four numbers  separated by spaces that
you want added together: 20 30 40 50
 
The total is: 140
Do you want to continue ? y
Enter four numbers  separated by spaces that
you want added together: 1 2 3 4
 
The total is: 10
Do you want to continue ? n

Function Calls

Syntax

 postfix-expression
( [argument-expression-list] )

Description

Function calls provide a means of invoking a function and passing arguments to it.

The postfix-expression must have the type "pointer to function returning T". The result of the function will be type T. Functions can return any type of object except array and function. Specifically, functions can return structures. In the case of structures, the contents of the returned structure is copied to storage in the calling function. For large structures, this can use a lot of execution time.

Although the expression denoting the called function must actually be a pointer to a function, in typical usage, it is simply a function name. This works because the function name will automatically be converted to a pointer.

C has no call statement. Instead, all function references must be followed by parentheses. The parentheses contain any arguments that are passed to the function. If there are no arguments, the parentheses must still remain. The parentheses can be thought of as a postfix call operator.

If the function name is not declared before it is used, the compiler enters the default declaration:

  extern int identifier();

Function Arguments

Function arguments are expressions. Any type of object can be passed to a function as an argument. Specifically, structures can be passed as arguments. Structure arguments are copied to temporary storage in the called function. The length of time required to copy a structure argument depends upon the structure"s size.

If the function being called has a prototype, each argument is evaluated and converted as if being assigned to an object of the type of the corresponding parameter. If the prototype has an ellipsis, any argument specified after the fixed parameters is subject to the default argument promotions described below.

The compiler checks to see that there are as many arguments as required by the function prototype. If the prototype has an ellipsis, additional parameters are allowed. Otherwise, they are flagged erroneous. Also, the types of the arguments must be assignment-compatible with their corresponding formal parameters, or the compiler will emit a diagnostic message.

If the function does not have a prototype, then the arguments are evaluated and subjected to the default argument promotions; that is, arguments of type char or short (both signed and unsigned) are promoted to type int, and float arguments are promoted to double.

In this case, the compiler does not do any checking between the argument types and the types of the parameters of the function (even if it has seen the definition of the function). Thus, for safety, it is highly advisable to use prototypes wherever possible.

In both cases, arrays of type T are converted to pointers to type T, and functions are converted to pointers to functions.

Function Formal Parameters

Within a function, the formal parameters are lvalues that can be changed during the function execution. This does not change the arguments as they exist in the calling function. It is possible to pass pointers to objects as arguments. The called function can then reference the objects indirectly through the pointers. The result is as if the objects were passed to the function using call by reference. The following swap function illustrates the use of pointers as arguments. The swap() function exchanges two integer values:
  void swap(int *x,int *y)
  {
   int t;
 
   t = *x;
   *x = *y;
   *y = t;
}
To swap the contents of integer variables i and j, you call the function as follows:
  swap(&i, j);
Notice that the addresses of the objects (pointers to int) were passed and not the objects themselves.

Because arrays of type T are converted into pointers to type T, you might think that arrays are passed to functions using call by reference. This is not actually the case. Instead, the address of the first element is passed to the called function. This is still strictly call by value since the pointer is passed by value. Inside the called function, references to the array via the passed starting address, are actually references to the array in the calling function. Arrays are not copied into the address space of the called function.

Function Recursion

All functions are recursive both in the direct and indirect sense. Function A can call itself directly or function A can call function B which, in turn, calls function A. Note that each invocation of a function requires program stack space. For this reason, the depth of recursion depends upon the size of the execution stack.

Increment and Decrement Operators (++, --)

Syntax

Arguments

Description

The increment operator (++) adds 1 to its operand. The decrement operator (--) subtracts 1 from its operand.

The increment and decrement operators are unary. The operand must be a scalar lvalue - it is illegal to increment or decrement a constant, structure, or union. It is legal to increment or decrement pointer variables, but the meaning of adding 1 to a pointer is different from adding 1 to an arithmetic value. This is described in Pointer Operators (*, ->, &).

Postfix and Prefix Forms

There are two forms for each of the operators: postfix and prefix. Both forms increment or decrement the appropriate variable, but they do so at different times. The statement ++i (prefix form) increments i before using its value, while i++ (postfix form) increments it after its value has been used. This difference can be important to your program.

The postfix increment and decrement operators fetch the current value of the variable and store a copy of it in a temporary location. The compiler then increments or decrements the variable. The temporary copy, which has the variable"s value before it was modified, is used in the expression.

In many cases, you are interested only in the side effect, not in the result of the expression. In these instances, it doesn"t matter whether you use postfix or prefix.

You need to be careful, however, when you use the increment and decrement operators within an expression.

Standalone Increment Decrement Expressions
For example, as a stand-alone assignment or as the third expression in a for loop, the side effect is the same whether you use the prefix or postfix versions. The statement
x++;
is equivalent to
++x;
Similarly, the statement
for (j = 0; j <= 10; j++)
is equivalent to
for (j = 0; j <= 10; ++j)
Using Increment and Decrement within Expressions
Consider the following function that inserts newlines into a text string at regular intervals.
#include <stdio.h>
 
void break_line(int interval)
{
    int c, j=0;
    while ((c = getchar()) != "\n") {
        if ((j++ % interval) == 0)
            printf("\n");
        putchar(c);
    }
}
This works because the postfix increment operator is used. If you use the prefix increment operator, the function breaks the first line one character early.
Side Effects of the Increment and Decrement Operators
The increment and decrement operators and the assignment operators cause side effects. That is, they not only result in a value, but they change the value of a variable as well. A problem with side effect operators is that it is not always possible to predict the order in which the side effects occur. Consider the following statement:
x = j * j++;
The C language does not specify which multiplication operand is to be evaluated first. One compiler may evaluate the left operand first, while another evaluates the right operand first. The results are different in the two cases. If j equals 5, and the left operand is evaluated first, the expression will be interpreted as
x = 5 * 5;   /* x is assigned 25 */
If the right operand is evaluated first, the expression becomes
x = 6 * 5;   /* x is assigned 30 */
Statements such as this one are not portable and should be avoided. The side effect problem also crops up in function calls because the C language does not guarantee the order in which arguments are evaluated. For example, the function call
f(a, a++)
is not portable because compilers are free to evaluate the arguments in any order they choose.

To prevent side effect bugs, follow this rule: If you use a side effect operator in an expression, do not use the affected variable anywhere else in the expression. The ambiguous expression above, for instance, can be made unambiguous by breaking it into two assignments:

x = j * j;
++j;
Precedence of Increment and Decrement Operators
The increment and decrement operators have the same precedence, but bind from right to left. So the expression
--j++
is evaluated as
--(j++)
This expression is illegal because j++ is not an lvalue as required by the operator. In general, you should avoid using multiple increment or decrement operators together.

Examples

i=k--;    /* Stores the value of k in i then decrements k. */
j=l++;    /* Stores the value of l in j then increments l. */
i=--k;    /* Decrements k then stores the new value of k in i. */
j=++l;    /* Increments l then stores the new value of l in j. */
The following example uses both prefix and postfix increment and decrement operators:
#include <stdio.h>
int main(void)
{
    int j = 5, k = 5, l = 5, m = 5;
    printf("j: %d\t k: %d\n", j++, k--);
    printf("j: %d\t k: %d\n", j, k);
    printf("l: %d\t m: %d\n", ++l, --m);
    printf("l: %d\t m: %d\n", l, m);
}
The result is as follows:
j: 5    k: 5
j: 6    k: 4
l: 6    m: 4
l: 6    m: 4
The results show that the initial values of j and k are used in the first printf(). They also show that l and m are incremented and decremented, respectively, before the third printf() call.

Logical Operators (&&, ||, !)

Syntax

Arguments

Description

The logical AND operator (&&) and the logical OR (||) operator evaluate the truth or falsehood of pairs of expressions. The AND operator evaluates to 1 if and only if both expressions are true. The OR operator evaluates to 1 if either expression is true. To test whether y is greater than x and less than z, you would write
(x < y) && (y < z)
The logical negation operator (!) takes only one operand. If the operand is true, the result is false; if the operand is false, the result is true.

The operands to the logical operators may be integers or floating-point objects. The expression

1 && -5
results in 1 because both operands are nonzero. The same is true of the expression
0.5 && -5
Logical operators (and the comma and conditional operators) are the only operators for which the order of evaluation of the operands is defined. The compiler must evaluate operands from left to right. Moreover, the compiler is guaranteed not to evaluate an operand if it is unnecessary. For example, in the expression
if ((a != 0) && (b/a == 6.0))
if a equals 0, the expression (b/a == 6) will not be evaluated. This rule can have unexpected consequences when one of the expressions contains side effects.

Truth Table for C"s Logical Operators

In C, true is equivalent to any nonzero value, and false is equivalent to 0. The following table shows the logical tables for each operator, along with the numerical equivalent. All of the operators return 1 for true and 0 for false.
 
Table 26: Truth Table for C"s Logical Operators 
Operand  Operator  Operand  Result 
zero && zero 0
nonzero && zero 0
zero && nonzero 0
nonzero && nonzero 1
zero || zero 0
nonzero || zero 1
zero || nonzero 1
nonzero || nonzero 1
not applicable ! zero 1
  nonzero 0

Examples of Expressions Using the Logical Operators

The following table shows a number of examples that use relational and logical operators. The logical NOT operator has a higher precedence than the others. The AND operator has higher precedence than the OR operator. Both the logical AND and OR operators have lower precedence than the relational and arithmetic operators.
 
Table 27: Examples of Expressions Using the Logical Operators 
Given the following declarations:

int j = 0, m = 1, n = -1; float x = 2.5, y = 0.0; 

   
Expression  Equivalent Expression  Result 
j && m (j) && (m) 0
j < m && n < m (j < m) && (n < m) 1
m + n || ! j (m + n) || (!j) 1
x * 5 && 5 || m / n  ((x * 5) && 5) || (m / n) 1
j <= 10 && x >= 1 && m ((j <= 10) && (x >= 1)) && m 1
!x || !n || m+n ((!x) || (!n)) || (m+n) 0
x * y < j + m || n ((x * y) < (j + m)) || n 1
(x > y) + !j || n++ ((x > y) + (!j)) || (n++) 1
(j || m) + (x || ++n) (j || m) + (x || (++n)) 2

Side Effects in Logical Expressions

Logical operators (and the conditional and comma operators) are the only operators for which the order of evaluation of the operands is defined. For these operators, operands must be evaluated from left to right. However, the system evaluates only as much of a logical expression as it needs to determine the result. In many cases, this means that the system does not need to evaluate the entire expression. For instance, consider the following expression:
if ((a < b) && (c == d))
The system begins by evaluating (a < b). If a is not less than b, the system knows that the entire expression is false, so it will not evaluate (c == d). This can cause problems if some of the expressions contain side effects:
if ((a < b) && (c == d++))
In this case, d is only incremented when a is less than b. This may or may not be what the programmer intended. In general, you should avoid using side effect operators in logical expressions.

Example

/*  Program name is "logical_ops_example". This program  */
/*  shows how logical operators are used.                */
#include <stdio.h>
 
int main(void)
{
    int won_lottery, enough_vacation, money_saved;
    char answer;
 
    won_lottery = enough_vacation = money_saved = 0;
 
    printf("\nThis program determines whether you can ");
    printf("take your next vacation in Europe.\n");
    printf("Have you won the lottery? y or n: ");
    fflush(stdin);
    scanf("%c", &answer);
  if (answer == "y")
      won_lottery = 1;
 
    printf("Do you have enough vacation days saved? \
y or n: ");
    fflush(stdin);
    scanf ("%c", &answer);
    if (answer == "y")
        enough_vacation = 1;
 
    printf("Have you saved enough money for the trip? \
y or n: ");
    fflush(stdin);
    scanf("%c", &answer);
    if (answer == "y")
        money_saved = 1;
 
    printf("\n");
    if (won_lottery)
    {
        printf("Why do you need a program to decide if you");
        printf(" can afford a trip to Europe?\n");
    }  /*  end if  */
    if (won_lottery || (enough_vacation &&money_saved))
        printf("Look out Paris!\n");
    else if (enough_vacation &&(!money_saved))
        printf("You"ve got the time, but you haven"t got \
the dollars.\n");
    else if (!enough_vacation || (!money_saved))
    {
        printf("Tough luck. Try saving your money and ");
        printf("vacation days next year.\n");
    }  /*  end else/if  */
}
If you execute this program, you get the following output:
This program determines whether you can take your next vacation
in Europe.
Have you won the lottery? y or n: y
Do you have enough vacation days saved? y or n: n
Have you saved enough money for the trip? y or n: n
 
Why do you need a program to decide if you can afford a trip to 
Europe?
Look out Paris!

Pointer Operators (*, ->, &)

Syntax

Description

A pointer variable is a variable that can hold the address of an object.

Assigning an Address Value to a Pointer

To declare a pointer variable, you precede the variable name with an asterisk. The following declaration, for example, makes ptr a variable that can hold addresses of long int variables:
long *ptr;
The data type, long in this case, refers to the type of variable that ptr can point to. To assign a pointer variable with the virtual address of a variable, you can use the address-of operator &. For instance, the following is legal:
long *ptr;
long long_var;
ptr = &long_var; /* Assign the address of long_var to ptr. */
But this is illegal:
long *ptr;
float float_var;
ptr = &float_var; /* ILLEGAL - because ptr can only store the
                           address of a long int.                   */
The following program illustrates the difference between a pointer variable and an integer variable.
/* Program name is "ptr_example1". */
#include <stdio.h>
int main()
{
    int j = 1;
    int *pj;
    pj = &j;  /* Assign the address of j to pj */
    printf("The value of j is: %d\n", j);
    printf("The address of j is: %p\n", pj);
}
If you run this program (in 32-bit mode), the output looks something like this:
The value of j is: 1
The address of j is: 7b033240

Dereferencing a Pointer

To dereference a pointer (get the value stored at the pointer address), use the * operator. The program below shows how dereferencing works:
/* Program name is "ptr_example2". */
 
#include <stdio.h>
 
int main(void)
{
    char *p_ch;
    char ch1 = "A", ch2;
    printf("The address of p_ch is %p\n", &p_ch);
    p_ch = &ch1;
    printf("The value of p_ch is %p\n", p_ch);
    printf("The dereferenced value of p_ch is %c\n",
            *p_ch);
}
The output from this program looks something like this:
The address of p_ch is 7b033240
The value of p_ch is 7b033244
The dereferenced value of p_ch is A
This is a roundabout and somewhat contrived example that assigns the character A to both ch1 and ch2. It does, however, illustrate the effect of the dereference (*) operator. The variable ch1 is initialized to A. The first printf() call displays the address of the pointer variable p_ch. In the next step, p_ch is assigned the address of ch1, which is also displayed. Finally, the dereferenced value of p_ch is displayed and ch2 is assigned to it.

The expression *p_ch is interpreted as "Take the address value stored in p_ch and get the value stored at that address." This gives us a new way to look at the declaration. The data type in the pointer declaration indicates what type of value results when the pointer is dereferenced. For instance, the declaration

float *fp;
means that when *fp appears as an expression, the result will be a float value.

The expression *fp can also appear on the left side of an expression:

*fp = 3.15;
In this case, we are storing a value (3.15) at the location designated by the pointer fp. This is different from
fp = 3.15;
which attempts to store the address 3.15 in fp. This, by the way, is illegal, because addresses are not the same as floating-point values.

When you assign a value through a dereferenced pointer, make sure that the data types agree. For example:

/* Program name is "ptr_example3". */
 
#include <stdio.h>
 
int main(void)
{
    float f = 1.17e3, g;
    int *ip;
    ip = &f;
    g = *ip;
    printf("The value of f is: %f\n", f);
    printf("The value of g is %f\n", g);
}
The result is
The value of f is: 1170.000000
The value of g is: 1150435328.000000
In the preceding example, instead of getting the value of f, g gets an erroneous value because ip is a pointer to an int, not a float. The HP C compiler issues a warning message when a pointer type is unmatched. If you compile the preceding program, for instance, you receive the following message:
cc: "ptr_example3.c", line 9: warning 604: Pointers are not
    assignment-compatible.

Pointer Arithmetic

The following arithmetic operations with pointers are legal: All other arithmetic operations with pointers are illegal.

When you add or subtract an integer to or from a pointer, the compiler automatically scales the integer to the pointer"s type. In this way, the integer always represents the number of objects to jump, not the number of bytes. For example, consider the following program fragment:

int x[10], *p1x = x, *p2x;
 
p2x = p1x + 3;
Since pointer p1x points to a variable (x) that is 4 bytes long, then the expression p1x + 3 actually increments p1x by 12 (4 * 3), rather than by 3.

It is legal to subtract one pointer value from another, provided that the pointers point to the same type of object. This operation yields an integer value that represents the number of objects between the two pointers. If the first pointer represents a lower address than the second pointer, the result is negative. For example,

&a[3] - &a[0]
evaluates to 3, but
&a[0] - &a[3]
evaluates to -3.

It is also legal to subtract an integral value from a pointer value. This type of expression yields a pointer value. The following examples illustrate some legal and illegal pointer expressions:

long *p1, *p2;
int a[5], j;
char *p3;
 
p1 = a;       /* Same as p1 = &a[0] */
p2 = p1 + 4;  /* legal */
j = p2 - p1;  /* legal -- j is assigned 4  */
j = p1 - p2;  /* legal -- j is assigned -4 */
p1 = p2 - 2;  /* legal -- p2 points to a[2] */
p3 = p1 - 1;  /* ILLEGAL -- different pointer types*/
j = p1 - p3;  /* ILLEGAL -- different pointer types*/
j = p1 + p2;  /* ILLEGAL -- can"t add pointers */

Arrays and Pointers

Arrays and pointers have a close relationship in the C language. You can exploit this relationship in order to write more efficient code. See the discussion of Array Subscripting ([ ]) for more information.

Casting a Pointer"s Type

A pointer to one type may be cast to a pointer to any other type. For example, in the following statements, a pointer to an int is cast to a pointer to a char. Presumably, the function func() expects a pointer to a char, not a pointer to an int.
int i, *p = &i;
func((char *) p);
As a second example, a pointer to a char is cast to a pointer to struct H:
struct H {
    int q;
} x, y;
char  *genp = &x;
y = (struct H *)genp->q;
See Cast Operator for more information about the cast operator.

It is always legal to assign any pointer type to a generic pointer, and vice versa, without a cast. For example:

float x, *fp = &x;
int j, *pj = &j;
void *pv;
pv = fp;  /* legal */
fp = pv;  /* legal */
In both these cases, the pointers are implicitly cast to the target type before being assigned.

Null Pointers

The C language supports the notion of a null pointer - that is, a pointer that is guaranteed not to point to a valid object. A null pointer is any pointer assigned the integral value 0. For example:
char *p;
p = 0;  /* make p a null pointer */
In this one case - assignment of 0 - you do not need to cast the integral expression to the pointer type.

Null pointers are particularly useful in control-flow statements, since the zero-valued pointer evaluates to false, whereas all other pointer values evaluate to true. For example, the following while loop continues iterating until p is a null pointer:

char *p;
 . . .
while (p) {
 . . .
/* iterate until p is a null pointer */
 . . .
}
This use of null pointers is particularly prevalent in applications that use arrays of pointers.

The compiler does not prevent you from attempting to dereference a null pointer; however, doing so may trigger a run-time access violation. Therefore, if it is possible that a pointer variable is a null pointer, you should make some sort of test like the following when dereferencing it:

if (px && *px)   /* if px = 0, expression will short-circuit
  . . .            before dereferencing occurs*/
Null pointers are a portable feature.

Example 1

/* Program name is "pointer_array_example1". This program
 * shows how to access a 1-dimensional array through
 * pointers. Function count_chars returns the number of
 * characters in the string passed to it.
 * Note that *arg is equivalent to a_word[0];
 * arg + 1 is equivalent to a_word[1]...   
 */
#include <stdio.h>
 
int count_chars(char *arg)
{
    int count = 0;
    while (*arg++)
        count++;
    return count;
}
 
int main(void)
{
    char a_word[30];
    int number_of_characters;
    printf("Enter a word -- ");
    scanf("%s", a_word);
    number_of_characters = count_chars(a_word);
    printf("%s contains %d characters.\n", a_word,
            number_of_characters);
}
If you execute this program, you get the following output:
Enter a word -- Marilyn
Marilyn contains 7 characters.

Example 2

/*  Program name is "pointer_array_example2".  This program
 *  demonstrates two ways to access a 2-dimensional array.
 */

#include <stdio.h>
 
int main(void)
{
    int count = 0, which_name;
    char c1, c2;
    static char str[5][10] = {"Phil", "Sandi", "Barry",
                              "David", "Amy"};
    static char *pstr[5] = { str[0], str[1], str[2],
                             str[3], str[4]};
/*  pstr is an array of pointers.  Each element in the array
 *  points to the beginning of one of the arrays in str.
 */
/* Prompt for information. */
    printf("Which name do you want to retrieve?\n");
    printf("Enter 0 for the first name,\n");
    printf("      1 for the second name, etc. -- ");
    scanf("%d", &which_name);
/* Print name directly through array. */
    while (c1 = str[which_name][count++])
        printf("%c", c1);
    printf("\n");
 
/* Print same name indirectly through an array of pointers. */
    while (c2 = *(pstr[which_name]++))
        printf("%c", c2);
/* We could also have used the following statement instead of
 * the two previous ones:  printf("%s", pstr[which_name]);
 */
    printf("\n");
}
If you execute this program, you get the following output:
Which name do you want to retrieve?
Enter 0 for the first name,
      1 for the second name, etc. -- 1
Sandi
Sandi

Relational Operators (>, >=, <, ==, !=)

Syntax

Arguments

Description

A relational expression consists of two expressions separated by one of six relational operators. The relational expression evaluates either to 1 (true) or 0 (false).

The equality operator (==) performs the same function as Pascal"s = or FORTRAN"s .EQ.; it just looks different. Although the equality operator looks similar to the assignment operator (=), the two operators serve completely different purposes. Use the assignment operator when you want to assign a value to a variable, but use the equality operator when you want to test the value of an expression.

Confusing = with ==

One of the most common mistakes made by beginners and experts alike is to use the assignment operator (=) instead of the equality operator (==). For instance:
while (j = 5)
   do_something();
What is intended, clearly, is that the do_something() function should only be invoked if j equals five. It should be written
while (j == 5)
  do_something();
The first version is syntactically legal, since all expressions have a value. The value of the expression j = 5 is 5. Since this is a nonzero value, the while expression will always evaluate to true and do_something() will always be invoked.

Relational Operators Precedence Rules

Relational operators have lower precedence than arithmetic operators. The expression
a + b * c < d / f
is evaluated as if it had been written
(a + (b * c)) < (d / f)
Among the relational operators, >, >=, <, and <= have the same precedence. The == and != operators have lower precedence. All of the relational operators have left-to-right associativity. The following table illustrates how the compiler parses complex relational expressions.
 
Table 28: Examples of Expressions Using the Relational Operators 
Given the following declaration:

int j = 0, m = 1, n = -1; float x = 2.5, y = 0.0;

   
Expression  Equivalent Expressions  Result 
j > m j > m 0
m / n < x (m / n) < x 1
j <= m >= n ((j <=m) >= n) 1
j <= x == m ((j <= x) == m) 1
- x + j == y > n > m ((-x) + j) == ((y > n) >= m) 0
x += (y >= n) x = (x + (y >= n)) 3.5
++j == m != y * 2 ((++j) == m) != (y * 2) 1

Evaluation of Relational Expressions

Relational expressions are often called Boolean expressions, in recognition of the nineteenth-century mathematician and logician, George Boole. Many programming languages, such as Pascal, have Boolean data types for representing true and false. The C language, however, represents these values with integers. Zero is equivalent to false, and any nonzero value is considered true.

The value of a relational expression is an integer, either 1 (indicating the expression is true) or 0 (indicating the expression is false). The examples in the following table illustrate how relational expressions are evaluated:
 
Table 29: Relational Expressions 
Expression  Value 
-1 < 0 1
0 > 1 0
5 == 5 1
7 != -3 1
1 >= -1 1
1 > 10 0

Because Boolean values are represented as integers, you can write

if (j)
   statement;
If j is any nonzero value, statement is executed; if j equals 0, statement is skipped. Likewise, the statement
if (isalpha(ch))
is exactly the same as
if (isalpha(ch) != 0)
The practice of using a function call as a Boolean expression is a common idiom in C. It is especially effective for functions that return 0 if an error occurs, since you can use a construct such as
if (func())
    proceed;
else
    error
handler;

Dangers of Comparing Floating-Point Values

You may get unexpected results if you compare floating-point values for equality because floating-point representations are inexact for some numbers. For example, the following expression, though algebraically true, will evaluate to false on many computers:
(1.0/3.0 + 1.0/3.0 + 1.0/3.0) == 1.0
This evaluates to 0 (false) because the fraction 1.0/3.0 contains an infinite number of decimal places (3.33333...). The computer is only capable of holding a limited number of decimal places, so it rounds each occurrence of 1/3. As a result, the left side of the expression does not equal exactly 1.0.

This problem can occur in even more subtle ways. Consider the following code:

double divide(double num, double denom)
{
    return num/denom;
}
int main(void)
{
    double c, a = 1.0, b = 3.0;
    c = a/b;
    if (c != divide(a, b))
        printf("Fuzzy doubles\n");
}
Surprisingly, the value stored in c may not equal the value returned by divide(). This anomaly occurs due to the fact that some computers can represent more decimal places for values stored in registers than for values stored in memory. Because the value returned by divide() is never stored in memory, it may not be equal to the value c, which has been rounded for memory storage.

NOTE  To avoid bugs caused by inexact floating-point representations, you should refrain from using strict equality comparisons with floating-point types.

Example

/*  Program name is "relational_example". This program
 *  does some mathematical calculations and shows
 *  C"s relational operators in action.
 */
#include <stdio.h>
 
int main(void)
{
    int num, i;
    printf("\n");
    num = 5;
    printf("The number is: %d\n", num);
    for (i = 0; i <= 2; i++)
    {
        if (num < 25)
        {
            num *= num;
            printf("The number squared is: %d\n", num);
        }
        else if (num == 25) {
            num *= 2;
            printf("Then, when you double that, you get: %d\n", num);
        }
        else if (num > 25)
        {
            num -= 45;
            printf("And when you subtract 45, you"re back where ");
            printf("you started at: %d\n", num);
        } /* end if */
    } /* end for */
 
    if (num != 5)
        printf("The programmer made an error in setting up this \
example\n");
}
If you execute this program, you get the following output:
The number is: 5
The number squared is: 25
Then, when you double that, you get: 50
And when you subtract 45, you"re back where you started at: 5

sizeof Operator

Syntax

sizeof exp;

sizeof (type_name)

Arguments

Description

The sizeof unary operator finds the size of an object. The sizeof operator accepts two types of operands: an expression or a data type. If you use an expression operand, the expression itself is not evaluated - the compiler only determines what type the result would be. Any side effects in the expression, therefore, will not have an effect. The result type of the sizeof operator is unsigned int.

If the operand is an expression, sizeof returns the number of bytes that the result occupies in memory:

/*  Returns the size of an int (4 if ints are four
 *  bytes long)
 */
sizeof(3 + 5)
 
/*  Returns the size of a double (8 if doubles are
 *  eight bytes long)
 */
sizeof(3.0 + 5)
 
/*  Returns the size of a float (4 if floats are
 *  four bytes long)
 */
float x;
sizeof(x)
For expressions, the parentheses are optional, so the following is legal:
sizeof x
By convention, however, the parentheses are usually included.

The operand can also be a data type, in which case the result is the length in bytes of objects of that type:

sizeof(char)    /* 1 on all machines */
sizeof(short)   /* 2 on HP 9000 Series */
sizeof(float)   /* 4 on HP 9000 Series  */
sizeof(int *)   /* 4 on HP 9000 Series */
The parentheses are required if the operand is a data type.

NOTE  The results of most sizeof expressions are implementation dependent. The only result that is guaranteed is the size of a char, which is always 1.

In general, the sizeof operator is used to find the size of aggregate data objects such as arrays and structures.

Example

You can use the sizeof operator to obtain information about the sizes of objects in your C environment. The following prints the sizes of the basic data types:
/*  Program name is "sizeof_example".  This program
 *  demonstrates a few uses of the sizeof operator.
 */

#include <stdio.h>
int main(void)
{
    printf("TYPE\t\tSIZE\n\n");
    printf("char\t\t%d\n", sizeof(char));
    printf("short\t\t%d\n", sizeof(short));
    printf("int\t\t%d\n", sizeof(int));
    printf("float\t\t%d\n", sizeof(float));
    printf("double\t\t%d\n", sizeof(double));
}
If you execute this program, you get the following output:
TYPE        SIZE
 
char        1
short       2
int         4
float       4
double      8

Structure and Union Members (., ->)

A member of a structure or a union can be referenced using either of two operators: the period or the right arrow.

Syntax

 postfix-expression
. identifier postfix-expression -> identifier

Description

Use the period to reference members of structures and unions directly. Use the arrow operator to reference members of structures and unions pointed to by pointers. The arrow operator combines the functions of indirection through a pointer and member selection. If P is a pointer to a structure with a member M, the expression P->M is identical to (*P).M.

The postfix-expression in the first alternative must be a structure or a union. The expression is followed by a period (.) and an identifier. The identifier must name a member defined as part of the structure or union referenced in the postfix-expression. The value of the expression is the value of the named member. It is an lvalue if the postfix-expression is an lvalue.

If the postfix-expression is a pointer to a structure or a pointer to a union, follow it with an arrow (composed of the - character followed by the |) and an identifier. The identifier must name a member of the structure or union which the pointer references. The value of the primary expression is the value of the named member. The resulting expression is an lvalue.

The . operator and the -> operator are closely related. If S is a structure, M is a member of structure S, and &S is a valid pointer expression, S.M is the same as (&S)->M.

Operator Precedence

Precedence is the order in which the compiler groups operands with operators. The C compiler evaluates certain operators and their operands before others. If operands are not grouped using parentheses, the compiler groups them according to its own rules.

The following lists C operator precedence in highest to lowest precedence:
 
Table 30: C Operator Precedence 
Class of operator  Operators  Grouping 
primary () [] -> . left to right
unary (type casting)

sizeof

& (address of)

* (dereference)

- (reverse sign)

~ ! 

++ --

right to left
multiplicative * / % left to right
additive + - left to right
shift << >> left to right
relational < <= > >= left to right
equality == !=  left to right
bitwise AND & left to right
bitwise XOR ^ left to right
bitwise OR left to right
logical AND && left to right
logical OR || left to right
conditional ?: right to left
assignment = += -= *= /= %= >>= <<= &= ^= |=  right to left
comma , left to right

Precedence among Operators of Same Class

Most operators group from the left to the right but some group from the right to the left. The grouping indicates how an expression containing several operators of the same precedence will be evaluated. Left to right grouping means the expression
a/b * c/d
behaves as if it had been written:
(((a/b)*c)/d)
Likewise, an operator that groups from the right to the left causes the expression
a = b = c
to behave as if it had been written:
a = (b = c)

Operator Quick Reference

Table 31: C Operators
Symbol  Meaning 
! logical negation
!= inequality
% remainder
& AND (bitwise) and address-of
&& AND (logical)
() cast and function call
* multiplication and indirection
+ addition and unary plus
++ increment, prefix or postfix
, comma
- subtraction and unary minus
-- decrement, prefix or postfix
-> structure/union pointer (arrow)
. structure/union member (dot)
/ division
< less-than
<< left-shift
<= less-than-or-equal-to
= assignment
== equality
> greater-than
>= greater-than-or-equal-to
>> right shift
?: conditional
[ ] subscript
^ OR (bitwise exclusive)
| OR (bitwise inclusive)
|| OR (logical)
~ complement
op= assignment, compound
sizeof compute object size at translation-time

Constant Expressions

Constant expressions contain only constant values. For example, the following are all constant expressions:
5
5 + 6 * 13 / 3.0
"a"

Integral Expressions

Integer expressions are expressions that, after all automatic and explicit type conversions, produce a result that has one of the integer types. If j and k are integers, the following are all integral expressions:
j
j * k
j / k + 3
k - "a"
3 + (int) 5.0

Floating-Point Expressions

Floating-point expressions are expressions that, after all automatic and explicit type conversions, produce a result that has one of the floating-point types. If x is a float or double, the following are floating-point expressions:
x
x + 3
x / y * 5
3.0
3.0 - 2
3 + (float) 4

lvalue Expressions

An lvalue (pronounced "el-value") is an expression that refers to a region of storage that can be manipulated.

For example, all simple variables, like ints and floats are lvalues. An element of an array is also an lvalue; however an entire array is not. A member of a structure or union is an lvalue; an entire structure or union is not.

Given the following declarations:

int *p, a, b;
int arr[4];
int func();
 
   a           /* lvalue */
   a + b       /* Not an lvalue */
   p           /* lvalue */
   *p          /* lvalue */
   arr         /* lvalue, but not modifiable */
   *(arr + a)  /* lvalue */
   arr[a]      /* lvalue, equivalent to *(arr+a) */
   func        /* Not an lvalue */
   func()      /* Not an lvalue */

Pointer Expressions

Pointer expressions are expressions that evaluate to an address value. These include expressions containing pointer variables, the address-of operator (&), string literals, and array names. If p is a pointer and j is an int, the following are pointer expressions:
p
&j
p + 1
"abc"
(char *) 0x000fffff

Evaluation of Expressions

Expressions are evaluated at run time. The results of the evaluation are called byproduct values. For many expressions, you won"t know or care what this byproduct is. In some expressions, though, you can exploit this feature to write more compact code.

Examples

The following expression is an assignment.
x = 6;
The value 6 is both the byproduct value and the value that gets assigned to x. The byproduct value is not used.

The following example uses the byproduct value:

y = x = 6;
The equals operator binds from right to left; therefore, C first evaluates the expression x = 6. The byproduct of this operation is 6, so C sees the second operation as
y = 6
Now, consider the following relational operator expression:
(10 < j < 20)
It is incorrect to use an expression like this to find out whether j is between 10 and 20. Since the relational operators bind from left to right, C first evaluates
10 < j
The byproduct of a relational operation is 0 if the comparison is false and 1 if the comparison is true.

Assuming that j equals 5, the expression 10 < j is false. The byproduct will be 0. Thus, the next expression evaluated:

0 < 20
is true (or 1). This is not the expected answer when j equals 5.

Finally, consider the following fragment:

static char  a_char, c[20] = {"Valerie"}, *pc = c;
 
  while (a_char = *pc++) {
            .  .  .
This while statement uses C"s ability to both assign and test a value. Every iteration of while assigns a new value to variable a_char. The byproduct of an assignment is equal to the value that gets assigned. The byproduct value will remain nonzero until the end of the string is reached. When that happens, the byproduct value will become 0 (false), and the while loop will end.

Evaluation Order of Subexpressions

The C language does not define the evaluation order of subexpressions within a larger expression except in the special cases of the &&, ||, ?:, and , operators. When programming in other computer languages, this may not be a concern. C"s rich operator set, however, introduces operations that produce side effects. The ++ operator is a prime example. The ++ operator increments a value by 1 and provides the value for further calculations. For this reason, expressions such as
b = ++a*2 + ++a*4;
are dangerous. The language does not specify whether the variable a is first incremented and multiplied by 4 or is first incremented and multiplied by 2. The value of this expression is undefined.