Question
I found a line of C code that looks like this:
ErrorHasOccured() ??!??! HandleError();
It compiles correctly and appears to run as expected. It seems to be checking whether an error has occurred and, if so, handling the error, but I do not understand what ??!??! means or how the statement works.
I have never seen ??!??! in C or in other programming languages, and I have not been able to find documentation for it. What does this sequence do in C, and how should this code actually be interpreted?
Short Answer
By the end of this page, you will understand that ??!??! is not a real C operator. It works because C historically supports trigraphs, where certain three-character sequences are replaced before compilation. In this case, ??! becomes |, so ??!??! becomes ||, the logical OR operator. You will also learn why the example behaves like a normal condition check, why trigraphs are confusing, and why modern C code should avoid them.
Concept
In C, ??!??! is not a special operator with its own meaning. Instead, it is the result of a very old language feature called trigraphs.
A trigraph is a sequence of three characters that the compiler replaces with a different single character very early in translation. This feature existed to support keyboards and character sets that could not easily type some punctuation characters used by C.
Here are a few trigraphs:
| Trigraph | Replaced with |
|---|---|
??= | # |
??( | [ |
??) | ] |
??! | ` |
??< | { |
Mental Model
Think of trigraphs as a secret keyboard translation step that happens before the compiler really reads your code.
Imagine you write a note using substitute symbols because your keyboard is missing some keys:
- every time you write
??!, a helper rewrites it to|
So:
??!??!
becomes:
||
Then the compiler reads the code normally.
For the || part, think of it like this:
- "Try the first thing."
- "If that is enough to decide the result, stop."
- "Otherwise, try the second thing."
So:
A || B
means:
- if
Ais true, no need to checkB - if
Ais false, then checkB
That is why functions on the right side of || may or may not run.
Syntax and Examples
The core idea here is really about trigraph replacement and logical OR.
Trigraph example
??! /* becomes | */
So this:
a ??!??! b
becomes:
a || b
Normal logical OR syntax
if (x == 0 || y == 0) {
printf("At least one value is zero\n");
}
This means:
- if
x == 0is true, the whole condition is true - otherwise, C checks
y == 0
Equivalent version of the strange code
ErrorHasOccured() || HandleError();
A clearer beginner-friendly example
#include <stdio.h>
int has_error {
;
}
{
();
;
}
{
has_error() || handle_error();
;
}
Step by Step Execution
Consider this code:
#include <stdio.h>
int first(void) {
printf("first() called\n");
return 0;
}
int second(void) {
printf("second() called\n");
return 1;
}
int main(void) {
first() ??!??! second();
return 0;
}
Step 1: Trigraph replacement
Before normal compilation, C replaces trigraphs:
first() ??!??! second();
becomes:
first() || second();
Step 2: Evaluate the left side
first() runs first.
Output:
first() called
It returns , which means false.
Real World Use Cases
Although trigraphs themselves are rarely used today, the logical OR with short-circuit evaluation is very common.
1. Fallback logic
config_loaded() || load_default_config();
If the main configuration fails, load a default one.
2. Error reporting
is_valid(input) || log_error("Invalid input");
If validation fails, log an error.
3. Guard-style checks
ptr != NULL || abort();
If ptr is null, terminate immediately.
4. Parsing and validation
read_header(file) || handle_bad_file();
If the header cannot be read successfully, handle the failure.
5. Command-line tools and scripts written in C
Short-circuit expressions are sometimes used to keep code compact when a fallback action should run only if the first check fails.
That said, many teams prefer a normal if statement because it is easier to read.
Real Codebase Usage
In real C codebases, developers usually rely on the logical OR operator directly, not trigraphs.
Common patterns
Guard clauses
if (ptr == NULL) {
return -1;
}
This is clearer than writing compressed expressions.
Early returns
if (!init_system()) {
return 0;
}
This avoids deep nesting.
Validation before work
if (!is_valid_user(user)) {
log_error("Invalid user");
return;
}
Short-circuit for compact code
socket_ready() || reconnect_socket();
This may appear in low-level utility code, but should be used carefully.
Why trigraphs are avoided
- they are hard to recognize
- many programmers have never seen them
- they can make debugging and searching harder
- modern keyboards no longer need this workaround
In modern codebases, if you see ??!??!, it is usually either:
Common Mistakes
Mistake 1: Thinking ??!??! is a unique operator
It is not. It becomes || through trigraph replacement.
Broken assumption:
a ??!??! b /* not a special custom operator */
Correct understanding:
a || b
Mistake 2: Misreading the logic direction
A lot of people assume this means "if error, then handle error," but that depends on the return value.
ErrorHasOccured() || HandleError();
If ErrorHasOccured() returns 1 for error, then HandleError() will not run.
Always verify what the function returns.
Mistake 3: Forgetting that C uses integers as truth values
In C:
0means false- non-zero means true
Broken mental model:
if (5 == true) {
}
Comparisons
| Concept | Meaning | Readability | Common today? | Notes |
|---|---|---|---|---|
| ` | ` | Logical OR | High | |
??!??! | Trigraph form of ` | ` | Very low | |
| ` | ` | Bitwise OR | Medium | Yes |
&& | Logical AND | High | Yes | Short-circuits when left side is false |
|| vs |
Cheat Sheet
Key fact
??!??! in C is not its own operator.
It is two trigraphs:
??! -> |
??! -> |
So:
??!??! -> ||
What || does
a || b
- evaluates
afirst - if
ais true,bis not evaluated - if
ais false,bis evaluated
Truth rules in C
0= false- non-zero = true
Example
ErrorHasOccured() || HandleError();
Equivalent meaning:
if (!ErrorHasOccured()) {
HandleError();
}
returns non-zero when successful and on failure. Always check the function's contract.
FAQ
Is ??!??! a real C operator?
No. It becomes || because of trigraph replacement.
Why did C have trigraphs at all?
They were added so C could be written on systems or keyboards that lacked some punctuation characters.
Does ErrorHasOccured() ??!??! HandleError(); always mean "if error, handle error"?
No. It only means ErrorHasOccured() || HandleError();. Whether HandleError() runs depends on the return value of ErrorHasOccured().
What is short-circuit evaluation in C?
It means || stops early if the left side is already true, and && stops early if the left side is already false.
Are trigraphs still used in modern C?
Very rarely. They are considered obsolete and are usually avoided.
What should I write instead of this?
Use || directly, or better, use an explicit if statement when readability matters.
Is | the same as || in C?
No. is bitwise OR, while is logical OR with short-circuit behavior.
Mini Project
Description
Build a small C program that demonstrates how short-circuit evaluation works with the logical OR operator. This project helps you see exactly when the second function is called and why writing clear conditional code matters more than writing clever code.
Goal
Create a program that prints which functions run in a short-circuit OR expression and then rewrite the same logic using a clear if statement.
Requirements
- Write one function that returns
0and prints a message. - Write another function that returns
1and prints a message. - Use the functions in a
||expression insidemain. - Add a second test where the left-hand function returns
1so the right-hand side does not run. - Rewrite one of the tests using an explicit
ifstatement.
Keep learning
Related questions
Building More Fault-Tolerant Embedded C++ Applications for Radiation-Prone ARM Systems
Learn practical C++ and compile-time techniques to reduce soft-error damage in embedded ARM systems exposed to radiation.
Definition vs Declaration in C and C++: What’s the Difference?
Learn the difference between declarations and definitions in C and C++ with simple examples, common mistakes, and practical usage.
Difference Between #include <...> and #include "..." in C and C++
Learn the difference between #include with angle brackets and quotes in C and C++, including search paths, examples, and common mistakes.