Use of short-circuit evaluation involves leveraging the guaranteed left-to-right evaluation order to create safe "guard" conditions with side effects, provide default values, and perform performance micro-optimizations.
Uses of Short-Circuit Evaluation (&& and ||) ⚡️
The logical AND (&&) and OR (||) operators in C do more than just combine conditions; they provide a guaranteed left-to-right order of evaluation with a sequence point after the left operand. This guarantee is a powerful tool for writing sophisticated and robust code.
1. Guaranteed Order and Side Effects
Unlike most C operators (like + or *), which have an unspecified order of evaluation for their operands, the logical operators are different. For A && B or A || B, the expression A is always fully evaluated first, including all of its side effects. Only then, if necessary, is B evaluated.
This allows you to safely sequence operations, particularly function calls, where the second operation depends on the success of the first.
Example: Sequential Function Calls
This is a common pattern for operations that can fail, like initialization routines or transactions.
C
#include <stdio.h>
#include <stdbool.h>
bool connect_to_database() {
printf("Attempting to connect to database...\n");
return true; // Simulate success
}
bool perform_transaction() {
printf("Performing transaction...\n");
return true;
}
int main() {
// perform_transaction() is guaranteed NOT to be called if
// connect_to_database() returns false (0).
if (connect_to_database() && perform_transaction()) {
printf("Operation completed successfully.\n");
} else {
printf("Operation failed at some stage.\n");
}
return 0;
}
2. The "Guard Clause" Pattern
The most critical use of short-circuiting is to "guard" a potentially dangerous operation. The first operand acts as a safety check that prevents the second operand from ever being evaluated if it would cause an error (like a crash).
Example 1: The Divisor Guard
This prevents a division-by-zero error, which would crash the program.
C
#include <stdio.h>
int main() {
int numerator = 100;
int divisor = 0;
// The right side is NEVER evaluated because 'divisor != 0' is false.
// This prevents a crash.
if (divisor != 0 && (numerator / divisor) > 5) {
printf("Result is greater than 5.\n");
} else {
printf("Cannot perform calculation, divisor is zero.\n");
}
return 0;
}
Example 2: The Pointer Guard
This prevents a segmentation fault from dereferencing a NULL pointer.
C
if (ptr != NULL && ptr->member == 10) {
// Safe to access ptr->member
}
3. Providing Default Values with ||
The || operator evaluates to the first "truthy" (non-zero) value it finds. This can be cleverly used to assign a default value if a primary value is unavailable (e.g., NULL or 0).
Example: Setting a Configuration Value
C
#include <stdio.h>
// This function might fail and return NULL
char* get_user_name() {
return NULL; // Simulate user name not being set
}
char* get_default_name() {
return "Guest";
}
int main() {
// If get_user_name() returns NULL (false), the right side is evaluated,
// and its return value ("Guest") is assigned to 'name'.
char *name = get_user_name() || get_default_name();
printf("Hello, %s!\n", name); // Prints "Hello, Guest!"
return 0;
}
4. Micro-optimizing Conditional Chains
In performance-critical code, the order of conditions in a long chain can matter. You can use short-circuiting to minimize the work done.
- For && chains: Place the condition that is cheapest to check or most likely to be false first. This exits the evaluation as quickly as possible.
- For || chains: Place the condition that is cheapest to check or most likely to be true first.
Example
Imagine is_admin() is a fast memory lookup, but has_filesystem_permission() is a slow system call.
C
// Assume the user is rarely an admin.
// GOOD: The expensive check is guarded by the cheap, likely-to-fail check.
if (is_admin(user) && has_filesystem_permission(user, "/etc")) {
// ...
}
// BAD: The expensive check is always performed, even for non-admin users.
if (has_filesystem_permission(user, "/etc") && is_admin(user)) {
// ...
}