A Comprehensive Guide to Format Specifiers in C
Format specifiers are essential tools in C programming, acting as placeholders that indicate where and how a value should be inserted into a string. They are primarily used with functions like printf, sprintf, and fprintf to control output formatting. Understanding their behavior is critical for producing correct, readable, and secure code.
Common Format Specifiers
Integer Specifiers
%d: signed decimal integer%u: unsigned decimal integer%x: unsigned hexadecimal (lowercase letters)%X: unsigned hexadecimal (uppercase letters)
#include <stdio.h>
int main() {
int value = 42;
printf("Signed: %d\n", value);
printf("Unsigned: %u\n", (unsigned)value);
printf("Hex (lower): %x\n", value);
printf("Hex (upper): %X\n", value);
return 0;
}
Character and String Specifiers
%c: single character%s: null-terminated string
#include <stdio.h>
int main() {
char letter = 'Z';
char words[] = "Hello, C!";
printf("Char: %c\n", letter);
printf("String: %s\n", words);
return 0;
}
Floating-Point Specifiers
%f: standard decimal notation%e: scientific notation (lowercase 'e')%E: scientific notation (uppercase 'E')%g: shorter of%for%e(trailing zeros removed)%G: shorter of%for%E
#include <stdio.h>
int main() {
double pi = 3.1415926535;
printf("Default: %f\n", pi);
printf("Scientific (lower): %e\n", pi);
printf("Scientific (upper): %E\n", pi);
printf("General: %g\n", pi);
return 0;
}
Pointer Specifier
%p: prints the address stored in a pointer (in hex)
#include <stdio.h>
int main() {
int var = 100;
int *ptr = &var;
printf("Address: %p\n", (void*)ptr);
return 0;
}
Controlling Width and Precision
Width specifies the minimum number of characters to output. If the value is shorter, it is padded with spaces (right-aligned by default, or left-aligned with - flag).
#include <stdio.h>
int main() {
int n = 7;
printf("Right-aligned: [%5d]\n", n);
printf("Left-aligned: [%-5d]\n", n);
return 0;
}
Precision behaves differently per type:
- For integers: minimum number of digits (padded with leading zeros).
- For floating-point: number of digits after the decimal point.
- For strings: maximum number of characters to print.
#include <stdio.h>
int main() {
double x = 2.71828;
char msg[] = "Precision demo";
printf("Float with 2 decimals: %.2f\n", x);
printf("String truncated to 4: %.4s\n", msg);
return 0;
}
Dynamic Width and Precision
Using * in the format string allows you to supply the width or precision as an additional argument. This is useful when the format is determined at runtime.
#include <stdio.h>
int main() {
int w = 8;
double val = 12.345;
printf("Dynamic width: %*f\n", w, val);
printf("Dynamic width & precision: %*.*f\n", w, 2, val);
return 0;
}
Pitfalls and Security
Type Mismatch
Using a specifier that does not match the argument type leads to undefined behavior. For example, applying %d to a double can produce garbage output or crash.
#include <stdio.h>
int main() {
double pi = 3.14;
/* WRONG: printf("%d\n", pi); */
printf("Correct: %f\n", pi);
return 0;
}
Truncation
When a string exceeds the precision limit, its silently cut off.
#include <stdio.h>
int main() {
char long_str[] = "This is a rather lengthy message";
printf("%.10s\n", long_str); /* prints only first 10 chars */
return 0;
}
Format String Vulnerabilities
Never pass user-controlled input directly as the format string. An attacker could use %s, %x, or %n to read or write memory. Always use "%s" as the format and pass the user string as an argument.
#include <stdio.h>
int main(int argc, char *argv[]) {
if (argc > 1) {
/* INSECURE: printf(argv[1]); */
printf("%s\n", argv[1]); /* secure */
}
return 0;
}
Practical Applications
Console Logging
Format specifiers enable clean, structured output for debugging or reporting.
#include <stdio.h>
int main() {
int id = 55;
double score = 87.4;
printf("ID: %d, Score: %.1f%%\n", id, score);
return 0;
}
Building Strings with sprintf
sprintf stores the formatted result in a buffer, which is useful for constructing messages for later use.
#include <stdio.h>
int main() {
char buffer[80];
int units = 3;
double price = 9.99;
sprintf(buffer, "Total: $%.2f for %d items", units * price, units);
printf("%s\n", buffer);
return 0;
}
File Output with fprintf
Formatted data can be written directly to files for persistent storage.
#include <stdio.h>
int main() {
FILE *fp = fopen("data.txt", "w");
if (!fp) return 1;
int count = 100;
double rate = 0.05;
fprintf(fp, "Count: %d, Rate: %f\n", count, rate);
fclose(fp);
return 0;
}
Format specifiers are not merely placeholders; they enforce type safety, enable precise layout control, and play a role in program security. Mastering them is a cornerstone of effective C programming.