Question
Why does the following C program print C++Sucks on the system where it was tested? What concepts are used in this expression?
#include <stdio.h>
double m[] = {7709179928849219.0, 771};
int main(void) {
m[1]-- ? m[0] *= 2, main() : printf((char *)m);
}
In particular, how do the conditional operator, comma operator, recursive call, and cast from double * to char * affect the result?
Short Answer
This program combines several legal-but-obscure C features: a conditional expression, the comma operator, recursion, floating-point arithmetic, and reading an array's raw bytes as characters. It prints the shown text only because its numeric values and memory layout were carefully chosen for a particular platform. You will learn how to parse such expressions and why this code should not be used in production.
Concept
The central lesson is operator parsing and evaluation in C, combined with a deliberately nonportable representation trick.
This expression:
m[1]-- ? m[0] *= 2, main() : printf((char *)m);
uses the conditional operator:
condition ? when_true : when_false
The condition is m[1]--. The postfix -- uses the old value first, then subtracts one.
A subtle C grammar rule says that the middle expression of ?: is parsed as though it were parenthesized. Therefore, the expression behaves like this:
m[1]-- ? (m[0] *= 2, main()) : printf((char *)m);
It does not mean this:
// Not the effective grouping
(m[1]-- ? m[0] *= 2 : printf((char *)m)), main();
When m[1] is nonzero, the program doubles m[0] and recursively calls main. When m[1] eventually reaches zero, it instead calls printf.
The unusual numeric constants are selected so that, after repeatedly multiplying the first double by 2, its bytes happen to spell C++Sucks on the tested machine. Casting m to char * makes printf interpret those bytes as a C string.
This output is not portable. It relies on details such as the floating-point format, byte order, how the compiler converts the decimal literal to double, and the resulting bytes containing a null terminator.
Mental Model
Think of the conditional operator as a fork in a path:
- If the countdown has not reached zero, take the repeat path.
- If it has reached zero, take the print path.
The comma operator is like completing two chores in order on the repeat path:
- Double
m[0]. - Call
main()again.
The double array is normally a pair of number containers. The cast to char * tells printf: “Do not treat this memory as numbers; inspect the same storage one byte at a time as text.”
That last step is like looking at the labels hidden in the raw packaging of a product rather than using the product normally. It can reveal a meaningful message only because someone intentionally arranged the bytes that way.
Syntax and Examples
The conditional operator chooses one of two expressions:
result = condition ? value_if_true : value_if_false;
The comma operator evaluates its left expression, discards that expression's value, then evaluates and produces the value of its right expression:
first_action(), second_action();
Example:
#include <stdio.h>
int main(void) {
int count = 2;
count-- ? (puts("Count was nonzero"), puts("Repeating"))
: puts("Count was zero");
return 0;
}
count-- evaluates to 2, then changes count to 1. Since 2 is true in C, the true branch runs. The comma operator ensures that both puts calls run from left to right.
A clearer equivalent is usually better:
if (count-- != 0) {
puts("Count was nonzero");
puts("Repeating");
} else {
puts("Count was zero");
}
In C, zero is false and every nonzero numeric value is true.
Step by Step Execution
Consider a smaller, readable version of the control flow:
#include <stdio.h>
int count = 2;
void repeat(void) {
count-- ? (puts("work"), repeat()) : puts("done");
}
int main(void) {
repeat();
return 0;
}
Execution trace:
countstarts as2.count--evaluates to2, thencountbecomes1.2is nonzero, so the true branch runs: it printsworkand callsrepeat()again.count--now evaluates to1, thencountbecomes0.1is nonzero, so it again printsworkand recurses.count--now evaluates to0, thencountbecomes-1.0is false, so the false branch runs and printsdone.
In the original program, the same process occurs 771 times. Each true branch performs:
m[0] *= 2;
main();
At the final call, the false branch executes:
printf((char *)m);
On the intended platform, the bytes beginning at m then represent C++Sucks followed by a zero byte, so printf treats them as its format string.
Real World Use Cases
The exact byte trick is not a real-world pattern, but the individual concepts are common.
-
Conditional operator: Choose a small value or expression, such as a default label.
const char *status = is_ready ? "ready" : "waiting"; -
Postfix decrement: Process a fixed number of retries or items.
while (retries-- > 0) { attempt_request(); } -
Recursion: Traverse tree-shaped data, such as directories, expression trees, or nested configuration data.
-
Byte-level access through
unsigned char *orchar *: Inspect or copy an object's representation when implementing low-level utilities, serializers, debuggers, or protocol code. This requires careful attention to portability and formats. -
Null-terminated strings: C library functions such as
printf,puts, andstrlenexpect strings to end with\0.
Real Codebase Usage
Production C code generally writes this logic explicitly rather than compressing it into one expression.
Prefer a loop for a countdown
for (int remaining = 771; remaining > 0; --remaining) {
value *= 2;
}
This avoids recursion depth and makes the stopping rule obvious.
Prefer explicit branches for side effects
if (count > 0) {
--count;
do_work();
} else {
report_done();
}
Conditional expressions are best when selecting a value. An if statement is clearer when each branch performs actions.
Treat raw bytes as data, not format strings
If bytes need to be displayed, use an explicit format:
printf("%s\n", text);
For arbitrary binary data, print hexadecimal bytes rather than passing it to printf as a format string:
for (size_t i = 0; i < length; ++i) {
printf("%02X ", bytes[i]);
}
printf("\n");
In real codebases, validation, clear control flow, bounded loops, and explicit formats are safer than relying on object representations.
Common Mistakes
Misreading the comma operator
Beginners may think the comma only separates parts of the conditional expression. Here it is an operator in the true branch.
// The true branch contains both expressions.
condition ? (do_first(), do_second()) : do_other();
Use parentheses when the grouping is not immediately obvious.
Forgetting that postfix -- uses the old value
int n = 1;
if (n--) {
/* Runs: the tested value was 1. n is now 0. */
}
If you need to decrement before testing, use --n, but make the intended condition explicit when possible.
Assuming ?: evaluates both branches
Only one branch of the conditional operator is evaluated.
int result = 1 ? 10 : dangerous_function();
dangerous_function() is not called here.
Treating every double as text
This is not a valid way to convert a number to a string:
// Broken idea: these bytes are not normally readable text.
double price = 19.99;
printf("%s\n", (char *)&price);
Use a conversion specification instead:
printf("%.2f\n", price);
Passing untrusted data as printf's format string
// Dangerous if user_text contains format specifiers such as %s or %n.
printf(user_text);
Use:
printf("%s", user_text);
Relying on machine representation
The original output depends on implementation details. A different CPU byte order or floating-point representation can produce different text, no text, or behavior that does not meet the string requirement. Do not store messages inside floating-point bit patterns.
Comparisons
| Feature | What it does | Best use | Readability concern |
|---|---|---|---|
condition ? a : b | Evaluates either a or b | Selecting one small value or expression | Nested or side-effect-heavy expressions become hard to read |
if/else | Executes one statement block | Branches with several actions | Usually clearer than ?: for side effects |
a, b comma operator | Evaluates a, then b; result is b | Rarely needed in ordinary application code | Easily confused with commas that separate arguments or declarations |
| Function recursion | A function calls itself | Naturally nested structures | Can consume stack space for large counts |
| Loop | Repeats statements iteratively | Counters and repeated work | Usually preferred over recursion for simple countdowns |
char * view of memory | Reads bytes as characters | Low-level byte inspection | Does not turn arbitrary bytes into a valid C string |
The original program uses recursion where a loop would be clearer, and it uses raw bytes as a string where an actual character array would be safer.
Cheat Sheet
// Conditional operator
condition ? when_true : when_false;
// Comma operator: left first, right second
(first_action(), second_action());
// Postfix decrement: use old value, then decrement
x--;
// Clear equivalent of a side-effect-heavy conditional
if (condition) {
first_action();
second_action();
} else {
fallback_action();
}
- In C,
0is false; nonzero values are true. x--tests or yields the old value, then decreasesx.- Only the selected
?:branch is evaluated. - The middle operand of
?:can contain a comma expression:c ? a, b : dacts likec ? (a, b) : d. printf(format)treatsformatas a format string. For text data, preferprintf("%s", text).- A
char *can examine an object's bytes, but object bytes are not automatically a portable or null-terminated string. - Prefer loops over recursion for a simple numeric countdown.
FAQ
Why does m[1]-- eventually select the false branch?
m[1]-- evaluates to its current value before decrementing it. Values 771 down to 1 are true; when its current value is 0, the conditional selects the false branch.
Why does m[0] *= 2, main() call main after doubling?
The comma operator evaluates its left operand first (m[0] *= 2), then evaluates its right operand (main()).
Does the comma operator have low precedence in C?
Yes. However, the middle operand of the conditional operator has a special parsing rule, so the comma in this example belongs to the true branch.
Is calling main() recursively valid C?
Calling main from within the program is not a good or portable program design. Even where it works, repeatedly calling it can exhaust the call stack. Put recursive logic in another function instead.
Is casting double * to char * always invalid?
No. C permits inspecting an object's representation through character types. But the observed bytes are implementation-dependent, and they may not form a valid null-terminated text string.
Why is printf((char *)m) risky?
Its first argument is a format string, not merely text. It also assumes the bytes at m form a null-terminated string and contain no unintended % format directives.
Will this print C++Sucks on every compiler?
No. The result depends on platform-specific floating-point representation, endianness, decimal-to-double conversion, and byte layout. It is a demonstration puzzle, not portable C.
Mini Project
Description
Build a readable countdown program that demonstrates the same control-flow ideas as the puzzle without recursion, hidden byte encodings, or unsafe format-string usage. This is the style appropriate for ordinary application code.
Goal
Print each remaining attempt, then print a completion message after the countdown reaches zero.
Requirements
Create an integer countdown starting at 3. Use a loop to run while attempts remain. Decrement the counter once per iteration. Print the remaining count during each iteration. Print a final message after the loop ends.
Keep learning
Related questions
2D Array Loop Order and Cache Performance in C
Learn why swapping nested loops changes 2D array performance in C, using row-major memory layout, cache locality, and practical benchmarks.
Array-to-Pointer Conversion in C and C++ Explained
Learn what array-to-pointer conversion means in C and C++, how array decay works, and how it differs from a pointer to an array.
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.