Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

A Comprehensive Guide to Format Specifiers in C

Tech May 16 1

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 %f or %e (trailing zeros removed)
  • %G: shorter of %f or %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.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.