简体   繁体   中英

printf("%p") and casting to (void *)

In a recent question, someone mentioned that when printing a pointer value with printf, the caller must cast the pointer to void *, like so:

int *my_ptr = ....

printf("My pointer is: %p", (void *)my_ptr);

For the life of me I can't figure out why. I found this question , which is almost the same. The answer to question is correct - it explains that ints and pointers are not necessarily the same length.

This is, of course, true, but when I already have a pointer, like in the case above, why should I cast from int * to void * ? When is an int * different from a void *? In fact, when does (void *)my_ptr generate any machine code that's different from simply my_ptr ?

UPDATE: Multiple knowledgeable responders quoted the standard, saying passing the wrong type may result in undefined behavior. How? I expect printf("%p", (int *)ptr) and printf("%p", (void *)ptr) to generate the exact same stack-frame. When will the two calls generate different stack frames?

The %p conversion specifier requires an argument of type void * . If you don't pass an argument of type void * , the function call invokes undefined behavior.

From the C Standard (C11, 7.21.6.1p8 Formatted input/output functions):

"p - The argument shall be a pointer to void."

Pointer types in C are not required to have the same size or the same representation.

An example of an implementation with different pointer types representation is Cray PVP where the representation of pointer types is 64-bit for void * and char * but 32-bit for the other pointer types.

See "Cray C/C++ Reference Manual", Table 3. in "9.1.2.2"http://docs.cray.com/books/004-2179-003/004-2179-003-manual.pdf

In C language all pointer types potentially differ in their representations. So, yes, int * is different from void * . A real-life platform that would illustrate this difference might be difficult (or impossible) to find, but at the conceptual level the difference is still there.

In other words, in general case different pointer types have different representations. int * is different from void * and different from double * . The fact that your platform uses the same representation for void * and int * is nothing more than a coincidence, as far as C language is concerned.

The language states that some pointer types are required to have identical representations, which includes void * vs. char * , pointers to different struct types or, say, int * and const int * . But these are just exceptions from the general rule.

Other people have adequately addressed the case of passing an int * to a prototyped function with a fixed number of arguments that expects a different pointer type.

printf is not such a function. It is a variadic function, so the default argument promotions are used for its anonymous arguments (ie everything after the format string) and if the promoted type of each argument does not exactly match the type expected by the format effector, the behavior is undefined. In particular, even if int * and void * have identical representation,

int a;
printf("%p\n", &a);

has undefined behavior.

This is because the layout of the call frame may depend on the exact concrete type of each argument. ABIs that specify different argument areas for pointer and non-pointer types have occurred in real life (eg the Motorola 68000 would like you to keep pointers in the address registers and non-pointers in the data registers to the maximum extent possible). I'm not aware of any real-world ABI that segregates distinct pointer types, but it's allowed and it would not surprise me to hear of one.

c11: 7.21.6 Formatted input/output functions (p8):

p The argument shall be a pointer to void . The value of the pointer is converted to a sequence of printing characters, in an implementation-defined manner.

In reality except on ancient mainframes/minis, different pointer types are extremely unlikely to have different sizes . However they have different types , and per the specification for printf , calling it with the wrong type argument for the format specifier results in undefined behavior . This means don't do it.

Addressing the question:

When will the two calls generate different stack frames?

The compiler may notice that the behaviour is undefined, and issue an exception, illegal instruction, etc. There's no requirement for the compiler to attempt to generate a stack frame, function call or whatever.

See here for an example of the compiler doing this in another case of UB . Instead of generating a deference instruction with null argument, it generates ud2 illegal instruction.

When the behaviour is undefined according to the language standard, there are no requirements on the compiler's behaviour.

when printing a pointer value with printf, the caller must cast the pointer to void *

Even casting to void * is not sufficient for all pointers.

C has 2 kind of pointer: Pointers to objects and pointers to functions .

Any object pointer can convert to void* with no problem:

printf("My pointer is: %p", (void *)my_ptr); // OK when my_ptr points to an object

A conversion of a pointer to a function to void * is not defined.

Consider a system in 2021 where void * is 64-bit and a function pointer is 128 bit.


C does specify (my emphasis)

Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type. C17dr § 6.3.2.3 6

To print a function pointer could attempt:

printf("My function pointer is: %ju", (uintmax_t) my_function_ptr); // Selectively OK

C lacks a truly universal pointer and lacks a clean way to print function pointers.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM