A pointer doesn't necessarily contain address of another variable, it contains an address of some data, which can but doesn't need to be in a variable. For example, the pointer may point to dynamically allocated data, or to data that is part of an array, or to static data that ships with the executable.
In case of char *p = "hello"
, p
points to the first character of a character array prepared by the compiler, and which contains the letters hello
followed by a terminating zero character. The %s
specifier is designed to receive such a pointer and print the letters up to the zero character, which is why it works.
Your second example, where you print a pointer using %d
is quite different in nature. It abuses the fact that addresses have integer representations, and that T *x = <some integer>
implicitly casts the integer to the corresponding memory address. In your case you create an invalid pointer pointing to the invalid address 10. Then you proceed to call printf
with the %d
specifier without dereferencing the pointer. Since %d
expects an integer, and the address is also internally represented as an integer, this prints the invalid address 10 the pointer points to. However, pointers and integers don't have compatible memory representations, so this kind of code only works by accident, and exhibits undefined behavior.