Question

How to Print size_t Portably with printf in C

cprintf

Question

I have a variable of type size_t, and I want to print it using printf(). Which format specifier should I use to print it portably?

On a 32-bit machine, %u appears to work. I compiled with:

g++ -g -W -Wall -Werror -ansi -pedantic

and there was no warning. However, when I compile the same code on a 64-bit machine, I get a warning.

size_t x = /* some value */;
printf("size = %u\n", x);

The compiler reports:

warning: format '%u' expects type 'unsigned int', 
    but argument 2 has type 'long unsigned int'

The warning goes away, as expected, if I change %u to %lu.

How can I write this code so that it compiles without warnings on both 32-bit and 64-bit machines?

As a possible workaround, I could cast the variable to a large enough integer type such as unsigned long and print it with %lu, but I would like to know whether there is a more portable solution.

Short Answer

By the end of this page, you will understand why size_t should not be printed with %u or %lu directly, and how to print it portably using the correct printf format specifier: %zu. You will also learn what size_t represents, why platform differences matter, and how to avoid common formatting mistakes in C.

Concept

size_t is an unsigned integer type used to represent sizes and counts of objects in memory. You commonly see it as the return type of sizeof(), and in standard library functions that work with array lengths, memory sizes, and buffer counts.

The important detail is that size_t is not guaranteed to be the same type on every platform:

  • On one system, it might be unsigned int
  • On another, it might be unsigned long
  • On another, it could be a different unsigned integer type

Because printf() is a variadic function, it does not know the actual type of each argument unless the format string tells it correctly. That means the format specifier must match the argument type exactly.

Using %u assumes the argument is an unsigned int. Using %lu assumes the argument is an unsigned long. Neither is guaranteed to match size_t everywhere.

The portable solution in standard C is:

printf("%zu\n", value);

Here:

  • %z is the length modifier for size_t
  • u means print it as an unsigned decimal integer

So %zu means: print a size_t value as an unsigned decimal number.

This matters in real programming because code often moves between:

  • 32-bit and 64-bit systems
  • different compilers
  • different operating systems
  • different C library implementations

Portable code avoids assuming what exact integer type size_t maps to.

Mental Model

Think of size_t as a container label that changes depending on the machine.

You want to print the number inside the container. But printf() needs the correct label to know how to read it.

  • %u says: "This container is an unsigned int."
  • %lu says: "This container is an unsigned long."
  • %zu says: "This container is a size_t."

If you use the wrong label, printf() may read the bytes incorrectly or the compiler may warn you.

So the rule is simple:

  • If the variable is size_t, use %zu

Match the format to the real type, not to what you guess the type probably is on your machine.

Take Quiz

Syntax and Examples

The correct portable syntax is:

#include <stdio.h>
#include <stddef.h>

size_t n = 42;
printf("%zu\n", n);

Example 1: Printing the result of sizeof

#include <stdio.h>

int main(void) {
    printf("int uses %zu bytes\n", sizeof(int));
    return 0;
}

sizeof(int) returns a size_t, so %zu is the correct format specifier.

Example 2: Printing an array length

#include <stdio.h>

int main(void) {
    int values[] = {10, 20, 30, 40};
    size_t count = sizeof(values) / sizeof(values[0]);

    printf("Array length: %zu\n", count);
    return 0;
}

This is a very common use case. Array lengths are often stored in size_t because they represent sizes or counts.

Example 3: What not to do

size_t n = 100;
printf("%u\n", n);   // Not portable
printf("%lu\n", n);  // Also not guaranteed portable

These may appear to work on some systems, but they rely on assumptions about the underlying type of size_t.

If your compiler or library is very old

Modern standard C supports %zu. If you are stuck on an older environment that does not support it, a cast may be used as a fallback, but it is less ideal:

printf("%lu\n", (unsigned long)n);

This is only a workaround when %zu is unavailable.

Step by Step Execution

Consider this example:

#include <stdio.h>

int main(void) {
    size_t count = sizeof(double) * 4;
    printf("count = %zu\n", count);
    return 0;
}

Step by step

  1. sizeof(double) is evaluated.

    • Suppose double is 8 bytes on your system.
  2. sizeof(double) * 4 becomes 8 * 4.

    • The result is 32.
  3. That result is stored in count.

    • count has type size_t.
  4. printf("count = %zu\n", count); is called.

    • %z tells printf() that the argument is a size_t
    • u tells it to print the value as an unsigned decimal integer
  5. The output is:

count = 32

Why %u is risky here

If you wrote:

printf("count = %u\n", count);

then printf() would expect an unsigned int, not a size_t. On some systems those are the same type, but on others they are not. That mismatch leads to compiler warnings and can cause incorrect behavior.

Real World Use Cases

size_t appears often in real C programs, especially where code deals with memory, lengths, or buffer sizes.

Common situations

  • Printing the result of sizeof()
  • Displaying array lengths
  • Showing buffer capacities
  • Logging memory allocation sizes
  • Reporting bytes read or written
  • Working with string or data processing loops

Example: buffer logging

#include <stdio.h>

void log_buffer_size(size_t size) {
    printf("Allocated buffer: %zu bytes\n", size);
}

Example: loop count reporting

#include <stdio.h>

int main(void) {
    char text[] = "hello";
    size_t len = sizeof(text) - 1;

    printf("Text length: %zu\n", len);
    return 0;
}

Example: memory allocation diagnostics

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    size_t bytes = 1024;
    void *ptr = malloc(bytes);

    if (ptr != NULL) {
        printf("Allocated %zu bytes successfully\n", bytes);
        free(ptr);
    }

    return 0;
}

In all of these cases, %zu keeps the code portable and type-correct.

Take Quiz

Real Codebase Usage

In real projects, developers usually follow a few simple patterns when working with size_t.

1. Use size_t for sizes and counts

If a variable stores:

  • number of elements
  • buffer length
  • byte count
  • result of sizeof

then size_t is usually the right type.

size_t capacity = 256;
size_t item_count = 10;

2. Print it with %zu

printf("capacity=%zu, item_count=%zu\n", capacity, item_count);

3. Validate sizes before allocation

if (count == 0) {
    printf("No items to allocate\n");
    return;
}

4. Use size_t consistently across APIs

void process_items(const int *items, size_t count) {
    for (size_t i = 0; i < count; i++) {
        printf("items[%zu] = %d\n", i, items[i]);
    }
}

5. Avoid unnecessary casts

A cast can hide a mismatch instead of fixing it. In well-written code, developers prefer matching the correct format specifier to the original type.

6. Combine with guard clauses and error logging

if (buffer_size > max_size) {
    fprintf(stderr, "Buffer too large: %zu\n", buffer_size);
    return;
}

This is common in parsing code, file processing, networking, and memory management.

Common Mistakes

Here are some frequent mistakes beginners make when printing size_t.

Mistake 1: Using %u

size_t n = 10;
printf("%u\n", n);  // Wrong on some systems

Why it is a problem

%u expects unsigned int, but size_t may be a different type.

Fix

printf("%zu\n", n);

Mistake 2: Using %lu because it works on one machine

size_t n = 10;
printf("%lu\n", n);  // Still not fully portable

Why it is a problem

This assumes size_t is unsigned long, which is not guaranteed everywhere.

Fix

printf("%zu\n", n);

Mistake 3: Casting instead of using the right format

size_t n = 10;
printf("%lu\n", (unsigned long)n);

Why it is a problem

This may work as a workaround, but it adds unnecessary assumptions and can hide type issues.

Better approach

Use the correct format specifier directly.

printf("%zu\n", n);

Mistake 4: Forgetting that sizeof returns size_t

printf("%u\n", sizeof(int));  // Wrong

Fix

printf("%zu\n", sizeof(int));

Mistake 5: Ignoring compiler warnings

If the compiler says the format does not match the argument type, treat that as a real issue.

Compiler warnings for printf format mismatches are valuable because variadic functions do not provide type safety by themselves.

Comparisons

Here is a quick comparison of common integer format choices when printing unsigned values in C.

FormatExpected typeGood for size_t?Notes
%uunsigned intNoOnly correct if the value is actually unsigned int
%luunsigned longNoWorks only if size_t is unsigned long on that platform
%zusize_tYesPortable standard choice

size_t vs other integer types

TypePurposeTypical signednessBest printf format
size_tSizes and countsUnsigned%zu
intGeneral integer valuesSigned%d
unsigned intGeneral unsigned integer valuesUnsigned%u
longLarger integer valuesSigned%ld
unsigned longLarger unsigned valuesUnsigned%lu

%zu vs casting

ApproachPortableClearRecommended
printf("%zu", n)YesYesYes
printf("%lu", (unsigned long)n)SometimesLessOnly as fallback

The key idea is simple: choose the format based on the variable's actual type, not on the machine you happen to be using.

Take Quiz

Cheat Sheet

// Correct way to print size_t
size_t n = 42;
printf("%zu\n", n);

Rules

  • size_t is used for sizes, lengths, and counts
  • sizeof(...) returns size_t
  • Use %zu with printf() for size_t
  • Do not assume size_t is unsigned int or unsigned long

Common examples

printf("%zu\n", sizeof(int));
printf("length = %zu\n", len);
printf("capacity = %zu bytes\n", capacity);

Avoid

printf("%u\n", n);   // not portable
printf("%lu\n", n);  // not guaranteed portable

Fallback for very old environments

printf("%lu\n", (unsigned long)n);

Use this only if %zu is unavailable.

Remember

  • %z = length modifier for size_t
  • %zu = print size_t as unsigned decimal
  • Match the format string to the real argument type

FAQ

What is the correct printf specifier for size_t in C?

Use %zu.

Why does %u sometimes work for size_t?

It works only on systems where size_t happens to be the same type as unsigned int. That is not guaranteed by the language.

Can I use %lu for size_t?

Only if size_t is actually unsigned long on that platform. For portable code, use %zu instead.

Does sizeof return int?

No. sizeof returns a value of type size_t.

Why does the compiler warn about %u with size_t?

Because printf expects an unsigned int for %u, but your argument has type size_t, which may be a different type.

Is casting size_t to unsigned long a good solution?

It can be a workaround for old environments, but it is not the best modern portable solution. Prefer %zu.

Does %zu work in C++ too?

If you are using printf in a standards-compliant environment that supports %zu, yes. In idiomatic C++, many developers prefer std::cout, but %zu is still the right printf-style specifier for size_t.

Take Quiz

Mini Project

Description

Build a small C program that reports the sizes of several data types and array lengths using size_t. This project demonstrates how size_t appears naturally in everyday C code and how to print it correctly with printf in a portable way.

Goal

Create a program that computes and prints several size-related values using %zu correctly.

Requirements

  • Print the result of sizeof(char), sizeof(int), and sizeof(double)
  • Store an array length in a size_t variable and print it
  • Use %zu for every size_t value
  • Include at least one loop that uses a size_t index
Take Quiz

Keep learning

Related questions

View more