ProgrammingC programmer

Explain how the logical AND (&&) and OR (||) operators work in C. What are the features of what is called 'short-circuit evaluation'? How can a misunderstanding of the behavior of these operators lead to errors?

Pass interviews with Hintsage AI assistant

Answer.

History of the question:

The logical operators && and || were introduced in C to evaluate complex logical conditions. A key feature of their operation is the support of short-circuit evaluation: the second operand is not evaluated if the result can be deterministically known from the first one.

Problem:

Many programmers expect that both operands are always evaluated, or incorrectly use side effects in the second operand, assuming it will always be executed. In practice, this leads to errors, resource leaks, and unexpected behavior.

Solution:

Understanding the mechanism of short-circuit evaluation helps to build safe constructs, especially in pointer checks, resources, and file handling. Using side effects in the right part of an expression is only acceptable if done consciously. Example of a safe check:

if (ptr && ptr->field) { /* ... */ }

Key features:

  • && and || utilize the 'short-circuit' rule — the second operand is evaluated only if the result is not determined after the first.
  • Short-circuiting helps to avoid dereferencing null pointers, division by zero, and other dangerous situations.
  • Errors occur when nesting expressions with side effects, where the right part may not execute at all.

Trick questions.

Will the expression f() be executed in the fragment: if (0 && f())

No, the function f() will not be called because the result is already clear — the expression is false, further evaluation is pointless.

And in the next statement: if (1 || f())?

Again, f() will not be called: the result is already true after the first operand.

Is it possible to use && and || operators to control the order of execution of functions with side effects?

Technically yes, but such control leads to unreadable and unstable code. It is better to explicitly state the order of function calls, without relying on short-circuit behavior for side effects.

Common mistakes and anti-patterns

  • Using side effects in the right part of expressions hoping they will always execute.
  • Failing to check for NULL before dereferencing a pointer.
  • Complex nested conditions that hinder understanding of the evaluation order.

Real-life example

Negative case

if (flag || process()) { // ... }

The process will never be called if the flag is true.

Pros:

  • There is protection from unnecessary work.

Cons:

  • Side effects do not occur when expected, resulting in a bug.

Positive case

if (!flag) process();

Pros:

  • Readable, safe, and predictable code.

Cons:

  • Slightly more lines, requires more explicit control, but readability and predictability increase.