There are three variables in your code snippet. Here is a table showing the value each variable would have after the execution of each of the lines (if the code were to actually compile).
| x | p_int | p_p_int |
-------------------------+----+-------+---------|
int x = 0; | 0 | undef | undef |
int *p_int = & x; | 0 | &x | undef |
int **p_p_int = & p_int; | 0 | &x | &p_int | <-- begin: all variables lead to x
-------------------------+----+-------+---------|
*p_int = 12; | 12 | &x | &p_int |
**p_p_int = 25; | 25 | &x | &p_int |
-------------------------+----+-------+---------| <-- end: all variables lead to x
p_int = 12; | 25 | 12 | &p_int | <-- p_int now points to garbage
*p_p_int = 3; | 25 | 3 | &p_int |
-------------------------+----+-------+---------|
p_p_int = 27; | 25 | 3 | 27 | <-- p_p_int now points to garbage
------------------------------------------------/
I've used undef to indicate that the variable does not yet exist (i.e. is undefined); &x and &p_int to represent the addresses of those variables (since an exact value is not knowable).
This is a thought exercise to see if you understand different levels of indirection, if you realize that *p_int and p_int refer to different values in memory. As you noticed, it does not compile. Assigning a numeric literal to a pointer is almost certainly an error, of no use outside this sort of thought experiment. In real code, a line like p_int = 12 is probably a typo (likely intended to be *p_int = 12) and the compiler will alert you to this.
Fortunately, the author seems to be aware of the inadvisability of trying to access the memory at addresses 12, 3, and 27, as neither p_int nor p_p_int was de-referenced while storing a bogus address. Still, it would be nice if the author acknowledged these limitations, or better yet devised an exercise that does not need such a disclaimer. (Hopefully this was an isolated slip. Unlike some people, I would not denounce an entire book on the basis of a single exercise. Books are long, C++ is complex, and mistakes happen. )