Operators in C

Subpage of C Deep Dive

A close look at the C language.

Arithmetic Operators

These perform standard mathematical operations. All of these are left associative.

  • +-*/: Addition, Subtraction, Multiplication, Division.
  • % (Modulo): Returns the remainder of an integer division.

    For example -10 % 4 will yield -2.

Important: The sign of the result for % is implementation-defined for negative operands in C89, but since C99, it follows the dividend (the first operand). -7 % 3 is 1.

Unary Operators

unary operator is an operator that acts on a single operand to produce a new value. This contrasts with binary operators (like +*) which act on two operands. All these operators are right associative.

The unary operators in C are:

  • + (Unary Plus): Indicates a positive value. (e.g., +5). It's rarely used as values are positive by default.
  • - (Unary Minus): Negates the value of its operand. (e.g., 5).
  • ! (Logical NOT): Inverts the truth value of its operand. If the operand is non-zero (true), it returns 0 (false). If the operand is zero (false), it returns 1 (true). (e.g., !(a == b) is equivalent to a != b).
  • ~ (Bitwise NOT): Flips every bit in the operand. (e.g., ~0b1010 becomes 0b0101). This is also called the one's complement.
  • sizeof: Returns the size in bytes of its operand (a type or a variable). This is evaluated at compile-time, not runtime. (e.g., sizeof(int)sizeof myArray).

    Note that sizeof can be used on both types and variables.

  • & (Address-of): Returns the memory address of its operand. (e.g., &variable gives you a pointer to variable).
  • * (Dereference): Accesses the value a pointer points to. It is the inverse of the address-of operator. (e.g., pointer gives the value stored at the address held by pointer).

Increment/Decrement Operators

These are a specific, very common subset of unary operators that modify their operand.

  • ++ (Increment): Increases a variable's value by 1.
  • -- (Decrement): Decreases a variable's value by 1.

They can be used in two forms, which affects when the increment happens:

  • Prefix (e.g., ++a): The increment happens first, and the new value is then used in the expression.
  • Postfix (e.g., a++): The current value is used in the expression first, and then the increment happens.
int a = 5;
int b = ++a; // a becomes 6, then b is assigned 6.
int c = a++; // c is assigned 6, then a becomes 7.

Relational Operators

These compare two operands and return an integer value of 1 (true) or 0 (false).

  • == (Equal to), != (Not equal to)
  • < (Less than), > (Greater than)
  • <= (Less than or equal to), >= (Greater than or equal to)

Crucial Note: The equality operator is == (double equals), not = (single equals, which is assignment). Using = in a condition is a common bug.

Logical Operators

These operate on true/false values (in C, any non-zero is true, zero is false).

  • && (Logical AND): Returns 1 only if both operands are true.
  • || (Logical OR): Returns 1 if at least one operand is true.

Short-Circuiting: This is a critical behavior. If the result can be determined by the first operand, the second is never evaluated.

  • (a != 0) && (b / a > 5): If a is 0, the second expression (b / a) is skipped, preventing a division-by-zero error.
  • (a == 0) || (b / a > 5): If a is 0, the second expression is skipped.

Bitwise Operators

These perform operations on the individual bits of integer operands.

  • & (Bitwise AND): A bit in the result is 1 only if both corresponding bits are 1. Often used for masking (turning bits off).
  • | (Bitwise OR): A bit in the result is 1 if at least one corresponding bit is 1. Often used for turning bits on.
  • ^ (Bitwise XOR): A bit in the result is 1 if the corresponding bits are different. Often used for toggling bits.
  • << (Left Shift): Shifts bits to the left. Equivalent to multiplying by 2 for each shift (if no overflow).
  • >> (Right Shift): Shifts bits to the right. Equivalent to integer division by 2 for each shift. Whether it's a logical (zeros shifted in) or arithmetic (sign bit preserved) shift is implementation-defined for negative numbers.

Assignment Operators

These assign a value to a variable.

  • = (Simple Assignment): a = b;
  • Compound Assignment: These perform an operation and then assign the result. The form is op=. They are shorthand and often more efficient.
    • a += b; is equivalent to a = a + b;
    • Others: ==/=%=&=|=^=<<=>>=

The assignment operator is an expression with the following properties:

  • Returns the value being assigned
  • Right-to-left associativity

As a result we can write statement such as:

a = b = c = 10;
a = (b = (c = 10)); // Equivalent to the above statement

This works because the expression c = 10 assigns the number 10 to a variables and the expression c = 10 itself has the value that was assigned: 10. Thus, all the variables in the expression get set to 10. Such behaviour is often referred to as cascading.

Other Important Operators

  • Conditional (Ternary) Operator (? :): A shorthand for an if-else statement.

    Syntax: condition ? expression_if_true : expression_if_false

    int max = (a > b) ? a : b; // Sets max to the larger of a or b.
  • Comma Operator (,): Evaluates two expressions, discarding the result of the first and returning the result of the second. It has the lowest precedence.
    int a = (x++, y++); // x is incremented, then y is incremented and assigned to a.

    It's most commonly used in the initialization and increment parts of a for loop: for(i=0, j=10; i<j; i++, j--).

  • Member Operators:
    • . (Direct Member Access): Accesses a member of a struct or union variable. myStruct.age
    • > (Indirect Member Access): Accesses a member of a struct or union through a pointermyStructPtr->age is equivalent to (*myStructPtr).age.

Here is a summary of operators:

int a = 5, b = 2;
int sum = a + b;            /* arithmetic */
int mod = a % b;            /* remainder */
int *p = &a;                /* address-of */
int val = *p;               /* dereference */
p++;                        /* pointer arithmetic */
int t = (a > b) ? a : b;    /* conditional */
a <<= 1;                    /* compound assignment (left shift) */
int cond = (a > 0) && (b != 0); /* logical and (short-circuits) */

Evaluation Order

This is a critical and often misunderstood point. Precedence and associativity determine grouping, not the order of evaluation of operands.

Precedence: Determines which operator is bound more tightly to its operands. For example, in a + b * c* has higher precedence than +, so it's evaluated as a + (b * c).

Here is a detailed look into precedence.

The following table summarizes the precedence and associativity of C operators, listed from highest precedence (evaluated first) to lowest precedence (evaluated last).

PrecedenceOperatorDescriptionAssociativity
1() [] . -> ++ -- (postfix)Grouping, array subscript, member access, post-increment/decrementLeft-to-right
2++ -- + - ! ~ * & sizeof (type)Unary prefix increment/decrement, plus/minus, logical/bitwise NOT, dereference, address-of, size, type castRight-to-left
3* / %Multiplication, division, remainderLeft-to-right
4+ -Addition, subtractionLeft-to-right
5<< >>Bitwise left and right shiftLeft-to-right
6< <= > >=Relational less than, less than or equal to, greater than, greater than or equal toLeft-to-right
7== !=Equality, inequalityLeft-to-right
8&Bitwise ANDLeft-to-right
9^Bitwise XOR (exclusive OR)Left-to-right
10|Bitwise inclusive ORLeft-to-right
11&&Logical ANDLeft-to-right
12||Logical ORLeft-to-right
13?:Conditional (ternary) operatorRight-to-left
14= += -= *= /= %= <<= >>= &= ^= |=Assignment and compound assignmentRight-to-left
15,Comma operator (sequential evaluation)Left-to-right

Associativity: Determines the order of evaluation when operators of the same precedence appear in an expression.

  • Left-to-Right: a - b - c is (a - b) - c
  • Right-to-Left: a = b = c is a = (b = c)

Note: Avoid using increment/decrement operators (++--) in complex expressions where the order of evaluation matters, as it can lead to undefined behaviora[i] = i++; is a classic example of bad code.

/ Continue

Follow the technical trail.

Use the dense notes as the source material, then move through the guided route, writing, or project proof when you want a cleaner entry point.