Banging my head against C pointer dereference

The pointers always get me when I try my hands at C. Now, I’m not dumber than most people, and the basic idea is pretty clear to me. But what trips my brain each time is the notation of declaration.

It’s not always illogical. After all, as K&C points out, int *ip is intended as a mnemonic; it says that the expression *ip is an int. Okay, that seems innocent enough, but in fact it’s a two-edged sword. What about when you’re declaring a pointer as a parameter for a function, then calling the function? Compare these two short programs below:

Listing 1. Listing 2.
int foo(int i);
int main() {
 int j = 1;
 foo(j);
 return 0;
}

int foo(int i) {
  /* do something with i */
  return 0;
}
int foo(int *ip);
int main() {
  int j = 2;
  foo(&j);
  return 0;
}

int foo(int *ip) {
  /* do something with (*)ip */
  return 0;
}

For the first program, it’s easy to see that the parameter of foo(), i, is assigned the contents of j from main(): you’re in effect saying:

  1. Let foo() have one parameter, i, of type int.
  2. From main(), assign the value of j, also of type int, to i.

But for the second one, foo() has to be called with an address (a pointer), and yet the declaration, with the mnemonic notation in place, seems to call for an int:

  1. Let foo() have one parameter, *ip, of type int.
  2. From main(), assign the value of &j, of type… uhh… pointer, to… uhh… ???

This difficulty for me to grasp what type of a parameter foo() should be called with is due to the mnemonic notation in int *ip: when you have an int <something> as a parameter declaration, you expect to assign the parameter something that is of type int — not something that is a pointer to an int.

Now, if I give up the mnemonic and instead write the declaration as int* ip, the code translates to natural language quite fluidly:

Listing 3.
int foo(int* ip);
int main() {
  int j = 2;
  foo(&j);
  return 0;
}

int foo(int* ip) {
  /* do something with (*)ip */
  return 0;
}
  1. Let foo() have one parameter, ip, which is a pointer to an int.
  2. From main(), assign the value of &j, also a pointer to an int, to ip.

Of course, this notation opens a whole new can of worms. Consider the following declaration:

int* i, j;

Is j a pointer now, or just an int? It’s way too open to misinterpretation to make this notation commendable despite the previous advantage in readability.

So I’m better off sticking to the mnemonic notation, and just trying to get my head around it. For that, I’m still in desperate need for an easy translation of such code into natural language.

Maybe the mnemonic is the root of the problem. It’s a really bad mnemonic because it almost always works, but then there’s (at least) this one case, where you’re better off not remembering it, because it screws up your logic if you do. So I should think of int *ip as ip is a pointer to an int and just forget about the fact that it resembles a declaration of an int called *ip.