Ian.M provided a very detailed answer. To continue from that (and apologies for repeating some of what Ian.M wrote):
The difference between the working expression,
printf("%d", *(int*)ptr);and the non-working expression,
printf("%d", *ptr);is the cast of the void pointer to a pointer to an int, before being dereferenced.
Remember, when applied to a pointer type,
* is the dereferencing operator: it "reaches through" the pointer, and grabs the value pointed by the pointer. The type of that value is defined by the pointer type.
If we have
int *ptr1; float *ptr2;and we properly set them to point somewhere valid, then
*ptr1 is the integer that
ptr1 points to, and
*ptr2 is the float that
ptr2 points to.
At this point, I'd like to repeat something I basically always tell those who are learning about pointers in C.
We have three types of pointers:
void pointers, object pointers, and function pointers.
Function pointers point to functions, and can be called like functions.
Object pointers point to data of some type.
Void pointers are those that do not specify a type at all. POSIX C states that void pointers can be cast to and from both object and function pointers, but standard C does not allow conversion between object and function pointers and does not allow casting a void pointer into a function pointer. This leads to some unfortunate language-lawyerism wrt.
dlsym() on POSIXy systems (Linux, Android, Mac OS, BSDs) when portability is discussed. The reason for this is some historical ABIs had certain features that made the distinction useful. On AVR and ARM microcontrollers, object and function pointers are interchangeable. Those with Harvard architectures (separate address spaces for Flash and RAM) can use compiler-provided address space attributes; for example GCC and Clang have excellent support for these now (in 2022), although not all existing code (especially Arduino) use these very effectively.
When reading an object or void pointer type or type specification, split it at each
*. Start reading from right (starting at the variable name, if any), each split part at a time from right to left, replacing the
* with
"is a pointer to". This does not work for function pointers because their syntax is more complex, and you can argue how one should read qualifiers like
const or
volatile, but it does give a pretty good intuitive understanding.
For example,
char *const *ptr could be described in English as "
ptr is a pointer to const pointer to char". This means that it is an indirect pointer, it points to a pointer to char. The variable
ptr itself may be modified, and the char or char array the indirect pointer points to may be modified as well, but the type includes a promise that we do not try to modify the pointer that points to the first char, i.e. we promise not to try and modify
*ptr.
Finally, array notation is mostly just a convenient form of pointer notation. If we have
char *s that points to a string –– a string being an array of
char terminated with a nul char
'\0' ––, then both
*s and
s[0] refer to the first character in that string. Similarly, given some integral type variable
i (usually
size_t,
ssize_t, or
int, although
int may be limited to a smaller range),
si] and
*(s + i) both refer to the same,
i+1'th character of the string (since
i = 0 is the first one).
One quirk of C array notation, related to this, should only be used in Obfuscated C contests. It is that
i[a] is the same as
a[i], if
a is an array or pointer expression, and
i is an expression having some integral type. Thus, for example
(x+w*y)[a] is the same as
a[x+w*y], if
x,
y, and
w have some integral type, and
a is an array or a pointer or a pointer expression. This only leads to confusion, and should never be used. Nevertheless, knowing about this is useful, because if one accidentally writes such an expression, the compiler will not complain about it (because it is valid C), so it is important for us human programmers to detect these instead. (I would even go as far as rejecting code that relies on such, because if code is intended to be confusing, I definitely don't want anything to do with it.)