Pointers are the backbone of C programming, offering direct memory manipulation capabilities that define the language’s power and efficiency. Even so, for beginners and even intermediate developers, correctly displaying the value of a pointer variable remains a surprisingly common source of confusion, warnings, and subtle bugs. Understanding how to print a pointer in C requires more than just memorizing a format specifier; it demands a grasp of memory addresses, type safety, and the specific requirements of the C standard library.
The Fundamental Format Specifier: %p
The single most important rule when printing a pointer address in C is to use the %p format specifier with the printf family of functions. This specifier is designed specifically to handle void * (generic pointer) types and prints the address in an implementation-defined format, typically hexadecimal with a 0x prefix Took long enough..
This changes depending on context. Keep that in mind Simple, but easy to overlook..
Consider this basic example:
#include
int main() {
int value = 42;
int *ptr = &value;
printf("Address of value: %p\n", (void *)ptr);
return 0;
}
Output (example):
Address of value: 0x7ffd1a2b3c4c
Why the Cast to (void *) is Mandatory
You might wonder why the cast (void *) is used in the example above. The C standard dictates that the %p specifier expects an argument of type void *. While pointers to object types (like int *, char *, struct node *) are implicitly convertible to void * in assignment contexts, variadic functions like printf do not perform implicit conversions for arguments corresponding to ... (ellipsis) parameters due to default argument promotions That alone is useful..
Passing an int * directly to %p without a cast is technically undefined behavior according to the C standard (though it works on virtually all modern architectures because pointers share the same representation). To write strictly conforming, portable code, always cast your pointer to void * when using %p Simple, but easy to overlook..
It sounds simple, but the gap is usually here It's one of those things that adds up..
// Correct and Portable
printf("%p\n", (void *)my_pointer);
// Technically Undefined Behavior (avoid this)
printf("%p\n", my_pointer);
Common Pitfalls: %x, %lu, and %d
A frequent mistake is treating a pointer as an integer and printing it with integer format specifiers like %x (hexadecimal), %lu (unsigned long), or %d (decimal). This approach is fraught with danger That's the part that actually makes a difference..
1. Size Mismatch
On 64-bit systems (LP64 data model), pointers are 64 bits (8 bytes), while int is often 32 bits (4 bytes) and long might be 32 or 64 bits depending on the OS (Windows vs. Linux/macOS). Using %x or %d truncates the upper bits of the address, printing a wrong value. Using %lu might work on Linux/macOS (where long is 64-bit) but fails on 64-bit Windows (where long is 32-bit).
2. Type Safety and Promotions
printf is a variadic function. Default argument promotions convert float to double and narrow integers (char, short) to int. Pointers are not promoted to integers. If you use %lx (expecting unsigned long) but pass a pointer (64-bit), the register usage or stack layout expected by printf will not match the actual argument passed, leading to garbage output or crashes Worth knowing..
3. The uintptr_t Solution (C99 and later)
If you absolutely must treat a pointer as an integer—for example, to perform bitwise arithmetic on the address or store it in an integer variable—use the uintptr_t type defined in <stdint.h>. This is an unsigned integer type guaranteed to be capable of holding a pointer.
To print it, use the PRIuPTR macro from <inttypes.h>:
#include
#include
#include
int main() {
int val = 10;
int *p = &val;
// Safe conversion to integer
uintptr_t addr = (uintptr_t)p;
// Correct printing using PRIuPTR macro
printf("Address as integer: 0x%" PRIxPTR "\n", addr); // Hex
printf("Address as decimal: %" PRIuPTR "\n", addr); // Decimal
return 0;
}
The official docs gloss over this. That's a mistake.
This is the only portable way to print a pointer address as a raw number without using %p.
Printing Different Pointer Types
The %p specifier handles void * generically, but you will encounter specific pointer types frequently. Here is how to handle them correctly.
Character Pointers (char *) and Strings
This is the most notorious trap in C. If you pass a char * to printf with %p, it prints the address. If you pass it with %s, printf treats it as a string and prints characters until it hits a null terminator (\0).
char *str = "Hello";
char c = 'A';
char *char_ptr = &c;
printf("String content (%%s): %s\n", str); // Prints: Hello
printf("String address (%%p): %p\n", (void *)str); // Prints address
// DANGER: char_ptr points to a single char, not a null-terminated string
// printf("%s\n", char_ptr); // UNDEFINED BEHAVIOR: reads past 'A' until random \0
printf("Single char address (%%p): %p\n", (void *)char_ptr); // Safe
Rule of thumb: Always cast to (void *) and use %p for the address. Only use %s when you are certain the pointer references a valid, null-terminated string It's one of those things that adds up. Surprisingly effective..
Function Pointers
Printing function pointers is a gray area in the C standard. The %p specifier is only guaranteed to work for object pointers (data pointers). Function pointers may have a different size or representation (e.g., Harvard architecture, or x86 far pointers in legacy 16-bit code) Worth keeping that in mind..
On modern common architectures (x86-64, ARM64), function pointers and data pointers share the same representation, so casting to void * usually works:
void my_func() {}
int main() {
// Common workaround, not strictly standard-guaranteed for function pointers
printf("Function address: %p\n", (void *)my_func);
return 0;
}
Strictly speaking, the standard does not require function pointers to be convertible to void *. For maximum portability on exotic architectures, avoid printing function pointers or cast via an integer type like uintptr_t if the platform supports it.
void * (Generic Pointers)
Since %p expects void *, a void * variable requires no cast.
void *generic_ptr = malloc(100);
printf("Allocated memory at: %p\n", generic_ptr); // No cast needed
free(generic_ptr);
Advanced Formatting Control
The output format of %p is implementation-defined. Practically speaking, the C standard does not mandate the 0x prefix, lowercase vs uppercase hex digits, or leading zeros. On the flip side, most modern compilers (GCC, Clang, MSVC) output lowercase hex with 0x prefix (e. g., 0x7ffc12345678).
You cannot force uppercase hex (%P does not exist) or remove the prefix using standard printf flags. If you need a specific format (