Question
#include <stdio.h>
int main(void) {
unsigned long long int num = 285212672;
int normalInt = 5;
printf(
"My number is %d bytes wide and its value is %ul. A normal number is %d.\n",
sizeof(num),
num,
normalInt
);
return 0;
}
This program prints unexpected output:
My number is 8 bytes wide and its value is 285212672l. A normal number is 0.
I suspect the problem is the printf() format used for the unsigned long long int. What is the correct way to print an unsigned long long int with printf(), and why does the current format string produce incorrect output?
Short Answer
By the end of this page, you will understand how printf() format specifiers work in C, why the type and format must match exactly, and how to correctly print an unsigned long long int using %llu. You will also learn why sizeof() should usually be printed with %zu, and how format mismatches can cause strange output.
Concept
In C, printf() is a formatted output function. It does not know the types of the arguments by itself. Instead, it relies entirely on the format string to know how to interpret each value.
That means this is crucial:
%dexpects anint%uexpects anunsigned int%ldexpects along%lluexpects anunsigned long long
If the format specifier does not match the actual argument type, the behavior is undefined. In practice, this often leads to:
- wrong numbers being printed
- extra characters appearing
- later arguments being read incorrectly
- platform-dependent bugs
In your example, %ul is not the correct specifier for unsigned long long. In printf(), the length modifier must come before the conversion letter. So:
%lu=unsigned long%llu=unsigned long long
Writing %ul makes printf() read %u first and treat the l as a normal character in the string. That is why the output includes a trailing l.
It also causes the next argument to be interpreted incorrectly, which is why normalInt prints as 0 instead of 5.
The correct code is:
printf("My number is %zu bytes wide and its value is %llu. A normal number is %d.\n",
sizeof(num), num, normalInt);
Why %zu for sizeof(num)?
sizeofreturns a value of typesize_t- the correct
printf()specifier forsize_tis%zu
This makes the code portable and correct.
Mental Model
Think of printf() like a person reading labels on boxes coming down a conveyor belt.
Each format specifier is a label telling printf() what kind of box to open:
%d= "this box contains anint"%llu= "this box contains anunsigned long long"%zu= "this box contains asize_t"
If you put the wrong label on a box, printf() opens it the wrong way and gets confused. Once that happens, the rest of the boxes may also be read incorrectly.
So the rule is simple:
- the format string must describe the arguments exactly
- if the label is wrong, the output can become nonsense
Syntax and Examples
The correct syntax for printing an unsigned long long is:
printf("%llu", value);
Example:
#include <stdio.h>
int main(void) {
unsigned long long int num = 285212672;
printf("Value: %llu\n", num);
return 0;
}
Output:
Value: 285212672
If you also want to print the size of the variable:
#include <stdio.h>
int main(void) {
unsigned long long int num = 285212672;
printf("Size: %zu bytes, Value: %llu\n", sizeof(num), num);
return 0;
}
Output might be:
Size: 8 bytes, Value: 285212672
Why %ul is wrong
printf("%ul", num);
This is interpreted as:
%u→ print anunsigned intl→ print the characterl
So it does not mean unsigned long. The correct one is %lu.
Common integer format specifiers
int a = -10;
unsigned int b = 10;
long c = 1000;
unsigned long d = 1000;
long long e = 5000000000LL;
unsigned long long f = 5000000000ULL;
printf("%d\n", a);
printf("%u\n", b);
printf("%ld\n", c);
printf("%lu\n", d);
printf("%lld\n", e);
printf("%llu\n", f);
Step by Step Execution
Consider this corrected example:
#include <stdio.h>
int main(void) {
unsigned long long num = 285212672;
int normalInt = 5;
printf("My number is %zu bytes wide and its value is %llu. A normal number is %d.\n",
sizeof(num), num, normalInt);
return 0;
}
Step by step
numis created as anunsigned long long.normalIntis created as anint.sizeof(num)is evaluated.- Its type is
size_t. - On many systems its value is
8.
- Its type is
printf()reads the format string from left to right.%zutellsprintf():- "read the next argument as
size_t" - it prints
8
- "read the next argument as
%llutellsprintf():- "read the next argument as
unsigned long long" - it prints
285212672
- "read the next argument as
%dtellsprintf():- "read the next argument as
int" - it prints
5
- "read the next argument as
- The final output is correct.
What went wrong in the original code
Original format:
"My number is %d bytes wide and its value is %ul. A normal number is %d.\n"
printf() reads %ul as:
%ufor anunsigned int- then literal
l
But the argument is actually unsigned long long, not unsigned int.
That mismatch means printf() consumes the wrong amount or wrong interpretation of data. After that, the remaining arguments may no longer line up properly, so the final %d reads garbage or the wrong bytes.
Real World Use Cases
Using the correct printf() format specifier matters anywhere C code prints values for debugging, logging, reporting, or user output.
Common real uses
- System programming: printing file sizes, memory sizes, counters, or IDs
- Embedded programming: printing hardware counters and timestamps
- Networking: logging packet counts or large sequence numbers
- Data processing tools: printing large unsigned totals
- Debugging: verifying variable values during development
Example: logging a large counter
unsigned long long totalRequests = 123456789ULL;
printf("Total requests: %llu\n", totalRequests);
Example: printing object size safely
char buffer[256];
printf("Buffer size: %zu\n", sizeof(buffer));
If the format is wrong, logs become unreliable, and debugging gets harder.
Real Codebase Usage
In real projects, developers usually follow a few practical patterns when using printf().
1. Match every type exactly
This is the most important habit.
size_t len = 42;
unsigned long long count = 1000;
printf("len=%zu count=%llu\n", len, count);
2. Let compiler warnings help you
Compiling with warnings catches many format mistakes.
gcc -Wall -Wextra -Wformat program.c
These warnings are extremely useful in production code.
3. Use fixed-width integer macros when needed
When code uses types like uint64_t, many codebases prefer <inttypes.h> macros for portability.
#include <inttypes.h>
#include <stdint.h>
uint64_t id = 123;
printf("id=%" PRIu64 "\n", id);
This is common in portable libraries and cross-platform systems code.
4. Use logging wrappers carefully
Large codebases often wrap printf() in macros or logging functions. The same format rules still apply.
printf("user_id=%llu status=%d\n", userId, status);
5. Prefer explicit suffixes for large constants
unsigned long long maxCount = 5000000000ULL;
The ULL suffix makes the constant type clearer and avoids accidental type issues.
Common Mistakes
1. Using the wrong specifier order
Broken:
printf("%ul\n", num);
Correct:
printf("%lu\n", someUnsignedLong);
printf("%llu\n", num);
The length modifier comes before the final conversion letter.
2. Printing sizeof(...) with %d
Broken:
printf("%d\n", sizeof(num));
Better:
printf("%zu\n", sizeof(num));
sizeof returns size_t, not int.
3. Assuming wrong format strings are "close enough"
Broken:
unsigned long long n = 10;
printf("%u\n", n);
This is undefined behavior.
Correct:
printf("%llu\n", n);
4. Forgetting that undefined behavior can affect later arguments
Broken:
unsigned long long n = 10;
int x = 5;
printf("%u %d\n", n, x);
You might think only the first value is wrong, but later values can also become wrong.
5. Ignoring compiler warnings
If your compiler says the format and argument type do not match, fix it. Do not ignore it.
6. Confusing long and long long
These are different types.
%lu→unsigned long%llu→unsigned long long
Do not assume they are interchangeable.
Comparisons
| Type | Correct printf() specifier | Notes |
|---|---|---|
int | %d | Signed integer |
unsigned int | %u | Unsigned integer |
long | %ld | Signed long |
unsigned long | %lu | Unsigned long |
long long | %lld | Signed long long |
unsigned long long | %llu | Unsigned long long |
size_t | %zu | Commonly used with sizeof |
%lu vs %llu
| Specifier | Expected type |
|---|---|
%lu | unsigned long |
%llu | unsigned long long |
unsigned long long vs uint64_t
| Type | When used | Printing approach |
|---|---|---|
unsigned long long | General C integer type | %llu |
uint64_t | Exact-width integer from <stdint.h> | Often PRIu64 from <inttypes.h> |
If you specifically use fixed-width types, the <inttypes.h> approach is often more portable.
Cheat Sheet
// unsigned long long
unsigned long long x = 123ULL;
printf("%llu\n", x);
// long long
long long y = -123LL;
printf("%lld\n", y);
// unsigned long
unsigned long a = 10UL;
printf("%lu\n", a);
// size_t (for sizeof)
printf("%zu\n", sizeof(x));
Rules
printf()does not detect types automatically.- The format specifier must match the argument type exactly.
- For
unsigned long long, use%llu. - For
sizeof(...), use%zu. %ulis wrong;lis treated as a literal character.
Quick fixes for the original code
printf("My number is %zu bytes wide and its value is %llu. A normal number is %d.\n",
sizeof(num), num, normalInt);
Helpful compiler flags
gcc -Wall -Wextra -Wformat file.c
Portable fixed-width printing
#include <stdint.h>
#include <inttypes.h>
uint64_t n = 42;
printf("%" PRIu64 "\n", n);
FAQ
What is the correct printf specifier for unsigned long long in C?
Use %llu.
Why does %ul print an extra l?
Because printf() reads %u as the conversion specifier and then treats l as a normal character.
Why did the next %d print the wrong value?
Because the previous format mismatch caused printf() to interpret the arguments incorrectly. This is undefined behavior.
Should I use %d for sizeof?
No. sizeof returns size_t, so %zu is the correct specifier.
What is the difference between %lu and %llu?
%lu is for unsigned long. %llu is for unsigned long long.
Can I print uint64_t with %llu?
Not always safely on every platform. The portable approach is to use <inttypes.h> and PRIu64.
How can I catch printf mistakes early?
Compile with warnings such as -Wall -Wextra -Wformat.
Is this a runtime bug or a syntax error?
The code compiles, but the output is wrong because of undefined behavior caused by a format mismatch.
Mini Project
Description
Build a small C program that prints several integer types correctly. This project reinforces how printf() format specifiers must match the actual variable types, including int, unsigned int, unsigned long long, and size_t from sizeof.
Goal
Create a program that prints multiple numeric types using the correct printf() specifiers without warnings.
Requirements
- Declare an
int, anunsigned int, and anunsigned long long - Print each variable with the correct format specifier
- Print the size of at least one variable using
sizeof - Make sure the output is readable and labeled
- Use
main(void)and return0
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.