From: mikey@ontek.com (euphausia superba)
Newsgroups: comp.lang.c
Subject: Re: Why should POINTERS be so damn hard to understand ?
Keywords: LONG and BORING and TEDIOUS and KILL ME NOW I'M DEAD ALREADY
Date: 31 Aug 92 22:40:31 GMT
Organization: Ontek Corporation -- Laguna Hills, California

In comp.lang.c, chas@castle.ed.ac.uk (C Spencer) asks the musical question:
| 
| Why does a pointer need to know what it's pointing to? Surely it's just
| an address - if there was only one kind of pointer, there wouldn't be so
| much to learn, and be confused by.

Er, kind of.  Assembly languages are the usual thing to mention when
talking about untyped pointers, but it's difficult to make a useful
comparison with C because there are so many other significant differences
which cloud the picture. 

Perhaps a better comparison would be with PL/1.  In that language,
untyped pointers are available, and commonly used.  Some of the
things you'd do with typed pointers can be done with other
features of PL/1.  In my experience, pointers in C are used mostly for 
passing addresses of variables, pointing at dynamically allocated
structs, and to point at and index into arrays.  Let's compare
and contrast these sitations with their equivalents in PL/1, The
Language of the Untyped Pointer.

---

Example 1.  Passing parameters by address.

As we all know, passing a parameter by address in C requires
explicit use of the unary & operator.

      a()
      {
	int foo;

	b(&foo);
      }

      b(bar)
	int * bar;
      {
	*bar = 123;
      }

PL/1 on the other hand, defaults to pass-by-address, so no
special syntax is required.

      a: proc();
      begin;
	declare foo bin;

	call b(foo);
      end;

      b: proc(bar);
      begin;
	declare bar bin;

	bar = 123;
      end;

While pass-by-address has its faults, returning of many values
from a function is much simpler.  To pass-by-value in PL/1 
requires an extra set of parentheses:  "call b((foo));" turns 
the lvalue into an rvalue.  The assignment in b of bar would 
then have no effect on foo. 

So the argument here condenses down to which type of parameter
passing you'd do most often, and how paranoid you plan to be
about protecting your variables from unintended modification.
For me, the trade off doesn't really lean either direction, but
for some this could easily be a religious issue.

---

Example 2.  A pointer to a dynamically allocated data structure.

In C, imagine this simple program fragment:

    {
      struct emp {
	char        name[80];
	double      income;
      };

      struct emp    * foo, 
		    * NextEmp();

      foo = NextEmp();
      printf("%s %f\n", foo->name, foo->income);
    }

The equivalent PL/1 syntax (to the best of my hazy recollection) is:

    begin
      declare
	1 emp        based,
	  2 name     char(80) varying,
	  2 income   float bin(47),
	foo          pointer,  /* (a) */
	NextEmp      external entry returns(pointer);

      foo = NextEmp;
      put list(foo->emp.name, foo->emp.income) skip;
		 /* ^^^ (b) */
    end;

Aside from some trivial details, you'll note that (a) the pointer
has no type associated with it so (b) use of the pointer requires a
mention of the struct you intend to use it on.  It is uncommon to
inadvertently refer to the wrong structure, but on the rare occasions
when it does happen, it can be a difficult bug to track down.  The
C trick of using a union of structs with similar fields at the top
of each and then accessing the "wrong" branch of the union is unnecessary
here; it's perfectly acceptible to simply point at whatever
structure you please.

---

Example 3: Arrays.

It is, of course, common knowledge in this group that you can access arrays
by subscripts and by pointer arithmetic equally well, and that as far
as the compiler is concerned these are pretty much the same thing.
Let's write a function which sets the last character in a string to '!'.

      exclaim(a)
	char *a;
      {
	int c;
         
        c = strlen(a) - 1;

      #ifdef USE_ARRAY_SUBSCRIPTS
	a[c] = '!';
      #endif
        
      #ifdef USE_POINTER_ARITH
        a += c;
	*a = '!';
      #endif
      }

Now, in PL/1, the entire approach to arrays is different.  There is no
ambiguity between pointers and arrays, and no temptation to traverse an
array with a pointer or to do any sort of pointer arithmetic.  If you
want an element from an array, use a subscript.  Multidimensional
arrays are (much) simpler to use.  The main drawback is that to have
arrays with dynamic extents requires some trickery, which we'll not get
into here.  Char and bit strings are somewhat special and can be
accessed through the "substr()" pseudofunction, which has the
remarkable ability of returning a direct lvalue.

      exclaim: proc(a);
      begin
	declare 
	  char(*) varying a,
	  int c;

        c = length(a);

	substr(a, c, 1) = "!";       /* a[c]="!" would also work */
      end;

---

(End of examples; I didn't run any of it through cc or a pl1 compiler
so beware of typos.)

My overall point here is that pointers are really nifty and useful
things but because they are used for several totally different purposes
at times, the confusion that novices feel is completely understandable.  
I think their confusion would be less if arrays were always accessed as
arrays and if there were a less "pointy" way of passing parameters by
address.  PL/1 implemented untyped pointers not to add flexibility or
power, but because most of the really dangerous things you do with
typed pointers in C have to be done in less confusing ways in PL/1, so
there is no great loss in living without the type information.

But C is not PL/1, nor is it assembly, so unless you plan on casting 
everything to and from void* at every turn, you might as well get 
used to typed pointers.

      the krill

Newsgroups: comp.lang.c