Question
I have trouble remembering how to print an unsigned long value in C using printf.
Suppose unsigned_foo is declared as an unsigned long. I tried several format strings:
printf("%lu\n", unsigned_foo);
printf("%du\n", unsigned_foo);
printf("%ud\n", unsigned_foo);
printf("%ll\n", unsigned_foo);
printf("%ld\n", unsigned_foo);
printf("%dl\n", unsigned_foo);
Instead of printing the expected unsigned value, some of these print a negative number. What is the correct way to print an unsigned long in C, and why do the other format specifiers behave incorrectly?
Short Answer
By the end of this page, you will know the correct printf format specifier for unsigned long in C, understand why mismatched format specifiers can print incorrect values, and learn how to choose the right specifier for related integer types.
Concept
In C, printf does not automatically know the type of each value you pass. It relies entirely on the format string to interpret the bytes correctly.
For an unsigned long, the correct format specifier is:
%lu
%starts a format specifierlmeanslongumeansunsigned decimal integer
So this is correct:
unsigned long unsigned_foo = 123456UL;
printf("%lu\n", unsigned_foo);
If you use the wrong specifier, printf interprets the argument as the wrong type. That causes undefined behavior, which often shows up as strange output such as negative numbers or garbage values.
For example:
%ldmeanslong int(signed long)%umeansunsigned int%dmeansint%llumeansunsigned long long
Even if some of these types look similar, they are not interchangeable in printf.
This matters in real programming because formatted output is used everywhere:
- debugging values
- logging
- printing counters, file sizes, IDs, and timestamps
- displaying results in command-line tools
A small mismatch in a format string can make debugging harder or lead to incorrect output.
Mental Model
Think of printf like a machine reading labeled boxes on a conveyor belt.
Each value you pass is a box containing bytes. The format string tells printf which label to use when opening each box.
If the box contains an unsigned long, the label must match:
unsigned→ useulong→ usel
So the correct label is %lu.
If you attach the wrong label, printf opens the box incorrectly. It may treat an unsigned value as signed, or expect a different size. That is why wrong format specifiers can print nonsense or negative values.
Syntax and Examples
Core syntax
printf("%lu\n", value);
Use %lu when value has type unsigned long.
Basic example
#include <stdio.h>
int main(void) {
unsigned long count = 4000000000UL;
printf("count = %lu\n", count);
return 0;
}
Output might be:
count = 4000000000
Why %ld is different
#include <stdio.h>
int main(void) {
unsigned long value = 4000000000UL;
printf("Correct: %lu\n", value);
printf("Wrong: %ld\n", value);
return 0;
}
%lu prints the value as an unsigned long.
%ld tells printf to interpret the same bytes as a signed long. That mismatch causes undefined behavior and may print a negative number.
Printing with text around it
unsigned long size = 1024UL;
printf("File size: %lu bytes\n", size);
Related integer examples
unsigned int a = 10;
unsigned long b = 20UL;
unsigned long long c = 30ULL;
printf("%u\n", a); // unsigned int
printf("%lu\n", b); // unsigned long
printf("%llu\n", c); // unsigned long long
Step by Step Execution
Consider this program:
#include <stdio.h>
int main(void) {
unsigned long value = 42UL;
printf("Value: %lu\n", value);
return 0;
}
Step by step
-
unsigned long value = 42UL;- A variable named
valueis created. - Its type is
unsigned long. - The suffix
ULmakes the literal anunsigned long.
- A variable named
-
printf("Value: %lu\n", value);printfreads the format string.- It sees ordinary text:
Value: - Then it sees
%lu %lumeans: read the next argument as anunsigned longand print it in decimal form.- The next argument is
value, which is indeed anunsigned long. printfprints42.\nprints a newline.
-
return 0;- The program ends successfully.
What if you use %ld instead?
printf("Value: %ld\n", value);
Now printf is told to read the argument as a signed long. But the argument is actually unsigned long. The types do not match, so the behavior is undefined.
You might see:
- the same number
- a negative number
- a completely incorrect number
You cannot rely on any of these results.
Real World Use Cases
Logging counters and totals
unsigned long request_count = 150000UL;
printf("Requests handled: %lu\n", request_count);
Printing file sizes or byte counts
unsigned long bytes = 2048UL;
printf("Downloaded: %lu bytes\n", bytes);
Reporting loop progress
unsigned long i;
for (i = 0; i < 3; i++) {
printf("Step %lu\n", i);
}
Debugging values from system or library code
Some APIs return values stored in larger integer types. When printing them for debugging, the format must match the actual type exactly.
Embedded and systems programming
In low-level C programs, exact integer sizes and signedness matter. Printing with the right specifier helps verify values correctly during testing and troubleshooting.
Real Codebase Usage
In real projects, developers usually do more than just write printf("%lu", x).
1. Print values with clear labels
printf("timeout_ms=%lu\n", timeout_ms);
This makes logs easier to read.
2. Use the exact matching specifier
Developers check the actual variable type before printing:
unsigned long total;
printf("%lu\n", total);
Not:
printf("%u\n", total); // wrong if total is unsigned long
3. Use warnings to catch mistakes
Compilers can detect many printf mismatches.
gcc -Wall -Wextra -Wformat program.c
These warnings are extremely helpful in real codebases.
4. Use <inttypes.h> for fixed-width integer types
If a codebase uses types like uint32_t or uint64_t, developers often prefer macros such as PRIu64 instead of guessing the right format string.
#include <inttypes.h>
#include <stdio.h>
uint64_t id = 123;
printf("%
Common Mistakes
1. Using the right letters in the wrong order
This is wrong:
printf("%ul\n", value);
The correct order is length modifier first, then conversion specifier:
printf("%lu\n", value);
2. Using %d or %u for an unsigned long
Broken example:
unsigned long value = 5000000000UL;
printf("%u\n", value); // wrong
%u is for unsigned int, not unsigned long.
Use:
printf("%lu\n", value);
3. Using %ld because long looks close enough
Broken example:
unsigned long value = 4000000000UL;
printf("%ld\n", value); // wrong
%ld is signed long, not unsigned long.
4. Forgetting the semicolon
One of the original examples is missing semicolons:
printf("%lu\n", unsigned_foo)
It should be:
printf("%lu\n", unsigned_foo);
5. Assuming wrong output is always just a signedness issue
A format mismatch is not just a small display problem. In C, it is undefined behavior. That means the language does not guarantee what happens.
6. Ignoring compiler warnings
If your compiler says the format expects one type and you passed another, fix it immediately. Those warnings often point directly to the bug.
Comparisons
| Type | Correct printf specifier | Meaning |
|---|---|---|
int | %d | signed decimal integer |
unsigned int | %u | unsigned decimal integer |
long | %ld | signed long decimal integer |
unsigned long | %lu | unsigned long decimal integer |
long long | %lld | signed long long decimal integer |
unsigned long long | %llu | unsigned long long decimal integer |
%lu vs %u
| Specifier | Expected type | Use when |
|---|---|---|
%u | unsigned int | the variable is exactly unsigned int |
%lu | unsigned long | the variable is exactly unsigned long |
%lu vs %ld
| Specifier | Signed or unsigned | Expected type |
|---|---|---|
%ld | signed | long |
%lu | unsigned | unsigned long |
Even if both use long, the signedness still matters.
Plain integer types vs fixed-width integer types
If you use fixed-width types such as uint64_t, prefer <inttypes.h> macros when portability matters:
#include <inttypes.h>
#include <stdio.h>
uint64_t n = 100;
printf("%
Cheat Sheet
// unsigned long
printf("%lu\n", value);
Quick rules
unsigned long→%lulong→%ldunsigned int→%uint→%dunsigned long long→%llulong long→%lld
Pattern
% + length modifier + type
Examples:
l= longll= long longd= signed decimalu= unsigned decimal
Common valid examples
unsigned long a = 10UL;
long b = -10L;
unsigned long long c = 99ULL;
printf("%lu\n", a);
printf("%ld\n", b);
printf("%llu\n", c);
Common invalid examples
printf("%ul\n", a); // wrong order
printf("%u\n", a); // wrong type
printf("%ld\n", a); // signed vs unsigned mismatch
Best practices
- Match the format string to the exact type.
- Compile with
-Wall -Wextra -Wformat. - For
uint32_t,uint64_t, and similar types, consider<inttypes.h>macros.
Important edge case
A wrong printf specifier is not just "slightly wrong". It causes undefined behavior.
FAQ
What is the correct printf specifier for unsigned long in C?
Use:
%lu
Why does %ld sometimes print a negative number?
Because %ld tells printf to treat the value as a signed long. If the actual value is unsigned long, the types do not match.
Is %ul correct for unsigned long?
No. The correct order is %lu, not %ul.
Can I use %u for unsigned long if the value is small?
No. The format specifier must match the type, not just the value.
Why do some wrong format specifiers seem to work?
Sometimes the memory layout makes the output look correct by accident. But it is still undefined behavior and cannot be trusted.
How can I catch printf format mistakes?
Compile with warnings enabled:
gcc -Wall -Wextra -Wformat file.c
What should I use for uint64_t?
Prefer <inttypes.h> macros such as PRIu64 for portable code.
Do I need UL on the numeric literal?
Not always, but it helps make the literal type explicit when assigning to an unsigned long.
unsigned long x = 100UL;
Mini Project
Description
Build a small C program that prints several integer variables using printf. The purpose is to practice matching each variable type with the correct format specifier and to see how readable diagnostic output is written in real programs.
Goal
Create a program that correctly prints int, unsigned int, long, unsigned long, and unsigned long long values using the proper printf specifiers.
Requirements
- Declare at least five integer variables of different C integer types.
- Print each variable on its own line with a descriptive label.
- Use the correct
printfformat specifier for every variable. - Include at least one
unsigned longexample. - Compile cleanly with format warnings enabled.
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.