Printing addresses and values is a reasonable way to look at them. But if you can get a debugger up and running, that's much better, because you can follow pointers faster, watch them change as you step, and so on.
If you're familiar with "shortcuts" in Windows, or soft links in linux filesystems, then it might help, just as you're getting started, to think of a pointer as a shortcut (softlink) to another object (whether that object is a struct, a built-in type, another pointer, etc).
A shortcut is still a file. It takes up its own space on the disk drive, it refers to another file, and it can be modified to refer to a different file file from what it used to. Similarly, a pointer in C is an object which occupies memory, contains the address of another memory location, and can be changed to contain a different address just by assigning to it.
One difference is if you double-click a shortcut, it behaves as if you'd double-clicked the thing it points to. That's not the case with pointers - you always have to explicitly dereference a pointer with "*" or "->" in order to access the thing it points to. Another difference is that it's quite common to have pointers to pointers to something in C.
As for the jargon, you just have to learn it unfortunately. "int doSomething(char **hihi)" means "a function called doSomething, which returns an integer, and takes as a parameter a pointer to pointer a char". The crucial point is that "char ** hihi
" means "a pointer-to-pointer-to-char. We will call the pointer-to-pointer-to-char hihi". You say that the "type" of hihi is char**, and that the "type" of *hihi (what you get when you dereference the pointer) is char*, and the type of **hihi is char.
Frequently in C, a pointer to a char means a string (in other words, it's a pointer to the first char in a NUL-terminated array). So often "char *" means "string", but it doesn't have to. It might just mean a pointer to one char. A bit like a shortcut to a 1-byte file in Windows (well, with FAT32 anyway), a pointer to a char in C is actually bigger than the thing it points to :-)
Likewise, a char** often means not just a pointer to one string-pointer, but to an array of string-pointers. It might not, but if it does then the following little picture might help:
hihi
____ ____ ________ _________ _______
|____| -----> |____| *hihi ---> |___A____| |___B_____| |___C___|
|____| *(hihi+1) ------------------^ ^
|____| *(hihi+2) ---------------------------------|
| ...| etc.
hihi points to the tower-block effort, which is my way of representing an array of pointers. As you already noted, I could have written hihi[0] in place of *hihi, hihi[1] in place of *(hihi+1), and so on.
This is a contiguous block of memory, and each pointer-sized chunk of it contains the address of (that is, it "points to") another block of memory, off goodness-knows-where, containing one or more chars. So, hihi[0] is the address of the first char of string A, hihi[1] is the address of the first char of string B.
If hihi doesn't point to an array, just a single pointer, then the tower block is a bungalow. Likewise if *hihi doesn't point to a string, just one char, then the long thin block is a square. You might ask, "how do I know how many floors the tower block has?". That's a big deal in C programming - usually either the function documentation would tell you (it might say "1", or "12", or "enough for the thing you're telling me to do", or else you would pass the number of floors as an extra parameter, or else the documentation would tell you that the array is "NULL terminated", meaning that it will keep reading until it sees the address/value NULL, and then stop. The main function actually does both the second and third thing - argc contains the number of arguments, and just to be on the safe side argv is NULL-terminated.
So, whenever you see a pointer parameter, you have to look at the documentation for the function to see whether it expecting a pointer to an array, and if so how big the array has to be. If you aren't careful about this, you will create a kind of bug called "buffer overflow", where a function is expecting a pointer to a large array, you give it a pointer to a small array, and it scribbles off the end of what you gave it and starts corrupting memory.