Firstly you have to understand the ++ operators are undefined when used within a statement - ie they may run before or after the value is used within the printf statement. In fact, I think what it happening in your 2nd example is that you are getting the 'h' from hello, then one of the ++ operators is being applied (making it a 'j') but the other one is being applied after the printf statement has completed. The moral: do not try to be clever with ++ operators.
Now the other part, the 3-level indirection.. WTF?!
1 level of indirection is ok. a "char*" is a pointer to a memory location that contains a character. Easy. This is best thought of as a string type in itself, even though you are storing the string in memory somewhere, and only holding the pointer to that memory as a variable. So your p variable is only 4 bytes long (the length of a pointer), not the length of the string.
When you add another layer of indirection, its nearly always because you want to change the location of the string your variable is pointing to, and the variable has been passed into a function. As all function arguments are passed by-value, you cannot change the pointer, so you pass in a pointer to your pointer which allows you to change the 2nd one.
So you have 'hello' stored in memory somewhere, you pass in a pointer to the pointer to that memory to a function. That allows you to dereference the input to get the pointer to 'hello' and change it.
so
void fn(char** p) { *p = "world"; }
char* p1 = "hello";
fn(&p1);
will work, the location of the p1 variable is passed in, this is a by-value copy, but that's ok because you can derefence it to get the "real pointer" you want to change - p1.
3 levels of indirection... nobody does that, not ever. I think you get a 'j' just by luck, the pointer to a pointer to a pointer to a character is garbage.