0

I'm a beginner in C, today I'm studying the pointer part, I found that I can print an address directly, when using the right type escaper, I can even print the intended value stored in that memory address.

Later, I did some experiments:

##### CODE PART ######
#include <stdio.h>  // Define several variable types, macros, and functions about standard input/output.

int main () {
    char my_string[] = "address test";
    printf("%s\n", &my_string);
    printf("%p\n", &my_string);
    printf("%d\n", &my_string);
    printf("%x\n", &my_string);
    printf("\n");

    char *p = "pointer string test";
    printf("%s\n", p);
    printf("%p\n", p);
    printf("%d\n", p);
    printf("\n");

    char *p2 = 'p';
    printf("%c\n", p2);
    printf("%p\n", p2);
    printf("%d\n", p2);
    return 0;
}


##### OUTPUT #####
address test
0x7fff58778a7b
1484229243
58778a7b

pointer string test
0x107487f87
122191751

p
0x70
112

I'm not quite understand the behavior of %d format output at first, But after more observations and experiments. I found that %d is converting part of the hex value of the memory address.

But for address of my_string it omitted the 0x7fff part, for address of p it omitted 0x10 part, for p2 it omitted the 0x part. In my cognition, 0x is the head sign of a hex value.

But how should I know how much digits will be omitted by C when converting a memory address to int, as it does in the sample of my_string and p?


PS: My system version is OSX10.10

Zen
  • 4,381
  • 5
  • 29
  • 56
  • 2
    If you're using OS X 10.10, then you've got problems with `%d` and `%x` because the pointers will be 64-bit quantities, but your telling `printf()` to print 32-bit quantities. – Jonathan Leffler Dec 31 '15 at 07:02

4 Answers4

4

The C standard (ISO/IEC 9899:2011) has this to say about converting between pointers and integers:

6.3 Conversions

6.3.2.3 Pointers

¶5 An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation.67)

¶6 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.

67) The mapping functions for converting a pointer to an integer or an integer to a pointer are intended to be consistent with the addressing structure of the execution environment.

Note that the behaviour of converting between pointers and integers is implementation defined, not undefined. However, unless the integer type used is uintptr_t or intptr_t (from <stdint.h> — or <inttypes.h>), you are likely to see truncation effects if the sizes of the pointer and the integer types do not match. If you move your code between 32-bit and 64-bit systems, you will run into problems somewhere.

In your code, you have 64-bit pointers (because you're on Mac OS X 10.10 and you need to specify explicitly -m32 to get a 32-bit build, but your results are consistent with a 64-bit build anyway). When you pass those pointers to printf() with the %d and %x conversion specifications, you are requesting printf() to print a 32-bit quantity, so it formats 32 of the 64 bits you passed. The behaviour is ill-defined; you aren't getting a conversion, per se, but the calling code (in main()) pushes a 64-bit pointer onto the stack and the called code (printf()) reads a 32-bit quantity off the stack. If you requested that a single call to printf() should print several values (e.g. printf("%d %x\n", p, p);), you'd get more surprising results.

You should compile with options like:

gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \
    -Wold-style-definition -Werror …

With those options, your code would not compile; the compiler would complain about mismatches between the format strings and the values passed. When I saved your code into a file noise.c and compiled it with clang (from XCode 7.2, running on Mac OS X 10.10.5), I got:

$ /usr/bin/clang -O3 -g  -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \
>     -Wold-style-definition -Werror noise.c -o noise
noise.c:5:20: error: format specifies type 'char *' but the argument has type 'char (*)[13]'
      [-Werror,-Wformat]
    printf("%s\n", &my_string);
            ~~     ^~~~~~~~~~
noise.c:7:20: error: format specifies type 'int' but the argument has type 'char (*)[13]' [-Werror,-Wformat]
    printf("%d\n", &my_string);
            ~~     ^~~~~~~~~~
noise.c:8:20: error: format specifies type 'unsigned int' but the argument has type 'char (*)[13]'
      [-Werror,-Wformat]
    printf("%x\n", &my_string);
            ~~     ^~~~~~~~~~
noise.c:14:20: error: format specifies type 'int' but the argument has type 'char *' [-Werror,-Wformat]
    printf("%d\n", p);
            ~~     ^
            %s
noise.c:17:11: error: incompatible integer to pointer conversion initializing 'char *' with an expression of
      type 'int' [-Werror,-Wint-conversion]
    char *p2 = 'p';
          ^    ~~~
noise.c:18:20: error: format specifies type 'int' but the argument has type 'char *' [-Werror,-Wformat]
    printf("%c\n", p2);
            ~~     ^~
            %s
noise.c:20:20: error: format specifies type 'int' but the argument has type 'char *' [-Werror,-Wformat]
    printf("%d\n", p2);
            ~~     ^~
            %s
7 errors generated.
$

Compile with stringent warnings, and pay heed to those warnings.

Community
  • 1
  • 1
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
3

There is no such thing like "hex value". A number is an amount. Decimal and hex are just representations of the number using different conventions. One can, as well, represent the number using roman numerals and its value still remains the same.

The address of a variable is a concept, not a physical thing. It usually happens to be a (large) number on the current OSes and CPU architectures but this is not set in stone.

Depending on the compiler and the code it compiles, a variable can be stored in memory (it has an address that looks like a large integral number) or not. The compiler can optimize the code and store a temporary variable in a CPU register; in this case it doesn't have an address.

Back to your code. &my_string is the address of variable my_string. It looks like a number. You probably run the code on a 64-bit processor. Memory addresses in this situation are 64-bit unsigned numbers.

  • printf("%p\n", &my_string); - prints a 64-bit unsigned number (the most appropriate representation of a pointer on the hardware architecture you are using).
  • printf("%d\n", &my_string); - you pass a 64-bit number to printf() but because of the %d specifier it thinks the value is 32-bit. It grabs only half of the passed value (4 out of the 8 bytes) and represents it as a signed integer. But which half? It depends on the architecture where the code runs. The behaviour of this code is undefined.
  • printf("%x\n", &my_string); - similar with %d, it prints only (the same) half of the passed value using the hexadecimal notation. The behaviour of this code is, again, undefined.

The 0x prefix is not part of the hex representation; it is just a marker that signals to the C compiler that a number in the hexadecimal representation follows. While the hex representation is universal, different languages use different ways to encode them. Even the C language uses two different markers for them; 0x is used to prefix the numbers and \x is used to prefix the hex representation of a character.

axiac
  • 68,258
  • 9
  • 99
  • 134
2

There's no rule. This is not covered by the C standard. Your code causes undefined behaviour. Any results you observe for this entire program are meaningless.

With printf you must convert the arguments to the right type yourself.

Community
  • 1
  • 1
M.M
  • 138,810
  • 21
  • 208
  • 365
2
printf("%d\n", &my_string);
printf("%x\n", &my_string);

is cause for undefined behavior. The format specifier and the argument type must match for printf to work correctly. For a list of valid format specifiers and data types to which they apply, take a look at http://en.cppreference.com/w/c/io/fprintf.

The following lines suffer from the same problem.

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

printf("%c\n", p2);
printf("%d\n", p2);

The line

char *p2 = 'p';

assigns the integer value that represents the character 'p' to p2. However, that is not a valid address.

The integral types that can be used to hold a pointer are intptr_t and uintptr_t. Hence, you can use:

char my_string[] = "address test";
intptr_t ptr = &my_string;

However, you cannot use %d format specifier to print that value. You will need to use:

printf("%" SCNdPTR "\n", ptr);

to print that.

Take a look at http://en.cppreference.com/w/c/types/integer for more details.

R Sahu
  • 204,454
  • 14
  • 159
  • 270