Home > front end >  Consistent address values for %i, but not %p
Consistent address values for %i, but not %p

Time:10-02

I'm a beginner trying to get to grips with pointers. In the below, the %p value for 't' is unique when compared to 'p' and '&n', whereas the %i value for 't' is the same as 'p' and '&n'.

int main(void)
{
    int n = 50;
    int *p = &n;
    int t = &n;
    // using %p below
    printf("%p\n", p);
    printf("%p\n", &n);
    printf("%p\n", t);
    // using %i below
    printf("%i\n", p);
    printf("%i\n", &n);
    printf("%i\n", t);
}

Example output:

0x7ffc3f503cd8
0x7ffc3f503cd8
0x3f503cd8
1062223064
1062223064
1062223064

I think I am comfortable with %i being the same (although a clear explanation of this would be really appreciated too), and I'm aware that I haven't declared '*t', and so I was wondering what %p for 't' represents here?

CodePudding user response:

Rather than trying to explain what you saw in your rather badly damaged program, I'm going to present a similar but less-damaged and hopefully more-meaningful program. (Andreas Wenzel's answer does a good job of explaining the behavior you saw in the original program.)

int main(void)
{
    int n = 50;
    int *p = &n;
    int t = &n;     /* wrong, but let's see what it does */

    printf("n: addr = %p, contents = %d\n", &n, n);
    printf("p: addr = %p, contents = %p, indirect = %d\n", &p, p, *p);
    printf("t: addr = %p, contents = %x\n", &t, t);
}

When I run this on my machine, the output is

n: addr = 0x7ffeec9f49ac, contents = 50
p: addr = 0x7ffeec9f49a0, contents = 0x7ffeec9f49ac, indirect = 50
t: addr = 0x7ffeec9f499c, contents = ec9f49ac

Let's examine this output in detail:

  1. All three variables — n, p, and t — have similar addresses (0x7ffeec9f49xx) , since they're all local variables, next to each other in main's stack frame.
  2. The contents of the pointer p indeed match the address of variable n.
  3. The value pointed to by pointer p is indeed n's value (50).
  4. Although t is not a pointer variable, it does contain part of n's address: 0xec9f49ac.

The reason t can't hold an entire pointer value is that, on my machine at least, type int is a 32-bit type while pointers are 64 bits. That's why it's important to use real pointer variables to hold pointer values, and to print the pointer values using %p, not some format designed for ordinary integers.

One thing you shouldn't do is that line

int t = &n;

Here we're explicitly jamming a pointer into an int. On my machine, at least, my compiler warns me about this mistake, saying warning: incompatible pointer to integer conversion initializing 'int' with an expression of type 'int *'.

Notice that I have used %d to print n's value, if for no other reason than that I don't like %i.

There's a slight error in the code I've presented: theoretically, %p is only for printing pointers of type void *, not necessarily other pointer types. Strictly speaking I should have written

printf("n: addr = %p, contents = %d\n", (void *)&n, n);
printf("p: addr = %p, contents = %p, indirect = %d\n", (void *)&p, (void *)p, *p);
printf("t: addr = %p, contents = %x\n", (void *)&t, t);

CodePudding user response:

The line

int t = &n;

is not valid ISO C. It is not legal to assign a pointer type to an integer type without a cast. What your compiler is probably doing is ignoring this error and applying an implicit cast. However, on most 64-bit platforms, an int only has a size of 32 bits, whereas a pointer has a size of 64 bits. Therefore, on those platforms, an int is not able to store an entire pointer. This will cause the value to be truncated, by removing the most significant bits of the value.

In your case, the address of n has the following hexadecimal value:

0x00007ffc3f503cd8

Truncating the most significant 32 bits of this 64 bit value will yield the following value:

0x3f503cd8

Therefore, this is the value that is being assigned to t.

Also, the lines

printf("%p\n", t);

and

printf("%i\n", &n);

are invoking undefined behavior, because

  • the %p conversion format specifier requires an argument of type pointer, but you are instead supplying an argument of type int.

  • the %i conversion format specifier requires an argument of type int, but you are instead supplying it a pointer.

On most platforms, this mixing of integer and pointer types will work, as long as the size of the pointer has the same size as the integer data type. However, as already mentioned, on most 64-bit platforms, an int is 32 bits whereas a pointer is 64 bits, so in the case of

printf("%i\n", &n);

the value will probably be truncated, and in the case of

printf("%p\n", t);

it will attempt to print a 64-bit value, although only a 32-bit value exists. Therefore, printf will probably attempt to read an extra 32 bits from memory from whatever address is next to the value that was passed to the function. In your case, these extra 32 bits all happen to have the value 0. The meaning of these extra 32 bits may depend on the function calling convention that your platform is using.

If you want to ensure that the integer data type is sufficiently large to store a pointer, then I recommend that you use the uintptr_t data type (which is probably just an alias for long or long long, depending on your platform). Note that you must #include <stdint.h> if you want to use uintptr_t. However, generally, there is no reason to use an integer data type for storing a memory address.

CodePudding user response:

There are expected types for arguments corresponding to the %p and %i directives, those are void * and int. While some promotions do occur when passing arguments to a variadic function (such as printf), those don't involve converting int * to void * or int. If the argument passed (after the default argument promotions) is not of the expected type, the behaviour is undefined.

That is, the following statements all cause behaviour that is purely coincidental on your system, may do something different on other systems, or may change on your system to something entirely undesirable (such as crashes or vulnerabilities):

printf("%p\n", p);
printf("%p\n", &n);
printf("%p\n", t);
printf("%i\n", &n);

To resolve these issues we could add explicit casts:

printf("%p\n", (void *) p);
printf("%p\n", (void *) &n);
printf("%p\n", (void *) t);
printf("%i\n", (int) &n);

The output would still be coincidental for your system, due to the way %p is specified to function (the wording is rather vague; pointers aren't required to print as numeric) and some implementation-defined aspects behind converting pointers to int (could cause undefined behaviour on some systems), thus the suggestion to use wider types like uintptr_t (which has a "%"PRIuPTR directive, from #include <inttypes.h>) or uintmax_t (see below).

int main(void)
{
    int n = 50;
    printf("%ju\n", (uintmax_t) n);
    printf("%ju\n", (uintmax_t) &n);
}
  • Related