Question

What Does `??!??!` Mean in C? Trigraphs and How the Code Really Works

coperatorstrigraphs

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:

TrigraphReplaced with
??=#
??([
??)]
??!`
??<{
??>}
??-~
??/\\
??'^

So this code:

ErrorHasOccured() ??!??! HandleError();

is first transformed into:

ErrorHasOccured() || HandleError();

That means the code is using the logical OR operator.

In C, a || b works like this:

  • evaluate a
  • if a is true (non-zero), stop and return true
  • otherwise, evaluate b

This is called short-circuit evaluation.

So the expression:

ErrorHasOccured() || HandleError();

means:

  • call ErrorHasOccured()
  • if it returns non-zero, HandleError() is not called
  • if it returns 0, then HandleError() is called

That is important, because the original guess about the code may be backward depending on what ErrorHasOccured() returns.

For example:

  • if ErrorHasOccured() returns 1 when there is an error, then HandleError() will not run
  • if ErrorHasOccured() returns 0 when there is an error, then HandleError() will run

So the behavior depends entirely on the function's return convention.

This matters in real programming because C often allows compact expressions that rely on boolean-like integer results and short-circuit logic. However, code like this can be hard to read, especially when mixed with obscure features like trigraphs.

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 A is true, no need to check B
  • if A is false, then check B

That is why functions on the right side of || may or may not run.

Take Quiz

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 == 0 is 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(void) {
    return 0;
}

int handle_error(void) {
    printf("Handling error\n");
    return 1;
}

int main(void) {
    has_error() || handle_error();
    return 0;
}

What happens here?

  • has_error() returns 0, which is false in C
  • because the left side is false, C evaluates the right side
  • handle_error() runs and prints the message

A much clearer way to write it

if (!has_error()) {
    handle_error();
}

or, depending on meaning:

if (has_error()) {
    handle_error();
}

This is easier to understand than relying on a compact expression.

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 0, which means false.

Step 3: Because left side is false, evaluate the right side

Now C must check the right side, so second() runs.

Output:

second() called

It returns 1, which means true.

Step 4: Final result

The expression 0 || 1 is true.

Now change first() so it returns 1:

int first(void) {
    printf("first() called\n");
    return 1;
}

Then only first() runs. second() does not run, because || short-circuits once the result is already known.

That short-circuit behavior is the real reason this pattern can trigger a function call conditionally.

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.

Take Quiz

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:

  • old code
  • intentionally obscure code
  • code written for portability to old systems
  • a joke that still happens to compile

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:

  • 0 means false
  • non-zero means true

Broken mental model:

if (5 == true) {
    /* not how C boolean logic works in older styles */
}

Correct idea: any non-zero value is true.

Mistake 4: Ignoring short-circuit behavior

int a(void) { return 1; }
int b(void) { printf("runs\n"); return 1; }

a() || b();

b() never runs, because a() already made the expression true.

Mistake 5: Writing clever code that hides intent

This works:

check() || recover();

But for many readers, this is clearer:

if (!check()) {
    recover();
}

Prefer readability unless the compact form is genuinely clearer.

Mistake 6: Assuming all compilers treat trigraphs the same way by default

Modern compiler behavior may depend on standard version and compiler options. Even when supported, trigraphs are considered obsolete and should not be relied on in new code.

Comparisons

ConceptMeaningReadabilityCommon today?Notes
``Logical ORHigh
??!??!Trigraph form of ``Very low
``Bitwise ORMediumYes
&&Logical ANDHighYesShort-circuits when left side is false

|| vs |

a || b
  • logical OR
  • result is true or false
  • short-circuits
a | b
  • bitwise OR
  • combines bits
  • evaluates both sides

Example:

int x = 0;
int y = 4;

printf("%d\n", x || y);  /* 1 */
printf("%d\n", x | y);   /* 4 */

Compact expression vs if

StyleExampleBest when
Compact`check()
Explicitif (!check()) { fix(); }You want maximum clarity

In most beginner and team code, the explicit if version is easier to maintain.

Take Quiz

Cheat Sheet

Key fact

??!??! in C is not its own operator.

It is two trigraphs:

??!  ->  |
??!  ->  |

So:

??!??!  ->  ||

What || does

a || b
  • evaluates a first
  • if a is true, b is not evaluated
  • if a is false, b is evaluated

Truth rules in C

  • 0 = false
  • non-zero = true

Example

ErrorHasOccured() || HandleError();

Equivalent meaning:

if (!ErrorHasOccured()) {
    HandleError();
}

But only if ErrorHasOccured() returns non-zero when successful and 0 on failure. Always check the function's contract.

Common trigraphs

TrigraphResult
??=#
??([
??)]
??!`
??<{
??>}
??-~
??/\\
??'^

Best practice

  • Do not use trigraphs in new code
  • Prefer normal operators like ||
  • Prefer clear if statements when intent matters

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.

Take Quiz

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

  1. Write one function that returns 0 and prints a message.
  2. Write another function that returns 1 and prints a message.
  3. Use the functions in a || expression inside main.
  4. Add a second test where the left-hand function returns 1 so the right-hand side does not run.
  5. Rewrite one of the tests using an explicit if statement.
Take Quiz

Keep learning

Related questions

View more