[comp.lang.c] effect of free

ping@cubmol.BIO.COLUMBIA.EDU (Shiping Zhang) (08/14/89)

When some space is allocated using calloc() or similar routines
and assigned to more than one point, for example as in following lines,

int *pt1,*pt2;

pt1=(int *)calloc(100,sizeof(int);
pt2=pt1;

then if free() is called using ONE of the points, say pt1, as its 
argument, is the space pointed by pt1 really freed? Actually I have
two questions about this problem above.
First, can pt2 still be used as a valid point? In other words,
is pt2 still pointing to the location it is assigned to? 
My answer to this question SEEMS yes according to some tests I made.
Second, would the space still pointed to by pt2 be reallocated
by other calls to calloc() or other similar funtions?
According to the document I read about free(), the space pointed
by the argument to free() is made available for further allocation,
though its contents are left unchanged. But it does not say what will
happen to the other points pointed to the same space.

Can anyone clarify this issue for me? Thanks.


-ping

cpcahil@virtech.UUCP (Conor P. Cahill) (08/14/89)

In article <319@cubmol.BIO.COLUMBIA.EDU>, ping@cubmol.BIO.COLUMBIA.EDU (Shiping Zhang) writes:
> 
> When some space is allocated using calloc() or similar routines
> and assigned to more than one point, for example as in following lines,
> 
> int *pt1,*pt2;
> 
> pt1=(int *)calloc(100,sizeof(int);
> pt2=pt1;
> 
> then if free() is called using ONE of the points, say pt1, as its 
> argument, is the space pointed by pt1 really freed? Actually I have
> two questions about this problem above.
> First, can pt2 still be used as a valid point? In other words,
> is pt2 still pointing to the location it is assigned to? 
If you call free(pt1), both pt1 and pt2 will still point to the same
address, but this address is no longer "assigned".  A future memory 
allocation may use part of, or all of the same data space.  There is 
no guarranty that the next allocation will use it or won't use it.

> My answer to this question SEEMS yes according to some tests I made.
> Second, would the space still pointed to by pt2 be reallocated
> by other calls to calloc() or other similar funtions?
> According to the document I read about free(), the space pointed
> by the argument to free() is made available for further allocation,
> though its contents are left unchanged. But it does not say what will
> happen to the other points pointed to the same space.
> 
A free() will release the "reservation" on data space that was obtained
via any of the memory allocation routines.  A pointer into an area that
has been freed (be it a pointer to the beginning, middle, or end) may and may
not work.  Using such a pointer is a very bad practice and will usually
bite you in the *ss.

You seem to be confused about what a pointer acutally is.  The only function
of a pointer is to contain the address of a memory position.  Once you assign
a memory position to a pointer this value will not change unless you change
the pointer itself, so the free() will not have any effect on either pointer,
but will affect whether the data at the memory position they point to is
"reserved" for access through that pointer.

This is similar to the problem of returning the address of an automatic 
variable.  The address was valid while the function that contained the
variable was executing, but once that function ends, the data area on the
stack is freed for use by other routines.  You might be able to use the
address with no problem (although this is a bug that will almost always
catch up with you some time in the future).

bph@buengc.BU.EDU (Blair P. Houghton) (08/14/89)

In article <319@cubmol.BIO.COLUMBIA.EDU> ping@cubmol.UUCP (Shiping Zhang) writes:
>
[...mallocs space; assigns pt1 to point to it...]
>
>pt2=pt1;
>
>then if free() is called using ONE of the points, say pt1, as its 
>argument, is the space pointed by pt1 really freed?

Yes.  It can now be allocated to something else.  It is _not_,
however, cleared or otherwise overwritten by free(), nor are
any pointers into it deallocated or overwritten.

>First, can pt2 still be used as a valid point?  In other words,
>is pt2 still pointing to the location it is assigned to? 

Yes, it points there; but, the data is not guaranteed to be valid.  The
exception is if these pointers are actually stored in the block of
allocated memory into which they also point.  Their life is over.
Until their (sizeof long) bytes are reallocated to some other purpose
and overwritten, you could still dereference them.  Note, however, that
the only way to have a pointer inside the block is if you have, outside
the block, already allocated a pointer to that pointer.

Nobody ever said data-structures was easy... :-)

>My answer to this question SEEMS yes according to some tests I made.

No intervening [mc]alloc()'s, no problem.  You have only your
conscience to maintain.

>Second, would the space still pointed to by pt2 be reallocated
>by other calls to calloc() or other similar funtions?

Yes.  pt2 is as gone as pt1 is.  Which is to say, pt2 and pt1
are still there, but they point to unreliable things.

Programming tip:

It might be prudent to have a spare pointer to the beginning of the
allocated space expressly for the purpose of using that pointer in
a call to free(), since any other pointers (those used for data
processing) may be changed and never set back to their origin.
Prudent, if not economical; you rarely have a situation where none
of the pointers is already used to be the origin for offsets.

I'd do it in linked-list applications where the data is accessed by
routines that find the ends of lists by entries in the nodes.  I keep a
pointer to the original node, and a pointer to the origin of the data
block.  The original node may be removed from the list, and one of the
child nodes will become the new root for the list.  Now there is
obviously a need to maintain that pointer to the origin of the
dynamically-allocated block of memory, regardless of the use to which
the rest of the memory is put, or wherever any other pointers point.

>According to the document I read about free(), the space pointed
>by the argument to free() is made available for further allocation,
>though its contents are left unchanged. But it does not say what will
>happen to the other points pointed to the same space.

Leave it to the manual to be inadequate... Leave it to me to
obfuscate its inadequacy... :-)

The block originally allocated had a size (the size you specified).
That block is a unit.  When you make a call to free() using a pointer
to that block, the entire unit becomes free to be allocated again.  No
pointer that points into that block can be guaranteed to point to what
it pointed to before you free()'d the block, (although it certainly
points to the place that thing used to be).  This is true especially
if you know there is a [mc]alloc() between the free() and the
use of the pointer, and especially if the recent allocation
required less area than was freed.

				--Blair
				  "free(advice);"

cliffs@suntrek.east.sun.com (Clifford C. Skolnick) (08/14/89)

In article <3756@buengc.BU.EDU> bph@buengc.BU.EDU (Blair P. Houghton) writes:

   >then if free() is called using ONE of the points, say pt1, as its 
   >argument, is the space pointed by pt1 really freed?

   Yes.  It can now be allocated to something else.  It is _not_,
   however, cleared or otherwise overwritten by free(), nor are
   any pointers into it deallocated or overwritten.

In current implimentations this is true.  But what if some person makes
a C library that actually returns the space to the operating system?  I
would change this to a no.

   >[questions about using the pointer]
   >My answer to this question SEEMS yes according to some tests I made.

   No intervening [mc]alloc()'s, no problem.  You have only your
   conscience to maintain.

Again, what if the memory was returned to the operating system?

			Cliff Skolnick

--
Cliff Skolnick			cliffs@sunrock.east.sun.com

ping@cubmol.BIO.COLUMBIA.EDU (Shiping Zhang) (08/15/89)

After I posted the question, I got many responces from both the network
and e-mail. They certainly answered my original questions and clarified
a lot of confusions I had. I am very grateful to all of them.

Now some new questions arises. Many people said that after free()
is called, the point used as the argument to free() is still valid
and can be used IF NO (mc)alloc()'s are called after the call to free().
I understand the reason to say that is because that point still points
to the same space and the contents of it is not disturbed. (Same to
the other points point to the same space.)  But I work on a Sun3
machine under unix system, there are more than one process running
at the same time. So will other processes take that space and change
its contents? The answer must be yes to my understanding. Please
confirm me.  Even if no (mc)alloc()'s are called after free() in a
process, how about calls to other functions with local varibles?
The memory spaces for local varibles of functions are fixed at the
begining of a process or allocated dynamically by the parent process
or by the operating system? If the space for local varibles are
allocated dynamically, will the space freed by free() be allocated
for local varibles?


-ping

gwyn@smoke.BRL.MIL (Doug Gwyn) (08/15/89)

In article <319@cubmol.BIO.COLUMBIA.EDU> ping@cubmol.UUCP (Shiping Zhang) writes:
>Can anyone clarify this issue for me? Thanks.

After free(), the actual storage object itself is no longer valid.
All pointers to any part of it are no longer valid.
If you attempt to continue to use those pointer values, it may appear
to work for a while on some systems, but eventually you'll get into trouble.

gwyn@smoke.BRL.MIL (Doug Gwyn) (08/15/89)

In article <3756@buengc.BU.EDU> bph@buengc.bu.edu (Blair P. Houghton) writes:
>Yes.  It can now be allocated to something else.  It is _not_,
>however, cleared or otherwise overwritten by free(), ...

It can be; that's implementation dependent.

landon@Apple.COM (Landon Dyer) (08/15/89)

ping@cubmol.BIO.COLUMBIA.EDU (Shiping Zhang) writes:
> Now some new questions arises. Many people said that after free()
> is called, the point used as the argument to free() is still valid
> and can be used IF NO (mc)alloc()'s are called after the call to free().

Definitely not.  Code that does this is NOT portable.

ANSI 4.10.3 sez:

    "The pointer returned if the allocation succeeds ... may be
    ... used to access ... objects in the space allocated until
    the space is explicitly freed or reallocated."


"Foo!" you cry, "It works FINE on my FrobCo Unix(tm) machine!"

Super.  It won't work anywhere else, and it might stop working on YOUR
machine tomorrow.  DO IT RIGHT.

-----------------------------------------
Landon Dyer, Apple Computer, Inc.          "Mmmph!  Urghurmph!  Grugmph!"
Development Systems Group (MPW)            
                                           "What's he trying to say?"

cpcahil@virtech.UUCP (Conor P. Cahill) (08/15/89)

In article <320@cubmol.BIO.COLUMBIA.EDU>, ping@cubmol.BIO.COLUMBIA.EDU (Shiping Zhang) writes:
> After I posted the question, I got many responces from both the network
> and e-mail. They certainly answered my original questions and clarified
> a lot of confusions I had. I am very grateful to all of them.
> 
> Now some new questions arises. Many people said that after free()
> is called, the point used as the argument to free() is still valid
> and can be used IF NO (mc)alloc()'s are called after the call to free().

It is not "valid" to use a pointer into "freed" space.  Under the right
conditions you will get away with it, but if you keep doing it long enough
the man will be pulling you over to the side of the road with a core drop.

> I understand the reason to say that is because that point still points
> to the same space and the contents of it is not disturbed. (Same to
> the other points point to the same space.)  But I work on a Sun3
> machine under unix system, there are more than one process running
> at the same time. So will other processes take that space and change
> its contents? The answer must be yes to my understanding. Please
> confirm me.

Different processes execute in thier own address space.  The free(3) library
function does not return any space to the OS and therefore it will not be
available to other processes.  If it did return the space to the OS, the 
pointers would now be pointing out of your memory space and you would get
some form of a core drop.

>  Even if no (mc)alloc()'s are called after free() in a
> process, how about calls to other functions with local varibles?

Local variables are placed on the "stack" which is a different portion
of your address space and is not normally adjacent to your allocated data
region.  A sample picture of a c program layout would be something like the 
following:

	+-----------------------------------+
	|				    |
	|  text        			    |
	|				    |
	+-----------------------------------+
	|				    |
	|  Initialized data		    |
	|				    |
	+-----------------------------------+
	|				    |
	|  Uninitialized data		    |
	|				    |
	+-----------------------------------+
	|				    |
	|  stack region (grows down)        |
	|				    |
	:				    :
	:				    :
	:				    :
	:				    :
	:				    :
	|				    |
	|  allocated data region (grows up) |
	|				    |
	+-----------------------------------+

This picture can vary from machine to machine, but the concept is that 
there are two (three if you count shared memory) growable data regions in 
an executing program and never the twain shall meet.

flaps@dgp.toronto.edu (Alan J Rosenthal) (08/15/89)

Perhaps I can shed a little light on this discussion (not that it's been
completely void of light so far, far from it).

The original code looked something like this:

    char *a, *b, *malloc();
    a = malloc(...);
    b = a;
    free(a);
    ... b ...

The poster asked if free(a) freed b.  Seemed to be thinking of a lisp-like
language where objects don't go away until the last pointer to them does.

I think the following is a good way to think of it, given that pointers are
quite abstract objects.  Suppose you tell me your work phone number, and I
write it down.  I go home and copy it into my address book.  I use the copy in
my address book when I telephone you, but for some reason leave the piece of
paper you wrote your number on in my wallet.  A month later I see you and you
say "the number's been changed, here give me that piece of paper" and you
correct it on the piece of paper.  But I go home and the old number's written
down in my address book, not crossed off or altered.  Obviously I can't phone
you using the number you've crossed out, but can I phone you using my previous
copy of the number, which was copied when the number was still valid?

Copying a pointer doesn't copy what it points to, any more than copying the
phone number duplicates the phone it's the number of.

ajr

p.s. of course, as well, since C is call-by-value, "free(a)" can't change the
value of `a' either, but I think the original poster understood that.

davidsen@sungod.crd.ge.com (ody) (08/15/89)

In article <33994@apple.Apple.COM> landon@Apple.COM (Landon Dyer) writes:

| "Foo!" you cry, "It works FINE on my FrobCo Unix(tm) machine!"
| 
| Super.  It won't work anywhere else, and it might stop working on YOUR
| machine tomorrow.  DO IT RIGHT.

  Actually the problem is it won't work *everywhere* else. Use of free'd
space is a certain way to non-portability. And, since the topic was
brounght up, if you do a lot of malloc and free there is a good chance
that the data will be modified by the free when garbage collection turns
two adjacent small free blocks into one large block. I actually got
bitten by this once, when a typo resulted in use of the wrong pointer.
	bill davidsen		(davidsen@crdos1.crd.GE.COM)
  {uunet | philabs}!crdgw1!crdos1!davidsen
"Stupidity, like virtue, is its own reward" -me

bph@buengc.BU.EDU (Blair P. Houghton) (08/15/89)

In article <320@cubmol.BIO.COLUMBIA.EDU> ping@cubmol.UUCP (Shiping Zhang) writes:
>
>Now some new questions arises. Many people said that after free()
>is called, the point used as the argument to free() is still valid
>and can be used IF NO (mc)alloc()'s are called after the call to free().
>I understand the reason to say that is because that point still points
>to the same space and the contents of it is not disturbed.

Well, as many have informed me after I posted a response to the original,
some implementations are not all implementations.

It is possible that the call to free() may write into the freed block, and
that the OS may gain control of it, and use it for whatever the
system uses memory for.

I guess the word is, once space is freed, don't even try to get data
out of it.  It's risky at least, and nonportable without question.

				--Blair
				  "Four more compilers and
				   daddy's got a new pair of
				   shoes."

karl@haddock.ima.isc.com (Karl Heuer) (08/16/89)

In article <3777@buengc.BU.EDU> bph@buengc.bu.edu (Blair P. Houghton) writes:
>I guess the word is, once space is freed, don't even try to get data
>out of it.

It's worse than that.  Once space is free()'d, don't even copy the obsolete
pointer value from one pointer object to another.  If the segment has been
released, loading its value into an address register could cause a trap.

On the other side, it really was legal (and even encouraged) at one time to do
certain things after calling free().  This has generally been recognized as a
mistake, though I suspect the offending man pages still exist.

Karl W. Z. Heuer (ima!haddock!karl or karl@haddock.isc.com), The Walking Lint

bright@Data-IO.COM (Walter Bright) (08/17/89)

In article <320@cubmol.BIO.COLUMBIA.EDU> ping@cubmol.UUCP (Shiping Zhang) writes:
>Many people said that after free()
>is called, the pointer used as the argument to free() is still valid
>and can be used IF NO (mc)alloc()'s are called after the call to free().

This is NOT true of all implementations of free(). Under Zortech C, the
size of a freelist entry (6 bytes) is larger than the size (2 bytes)
of the header of an allocated block. So when you free a block, the first
4 bytes of it get trashed.

A pox on anyone who writes code like:
	for (p = listhead; p; p = p->next)
		free(p);

Write it like:
	for (p = listhead; p; p = pn)
	{	pn = p->next;
		free(p);
	}

Program defensively. Always assume:
1. Calls to free() invalidate any pointers into that memory block.
2. realloc() always shifts the location of the block.

bill@twwells.com (T. William Wells) (08/17/89)

In article <3777@buengc.BU.EDU> bph@buengc.bu.edu (Blair P. Houghton) writes:
: In article <320@cubmol.BIO.COLUMBIA.EDU> ping@cubmol.UUCP (Shiping Zhang) writes:
: I guess the word is, once space is freed, don't even try to get data
: out of it.  It's risky at least, and nonportable without question.

Actually, things are worse than that: once the space is freed, don't
even *look* at the pointer. For example, the following code fragment
is nonportable:

	char    *ptr;

	ptr = malloc(1);
	...
	free(ptr);
	...
	if (ptr == 0) ...

The if might cause a trap when the value of ptr is accessed.

---
Bill                    { uunet | novavax | ankh | sunvice } !twwells!bill
bill@twwells.com

rcd@ico.ISC.COM (Dick Dunn) (08/18/89)

> : I guess the word is, once space is freed, don't even try to get data
> : out of it.  It's risky at least, and nonportable without question.

The definition of free() has changed - I think for the better, but suffice
it that old code may have assumptions of the old behavior.

In V7, Sys III, and BSD, the definition of free() explicitly provided that
the storage was released but would remain intact and in place until the
next malloc call.  I'm not sure just what all this might allow, but one
example is a slight simplification of freeing a singly-linked structure:
	for (p=head; *p; p=p->next)
		free(p);
without needing two pointers.  (Note that the increment picks the "next"
field out of a freed struct.)

In Sys V.3.2, the default behavior of free destroys the block of freed
storage, but it can be modified to have the old behavior for compatibility
reasons.  Such usage is, of course, discouraged.

ANSI C makes no provision for the old behavior.

Seems just as well to let it die.
-- 
Dick Dunn     rcd@ico.isc.com    uucp: {ncar,nbires}!ico!rcd     (303)449-2870
   ...Are you making this up as you go along?

rcd@ico.ISC.COM (Dick Dunn) (08/18/89)

bill@twwells.com (T. William Wells) writes:
> ...For example, the following code fragment is nonportable:
> 
> 	char    *ptr;
> 
> 	ptr = malloc(1);
> 	...
> 	free(ptr);
> 	...
> 	if (ptr == 0) ...
> 
> The if might cause a trap when the value of ptr is accessed.

Not true.  The "if" only examines the value of the pointer, not what it
points to.  free(ptr) does not modify ptr; it can't.  Assuming the malloc
succeeded (and assuming an appropriate in-scope declaration of malloc, if
you want to be fussy), the code as written will work, and the body of the
"if" will not be executed, since ptr will be non-null from the malloc.

The trouble will only begin when you try to look at *ptr.

shut up,
inews!
I
know
what
I'm
doing.
-- 
Dick Dunn     rcd@ico.isc.com    uucp: {ncar,nbires}!ico!rcd     (303)449-2870
   ...Are you making this up as you go along?

idall@augean.OZ (Ian Dall) (08/18/89)

In article <1989Aug17.005548.745@twwells.com> bill@twwells.com (T. William Wells) writes:
-In article <3777@buengc.BU.EDU> bph@buengc.bu.edu (Blair P. Houghton) writes:
-: In article <320@cubmol.BIO.COLUMBIA.EDU> ping@cubmol.UUCP (Shiping Zhang) writes:
-: I guess the word is, once space is freed, don't even try to get data
-: out of it.  It's risky at least, and nonportable without question.
-
-Actually, things are worse than that: once the space is freed, don't
-even *look* at the pointer. For example, the following code fragment
-is nonportable:
-
-	char    *ptr;
-
-	ptr = malloc(1);
-	...
-	free(ptr);
-	...
-	if (ptr == 0) ...
-
-The if might cause a trap when the value of ptr is accessed.

Well you should strictly do something like 

      if (ptr == (char *) 0) ...

If that causes a trap your compiler and/or operating system is
definitely broken. Ptr is just a local variable and you can compare it
with anything of the appropriate type (a pointer to a char). Free does
not (and cannot since C is call by value) alter the quantity in ptr.
Free DOES make *ptr meaningless but not ptr.

The compiler doesn't know what malloc and free do. For all the
compiler knows malloc and free could be functions you wrote with
completely different semantics.

-- 
 Ian Dall           life (n). A sexually transmitted disease which afflicts
                              some people more severely than others.
idall@augean.oz

jourdan@seti.inria.fr (jourdan martin joseph) (08/18/89)

In article <16022@vail.ICO.ISC.COM> rcd@ico.ISC.COM (Dick Dunn) writes:
>bill@twwells.com (T. William Wells) writes:
>> ...For example, the following code fragment is nonportable:
>> 
>> 	char    *ptr;
>> 
>> 	ptr = malloc(1);
>> 	...
>> 	free(ptr);
>> 	...
>> 	if (ptr == 0) ...
>> 
>> The if might cause a trap when the value of ptr is accessed.
>
>Not true.  The "if" only examines the value of the pointer, not what it
>points to.

You're wrong, Dick.  Someone else already pointed it out, but let me
make it clear again.  Imagine a segmented memory architecture with
protections, and imagine that the call to "free" above frees the last
used block in the segment and that "free" is clever enough to
determine it and decides to release the segment to the OS, thus making
the whole segment invalid (this is perfectly conceivable, and not
forbidden by any ``standard''; actually I'd love to see some
programs/processes decrease their memory size when they no longer use
it).  Then merely loading the value of "ptr" in a register to test it
against 0 will cause a invalid-address trap.

The morale of the whole story is: DO NOT DO ANYTHING WITH A FREED
POINTER.  To make things safer, each time I free a pointer variable,
the next statement is to copy some valid pointer value (usually NULL)
into it.  Of course, that does not solve the aliasing problem which
was the basis of the whole discussion, but that alleviates many
problems.

Martin Jourdan <jourdan@minos.inria.fr>, INRIA, Rocquencourt, France.
My employers have no opinion and they guarantee my freedom of expression.
-- 

Martin Jourdan <jourdan@minos.inria.fr>, INRIA, Rocquencourt, France.
My employers have no opinion and they guarantee my freedom of expression.

dfp@cbnewsl.ATT.COM (david.f.prosser) (08/18/89)

In article <16022@vail.ICO.ISC.COM> rcd@ico.ISC.COM (Dick Dunn) writes:
>bill@twwells.com (T. William Wells) writes:
>> ...For example, the following code fragment is nonportable:
>> 
>> 	ptr = malloc(1);
>> 	free(ptr);
>> 	if (ptr == 0) ...
>> 
>> The if might cause a trap when the value of ptr is accessed.
>
>Not true.  The "if" only examines the value of the pointer, not what it
>points to.  free(ptr) does not modify ptr; it can't.  Assuming the malloc
>succeeded (and assuming an appropriate in-scope declaration of malloc, if
>you want to be fussy), the code as written will work, and the body of the
>"if" will not be executed, since ptr will be non-null from the malloc.
>
>The trouble will only begin when you try to look at *ptr.

Not according to the pANS.

Section 4.10.3, page 155, line 20:

	The value of a pointer that refers to freed space is indeterminate.

From the definition of undefined behavior, section 1.6, page 3, lines 23-25:

	Undefined behavior -- behavior, upon use of a nonportable or erroneous
	program construct, of erroneous data, or of indeterminately-valued
	objects, for which the Standard imposes no requirements.

Thus, an architecture is free to trap on the above code.  This was a
deliberate choice of the Committee.

Dave Prosser	...not an official X3J11 answer...

karl@haddock.ima.isc.com (Karl Heuer) (08/19/89)

In article <568@augean.OZ> Ian Dall writes:
>In article <1989Aug17.005548.745@twwells.com> bill@twwells.com (T. William Wells) writes:
>>	free(ptr);
>>	if (ptr == 0) ...
>>The if might cause a trap when the value of ptr is accessed.

>Well you should strictly do something like
>      if (ptr == (char *) 0) ...

Only for style; the cast doesn't change the semantics.

>If that causes a trap your compiler and/or operating system is
>definitely broken.  Ptr is just a local variable ...

The issue is not whether the value of ptr has changed.  Some architectures
distinguish between arithmetic registers and address registers, and refuse to
allow arbitrary garbage to be loaded into an address register.  If free()
actually releases the segment to the operating system (I don't know of any
implementations that do, but the possibility is there), then the value of ptr,
although unchanged, could now be unloadable.  In other words, not only *ptr
but also ptr itself are unusable.

>The compiler doesn't know what malloc and free do. For all the
>compiler knows malloc and free could be functions you wrote with
>completely different semantics.

That's implementation-dependent.  For all you know, malloc() and free() are
builtins on my compiler.

Karl W. Z. Heuer (ima!haddock!karl or karl@haddock.isc.com), The Walking Lint

henry@utzoo.uucp (Henry Spencer) (08/19/89)

In article <16022@vail.ICO.ISC.COM> rcd@ico.ISC.COM (Dick Dunn) writes:
>> The if might cause a trap when the value of ptr is accessed.
>
>Not true.  The "if" only examines the value of the pointer, not what it
>points to.  free(ptr) does not modify ptr; it can't.  Assuming the malloc
>succeeded ... the code as written will work...
>The trouble will only begin when you try to look at *ptr.

Dick, can you cite chapter and verse in X3J11 saying that it is legal to
examine a pointer which points to freed storage?  If not, I would say that
you are making an implementation-dependent assumption.  On some machines,
pointers are very special animals, held in special registers that "know"
that their contents are pointers, and loading a random value into such a
register can cause trouble even if you don't dereference it.  Whether any
of them care about whether the pointer points to valid storage is another
question.  But it's not clear to me that such behavior is ruled out.
-- 
V7 /bin/mail source: 554 lines.|     Henry Spencer at U of Toronto Zoology
1989 X.400 specs: 2200+ pages. | uunet!attcan!utzoo!henry henry@zoo.toronto.edu

davidsen@sungod.crd.ge.com (ody) (08/19/89)

In article <1989Aug17.005548.745@twwells.com> bill@twwells.com (T. William Wells) writes:
| 
| 	char    *ptr;
| 
| 	ptr = malloc(1);
| 	...
| 	free(ptr);
| 	...
| 	if (ptr == 0) ...
| 
| The if might cause a trap when the value of ptr is accessed.

  As far as I can see you have to be able to test the value of a pointer
to see if it's valid in any implementation, and as long as you don't
dereference the pointer I don't see any portability problems.

  The test to see if ptr is zero (ie. NULL) would be better placed after
the malloc, before trying the free...
	bill davidsen		(davidsen@crdos1.crd.GE.COM)
  {uunet | philabs}!crdgw1!crdos1!davidsen
"Stupidity, like virtue, is its own reward" -me

blarson@basil.usc.edu (bob larson) (08/19/89)

In article <14343@haddock.ima.isc.com> karl@haddock.ima.isc.com (Karl Heuer) writes:
>If free()
>actually releases the segment to the operating system (I don't know of any
>implementations that do, but the possibility is there),

The Os9/68k free will, under certain circumstanses, realease the space
back to the operating system for use by other processes.

-- 
Bob Larson	Arpa:	blarson@basil.usc.edu
Uucp: {uunet,cit-vax}!usc!basil!blarson
Prime mailing list:	info-prime-request%ais1@usc.edu
			usc!ais1!info-prime-request

alo@kampi.hut.fi (Antti Louko) (08/19/89)

In article <14343@haddock.ima.isc.com> karl@haddock.ima.isc.com (Karl Heuer) writes:
<<In article <1989Aug17.005548.745@twwells.com> bill@twwells.com (T. William Wells) writes:
<<<	free(ptr);
<<<	if (ptr == 0) ...
<<<The if might cause a trap when the value of ptr is accessed.

<The issue is not whether the value of ptr has changed.  Some architectures
<distinguish between arithmetic registers and address registers, and refuse to
<allow arbitrary garbage to be loaded into an address register.  If free()
<actually releases the segment to the operating system (I don't know of any
<implementations that do, but the possibility is there), then the value of ptr,
<although unchanged, could now be unloadable.  In other words, not only *ptr
<but also ptr itself are unusable.

It would be interesting to see a C implementation like this in a
multitasking operating system. Let's consider this fragment of code:

p = malloc(..);
free(p);
.
. What if there happens a context switch here ??
.
p = malloc(..);

There might happen a context switch somewhere between free(p) and when
p is assigned a new legal value. Task switching code should check all
saved register values so that they wouldn't cause a trep. I think that
it would be much easier to just ignore this kind of traps than
identify just the legal traps.

	Antti Louko

rcd@ico.ISC.COM (Dick Dunn) (08/22/89)

In the midst of the pointer-to-freed-area stuff, idall@augean.OZ (Ian
Dall) writes:
> -	if (ptr == 0) ...
. . .
> Well you should strictly do something like 
> 
>       if (ptr == (char *) 0) ...

Will this *never* die?  How many times does it need to be said that you
don't have to cast 0 to compare it with a pointer?  (mutter, grumble...)

In the context of free(ptr) before the test...
> If that causes a trap your compiler and/or operating system is
> definitely broken...

No, NOT broken according to the standard.  If it causes a trap, you have
quite an unusual system (and I'd like to know what it is), but it's not
broken.  free(ptr) won't change the pointer, of course, but it is theo-
retically possible for it to change the validity of testing it.  (more to
follow on this...)
-- 
Dick Dunn     rcd@ico.isc.com    uucp: {ncar,nbires}!ico!rcd     (303)449-2870
   ...Are you making this up as you go along?

rcd@ico.ISC.COM (Dick Dunn) (08/22/89)

I had written...
[example stuff about freeing storage, then testing a pointer to it]
> >Not true.  The "if" only examines the value of the pointer, not what it
> >points to.  free(ptr) does not modify ptr; it can't.  Assuming the malloc
> >succeeded ... the code as written will work...
> >The trouble will only begin when you try to look at *ptr.

henry@utzoo.uucp (Henry Spencer) duly chastises me:
> Dick, can you cite chapter and verse in X3J11 saying that it is legal to
> examine a pointer which points to freed storage?  If not, I would say that
> you are making an implementation-dependent assumption...

I was making an implementation-dependent assumption.  Or, more to the
point, I was addressing practice rather than theory:  I haven't been able
to find any implementations of C in which freeing storage associated with a
pointer will make an equality test on that pointer--and particularly a test
for NULL--either cause a trap or produce an unexpected result.  I would be
very interested in knowing of such an implementation!

>...On some machines,
> pointers are very special animals, held in special registers that "know"
> that their contents are pointers, and loading a random value into such a
> register can cause trouble even if you don't dereference it...

Yes, but let's be specific.  Some folks (not Henry) have suggested to me
that the problem might occur on a 386.  It doesn't in any implementation
I know of; it wouldn't in any sane implementation because you'll only use
32-bit pointers (to avoid monstrous performance penalties) which are just
offsets and don't affect segments.  Try a 286, where you need a segment and
offset portion, but the problem still doesn't happen because you don't load
a segment register to test the segment portion of a pointer.  Loading a
segment register is incredibly expensive, and you can't test it anyway!  In
fact, this suggests a more general reason why the problem won't occur in
practice:  Placing a pointer in a "special register" which might cause a
trap implies that the pointer is somehow validated; that validation is
likely to be expensive and is unnecessary to the comparison.  Almost all
machines will allow an integer comparison here.  If the pointer is already
in a register, then the compiler (in conspiracy with the OS against the
hardware) must arrange that the register value not cause a trap--including
the case someone else mentioned, where a context switch occurs along the
way and it is necessary to restore the register.  Add to this that you have
to ensure that pointer values which represent one step off the end of an
array (of possibly large elements) are still representable and comparable,
and efficiency will probably force you to be quite generous in values which
will work in pointer comparisons.

>...Whether any
> of them care about whether the pointer points to valid storage is another
> question.  But it's not clear to me that such behavior is ruled out.

It's clear to me that such behavior IS allowed by the standard.  What I
want to know is whether it ever happens in practice.  I assert that it does
not--if only to get someone to tell me off!  (I collect counterexamples:-)
-- 
Dick Dunn     rcd@ico.isc.com    uucp: {ncar,nbires}!ico!rcd     (303)449-2870
   ...Are you making this up as you go along?

idall@augean.OZ (Ian Dall) (08/23/89)

In article <14343@haddock.ima.isc.com> karl@haddock.ima.isc.com (Karl Heuer) writes:
>In article <568@augean.OZ> Ian Dall writes:
>>In article <1989Aug17.005548.745@twwells.com> bill@twwells.com (T. William Wells) writes:
>>>	free(ptr);
>>>	if (ptr == 0) ...
>>>The if might cause a trap when the value of ptr is accessed.
>The issue is not whether the value of ptr has changed.  Some architectures
>distinguish between arithmetic registers and address registers, and refuse to
>allow arbitrary garbage to be loaded into an address register.

"free" or ANY function cannot change it's argument. "free" should not load
anything into "the special address register" (let alone "arbitrary garbage").

>>The compiler doesn't know what malloc and free do. For all the
>>compiler knows malloc and free could be functions you wrote with
>>completely different semantics.
>
>That's implementation-dependent.  For all you know, malloc() and free() are
>builtins on my compiler.

Your compiler can define any builtins it likes, but if they don't work like
real functions they haven't been done properly.

I don't have a copy of the ANSI standard, but surely any specification of the
behaviour of standard fuctions is to be read in conjunction with the defined
behaviour of ANY function. Which is call by value.


i
n
e
w
s

f
o
d
d
e
r



-- 
 Ian Dall           life (n). A sexually transmitted disease which afflicts
                              some people more severely than others.
idall@augean.oz

gwyn@smoke.BRL.MIL (Doug Gwyn) (08/23/89)

In article <572@augean.OZ> idall@augean.OZ (Ian Dall) writes:
>In article <14343@haddock.ima.isc.com> karl@haddock.ima.isc.com (Karl Heuer) writes:
>>The issue is not whether the value of ptr has changed.  Some architectures
>>distinguish between arithmetic registers and address registers, and refuse to
>>allow arbitrary garbage to be loaded into an address register.
>"free" or ANY function cannot change it's argument. "free" should not load
>anything into "the special address register" (let alone "arbitrary garbage").

No, Karl knows what he's talking about.  A formerly valid pointer value
can become invalid as a side-effect of free()'s operation.  The machine
could barf if you continue to access the now-invalid pointer value (NOT
just what it points at, but the pointer itself).  There are admittedly
not many architectures like this in common use, but if you're striving
for maximum portability you might as well be aware of the possibility.

scott@bbxeng.UUCP (Engineering) (08/23/89)

In article <572@augean.OZ>  writes:
>In article <14343@haddock.ima.isc.com> karl@haddock.ima.isc.com (Karl Heuer) writes:
>>In article <568@augean.OZ> Ian Dall writes:
>>>In article <1989Aug17.005548.745@twwells.com> bill@twwells.com (T. William Wells) writes:
>>>>	free(ptr);
>>>>	if (ptr == 0) ...
>>>>The if might cause a trap when the value of ptr is accessed.
>>The issue is not whether the value of ptr has changed.  Some architectures
>>distinguish between arithmetic registers and address registers, and refuse to
>>allow arbitrary garbage to be loaded into an address register.
>
>"free" or ANY function cannot change it's argument. "free" should not load
>anything into "the special address register" (let alone "arbitrary garbage").
>

What the earlier poster was trying to say is that the block of memory
pointed to by ptr might have been deallocated by free() and no longer
part of the process's memory map.  It is possible that the mere access
to ptr in the 'if' statement could cause a trap.  This seems unlikely to
me since one should be expected to be able to compare pointers without
worry.  What bugs me about the example is passing a pointer to free() and
*then* testing it.  If there was a possibility that ptr==0 then it should
be checked first.  A glance at the Xenix programmers reference manual
shows that there are *two* malloc libraries.  One of them destroys a
freed memory block while the other doesn't.  Both libraries say that
free() should only be passed a pointer returned by malloc().  Since
malloc() can return a 0 then it follows that free() will handle a 0
argument.  I personally prefer to avoid tempting Murphy's Law in these
situations.  It's too easy to get burned.

-- 

---------------------------------------
Scott Amspoker
Basis International, Albuquerque, NM
505-345-5232

kim@kim.misemi (Kim Letkeman) (08/25/89)

The original poster asked (essentially) what effect there would be if
he freed a block of memory and then tested the pointer.

This prompted a lively and interesting discussion filled with
speculation as to the possible effects under various hardware
architectures, especially those that treat pointers as "special
animals". 

No real consensus was reached, although most people seemed to feel
that it was unreasonable for C language to do anything as nasty as
trap the process because it "should not know anything about the
pointer's current status."

A couple of people asserted (correctly, as far as I am concerned) that
the run-time implementation was free to make assertions that a pointer
when accessed must be valid or a trap will occur. This is likely to
happen on architectures where pointers really are special, and not
just glorified integers.

Through all the smoke and gunfire I feel that the original poster's
intent was never clearly answered.

It is bad practise to release a block of memory back to the os and
then attempt to do anything at all with the pointer or the memory. Any
behaviour that is not totally random is implementation dependant and
is therefore not worth the risk.

Take a look at "The Elements of Progamming Style" and "Software Tools"
for a very clear explanation of good coding style as it pertains to
taking risks in code and/or sloppiness and/or laziness and/or anything
else that reduces code quality.

Kim

By the way ... anyone out there have any ideas as to how many
programmers have read these classics? I have asked a number of people
who make their living as software designers and the numbers are quite
shocking. I would say that less than 5 percent of programmers have
even heard of these two books. And well under 10 percent ever read a
software engineering book at all.

Kim Letkeman       uunet!mitel!spock!kim

-- 

Kim Letkeman         ...uunet!mitel!spock!kim

dolf@idca.tds.PHILIPS.nl (Dolf Grunbauer) (09/07/89)

In article <248@seti.inria.fr> jourdan@minos.inria.fr (Martin Jourdan) writes:
->In article <16022@vail.ICO.ISC.COM> rcd@ico.ISC.COM (Dick Dunn) writes:
->>bill@twwells.com (T. William Wells) writes:
->>> ...For example, the following code fragment is nonportable:
->>> 	char    *ptr;
->>> 	ptr = malloc(1);
->>> 	free(ptr);
->>> 	if (ptr == 0) ...
->>> The if might cause a trap when the value of ptr is accessed.
->>Not true.  The "if" only examines the value of the pointer, not what it
->>points to.
->You're wrong, Dick.  Someone else already pointed it out, but let me
->make it clear again.  Imagine a segmented memory architecture with
->protections, and imagine that the call to "free" above frees the last
->used block in the segment and that "free" is clever enough to
->determine it and decides to release the segment to the OS, thus making
->the whole segment invalid [...]
-> [...].  Then merely loading the value of "ptr" in a register to test it
->against 0 will cause a invalid-address trap.

I agree with Dick, as the original statement is:
   if (ptr == 0) ...
I think Martin explained why the statement:
   if (*ptr == 0) ...
is unacceptable and how the OS could trigger this illegal use.
Is this true or am I missing Martin's point ?
-- 
Dolf Grunbauer          Tel: +31 55 432764  Internet dolf@idca.tds.philips.nl
Philips Telecommunication and Data Systems  UUCP ....!mcvax!philapd!dolf
Dept. SSP, P.O. Box 245, 7300 AE Apeldoorn, The Netherlands

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/07/89)

In article <246@ssp1.idca.tds.philips.nl> dolf@idca.tds.PHILIPS.nl (Dolf Grunbauer) writes:
-I agree with Dick, as the original statement is:
-   if (ptr == 0) ...
-I think Martin explained why the statement:
-   if (*ptr == 0) ...
-is unacceptable and how the OS could trigger this illegal use.
-Is this true or am I missing Martin's point ?

No, Martin tried to explain why if(ptr==0) could fail after the free().
There aren't many common architectures where this would be a problem,
but it can happen.

Tim_CDC_Roberts@cup.portal.com (09/07/89)

From Dolf Grunbauer, Martin Jourdan, Dick Dunn and T. William Wells:
 
>>>> ...For example, the following code fragment is nonportable:
>>>>    char    *ptr;
>>>>    ptr = malloc(1);
>>>>    free(ptr);
>>>>    if (ptr == 0) ...
>>>> The if might cause a trap when the value of ptr is accessed.
>>>Not true.  The "if" only examines the value of the pointer, not what it
>>>points to.
>>You're wrong, Dick.  Someone else already pointed it out, but let me
>>make it clear again.  Imagine a segmented memory architecture with
>>protections, and imagine that the call to "free" above frees the last
>>used block in the segment and that "free" is clever enough to
>>determine it and decides to release the segment to the OS, thus making
>>the whole segment invalid [...]
>> [...].  Then merely loading the value of "ptr" in a register to test it
>>against 0 will cause a invalid-address trap.
>
> I agree with Dick, as the original statement is:
>    if (ptr == 0) ...
> I think Martin explained why the statement:
>    if (*ptr == 0) ...
> is unacceptable and how the OS could trigger this illegal use.
> Is this true or am I missing Martin's point ?
 
I know I'm going to be sorry for entering into this.
 
Isn't this a quality of implementation issue?  In any architecture where
simply placing a value into an address register causes a hardware
validation of that address (CDC Cyber 170, for example), there MUST be an
alternative method of manipulating arbitrary bit patterns without
restriction.  That is, it MUST be possible to load the value of an
invalid address (which some of the "arbitrary bit patterns" would be)
into a general-purpose register.  Thus, in the case above, I would
insist that the compiler writer move the address to a general-purpose
register and do the comparison there.  Any other behaviour I would document
as a bug.
 
In the CDC Cyber 180s, which use a segmented memory architecture very
similar to the one described in the test case, any bit pattern may be
loaded and manipulated in an address register; address, segment and
ring validation is not done until an attempt is made to deference the
value.

Tim_CDC_Roberts@cup.portal.com                | Control Data...
...!sun!portal!cup.portal.com!tim_cdc_roberts |   ...or it will control you.

karzes@mfci.UUCP (Tom Karzes) (09/08/89)

In article <248@seti.inria.fr> jourdan@minos.inria.fr (Martin Jourdan) writes:
}>> 	char    *ptr;
}>> 
}>> 	ptr = malloc(1);
}>> 	...
}>> 	free(ptr);
}>> 	...
}>> 	if (ptr == 0) ...
}>> 
}You're wrong, Dick.  Someone else already pointed it out, but let me
}make it clear again.  Imagine a segmented memory architecture with
}protections, and imagine that the call to "free" above frees the last
}used block in the segment and that "free" is clever enough to
}determine it and decides to release the segment to the OS, thus making
}the whole segment invalid (this is perfectly conceivable, and not
}forbidden by any ``standard''; actually I'd love to see some
}programs/processes decrease their memory size when they no longer use
}it).  Then merely loading the value of "ptr" in a register to test it
}against 0 will cause a invalid-address trap.

Nonsense.  There are no indirect or indexed references in the code above,
hence no opportunities for invalid address traps.  Period.  It might be
invalid to attempt to dereference ptr, e.g., "if (*ptr == 0) ...", but
no such attempt has been made in the code.  If malloc returns 0x1000,
then ptr contains 0x1000 and 0x1000 is a valid address.  After the
call to free, 0x1000 may no longer be a valid address, so *ptr may
cause an invalid address trap.  But ptr itself still contains 0x1000,
and may be accessed regardless of whether or not 0x1000 is mapped,
so long as 0x1000 is not dereferenced.

}The morale of the whole story is: DO NOT DO ANYTHING WITH A FREED
}POINTER.  To make things safer, each time I free a pointer variable,
}the next statement is to copy some valid pointer value (usually NULL)
}into it.  Of course, that does not solve the aliasing problem which
}was the basis of the whole discussion, but that alleviates many
}problems.

Sorry, but NULL is no more valid than the pointer returned by malloc.
You are not allowed to dereference NULL (many machines do allow it,
and return 0, but this merely fixes broken programs which do not
proprtly guard against NULL pointers).  The one thing you can count
on is that NULL will compare unequal to any valid address, whereas
your moldy old pointer may match some newly allocated address.  But
neither may be dereferenced.

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/08/89)

In article <1010@m3.mfci.UUCP> karzes@mfci.UUCP (Tom Karzes) writes:
>Nonsense.  There are no indirect or indexed references in the code above,
>hence no opportunities for invalid address traps.  Period.

Loading an unmapped address descriptor into an address register may
cause an exception on some reasonable architectures.  Exclamation point.

mcdaniel@astroatc.UUCP (Tim McDaniel) (09/08/89)

In article <1010@m3.mfci.UUCP> karzes@mfci.UUCP (Tom Karzes) writes:
> Nonsense.  There are no indirect or indexed references in the code
> above, hence no opportunities for invalid address traps.

*sigh*  It goes around and around, and people keep not understanding ... 
A machine is permitted (by pANS C) to signal an error in other cases
than dereferencing.

An anology: there are machines that have rigorous checks on
floating-point numbers.  For instance, if you use an IEEE
"not-a-number" (any of a number of possible bit patterns), it may
signal an error.  It may even fault if you just load the value into a
register.  Even if you don't use it in a computation, the system
"knows" that it couldn't possibly be a valid floating-point number,
and signals an error when you just try to load it or store it.

There are machines that work the same way with addresses.  For
example, it may determine that page 0xf350 is not in your address
space, so no pointer that starts with 0xf350 can possibly be valid.
Such machines can fault if you use the pointer IN ANY WAY --
dereference, of course, but ALSO if you perform pointer arithmetic, or
pointer comparison, or EVEN JUST COPY IT into or out of an address
register.  This implementation may be lousy, but it exists, and it
conforms with pANS C.  (At least, it's permitted to exist; I'm not
personally familiar with any such systems, but I heard that they do
exist.)

> Sorry, but NULL is no more valid than the pointer returned by malloc.

It is not valid to dereference NULL, but it is permitted to assign it
to a pointer variable and to compare it to a pointer.  It is invalid
to do these two things to a free()d pointer (although most systems
will actualy permit it).  In that sense, NULL is more valid than a
free()d pointer.
-- 

\    Tim McDaniel       "Tim, the Bizarre and Oddly-Dressed Enchanter"
 \   Internet: mcdaniel%astroatc.uucp@cs.wisc.edu
/ \  USENET:   ...!ucbvax!uwvax!astroatc!mcdaniel

ok@cs.mu.oz.au (Richard O'Keefe) (09/08/89)

In article <10971@smoke.BRL.MIL>, gwyn@smoke.BRL.MIL (Doug Gwyn) writes:
In article <246@ssp1.idca.tds.philips.nl> dolf@idca.tds.PHILIPS.nl (Dolf Grunbauer) writes:
> -I agree with Dick, as the original statement is:
> -   if (ptr == 0) ...

It is perfectly true that loading a (formerly valid, now invalid) address
into an address register might cause a trap.  BUT there is no reason why
a compiler has to translate "if (ptr == 0)" by loading ptr into an
address register.  Presumably, whatever bit pattern represents the NULL
pointer on such machines may _also_ cause a trap if loaded into an
address register (several versions of UNIX, for example, invalidate the
first page of a virtual memory precisely to catch dereferencing NULL),
so a compiler has to be prepared to handle "ptr = 0;" without loading an
invalid address into such a register.  It also has to be prepared to handle
"ptr2 = ptr1;" and "if (ptr2 == ptr1)" where ptr1 may be NULL (and thus
might cause a trap if loaded into an address register).  So what's so
special about other (pointer values that may not be dereferenced)?

chris@mimsy.UUCP (Chris Torek) (09/08/89)

In article <2054@munnari.oz.au> ok@cs.mu.oz.au (Richard O'Keefe) writes:
>It is perfectly true that loading a (formerly valid, now invalid) address
>into an address register might cause a trap.  BUT there is no reason why
>a compiler has to translate "if (ptr == 0)" by loading ptr into an
>address register.

True enough.  (Indeed, all `if (ptr0 <compare> ptr1)' operations could
be done without loading either pointer into an address register, on all
machines of which I have ever heard.)  The problem is that the proposed
ANSI standard does not force compiler writers to do this.

Since everyone seems to want an example of a system on which

	ptr = malloc(size);
	if (ptr != NULL) {
		free(ptr);
		if (ptr == NULL) ... never gets here ...
		else ... never gets here either! ...
	}

might be the case, perhaps we should think for a moment and construct
one (an example, not a system).

First, we need a relatively common architecture that traps when
loading an invalid address into an address register.  How about the
80286?  It has `address' registers called CS, DS, ES, and SS, and
these trap when a bad segment is loaded and the processor is in
protected mode.

Now we need a way to have an invalid address happen.  So:


	void *malloc(size_t size) {
		segment_t seg = __syscall(_GET_SEGMENT);

		if (seg == _NO_SEGMENT) return NULL;
		return _SEG_TO_ADDR(seg);
	}

	void free(void *p) {
		if (__syscall(_RELEASE_SEGMENT(_ADDR_TO_SEG(p))))
			__runtime_abort("bad argument to free(): %lx",
			    (long)p);
	}

Now all we need is a sufficiently stupid compiler.  That is not
hard to write.  We construct one that, for every pointer construct,
compiles to something like this:

	; do something with a pointer
	xor	ax,ax			; check for segment 0 => nil
	or	ax,[bp + stackoff + 2]
	jz	Lptr_was_nil		; it was nil, so do not load it
	mov	es,[bp + stackoff + 2]	; not nil, load it.
	mov	di,[bp + stackoff]
	; what are we doing?  `ptr == nil'?  Oh, we already did that.
	jump	Lptr_was_not_nil
 Lptr_was_nil:
	; code...

(Please ignore any assembly syntax errors above; I have never used an
80x86 for any value of x, except perhaps as embedded controllers, where
I have never had to program them.  My only experience with 8086
assembly comes from looking over the operating systems class
assignments back when they were using IBM PCs.  [Now they are using
PS-halves, er, PS/2s, for that class.]  But I will say that I think the
8085 was nicer, and the Z80 had more reasonable opcodes.  [Both of
these were also descendents of the 4004.])

The only question the proposed ANSI standard can answer about the above
is whether this is a conforming implementation, as far as the specification
goes.  That is, is it conforming even though it does in fact generate
a trap on

	ptr = malloc(size); free(ptr); if (ptr == (char *)0)

even though we are not looking at *ptr?

The only *answer* the proposed standard gives is silence.  That is,
it does not say that this implementation is non-conformant (i.e., wrong).
Thus we must conclude that it does conform, and that the trap is
legal according to the proposed standard.

Standards (proposed or otherwise) generally do not say anything about
implementation quality, but I would have to agree that the implementation
described above is horrible.  It is not, however, outright illegal.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

diamond@csl.sony.co.jp (Norman Diamond) (09/08/89)

bill@twwells.com (T. William Wells) writes:

>>> ...For example, the following code fragment is nonportable:
>>> 	char    *ptr;
>>> 	ptr = malloc(1);
>>> 	...
>>> 	free(ptr);
>>> 	...
>>> 	if (ptr == 0) ...
>>> The if might cause a trap when the value of ptr is accessed.

In article <16022@vail.ICO.ISC.COM> rcd@ico.ISC.COM (Dick Dunn) writes:

>>Not true.  The "if" only examines the value of the pointer, not what it
>>points to.

In article <248@seti.inria.fr> jourdan@minos.inria.fr (Martin Jourdan) writes:

>You're wrong, Dick....  Imagine a segmented memory architecture with
>protections, and imagine that the call to "free" above frees the last
>used block in the segment ...
>The morale of the whole story is: DO NOT DO ANYTHING WITH A FREED
>POINTER....

In fact the block cannot be freed by "free."  It can be freed by the
next malloc/calloc, and might be freed by the next realloc.  However,
if there are no *alloc calls between the free and the if, it has to
work.

Why?

Well, because a freed pointer is still valid in a call to realloc
(as long as there are no intervening *alloc calls), and the data still
have to be sitting in limbo, not really quite freed yet.  (Much to the
chagrin of efficiency experts everywhere.)

To cover all cases though:  IT IS MADNESS TO DO ANYTHING WITH A FREED
POINTER.

--
-- 
Norman Diamond, Sony Corporation (diamond@ws.sony.junet)
  The above opinions are inherited by your machine's init process (pid 1),
  after being disowned and orphaned.  However, if you see this at Waterloo or
  Anterior, then their administrators must have approved of these opinions.

barmar@think.COM (Barry Margolin) (09/08/89)

In article <2054@munnari.oz.au> ok@cs.mu.oz.au (Richard O'Keefe) writes:
>  Presumably, whatever bit pattern represents the NULL
>pointer on such machines may _also_ cause a trap if loaded into an
>address register

If an implementation compares pointers by loading them into address
registers, and loading the NULL pointer into an address register
causes a trap, then the implementation is not conforming.
Implementations are required to allow comparisons to NULL and
assignments of NULL to other variables.  They aren't, however,
required to allow comparisons to or assignments from pointers to freed
memory.

I agree that a processor that faults on loads (as opposed to
dereferences) of invalid pointers is brain damaged.  However, I recall
a processor that does this: the Honeywell DPS-88.  I used to be in the
Multics development group at Honeywell, and when we were investigating
porting Multics to this processor (a followon to the DPS-8, the
processor Multics currently runs on), we discovered this misfeature in
the new processor's design.  In all the previous architectures we had
been using a pointer into segment 07777 as the null pointer (the
segment table only has 2^12 entries, so any segment number with
non-zero high-order three bits is automatically invalid).  By the time
we discovered this flaw, I think it was too late to change the design.
(It turned out not to be a problem, because the porting project was
never undertaken.)

Why would an implementation want to do pointer comparisons and
assignments using address registers?  In some architectures, pointers
contain additional fields besides just the address, and the address
register data paths automatically take the address apart properly.
For instance, Multics pointers have a ring number field and several
bits called "fault tags", as well as some unused bits.  When comparing
address registers, the extra fields are automatically ignored.  There
are also two formats of pointers (one-word "packed" pointers that omit
the above fields, and two-word full pointers), with different
instructions for loading and storing each.  If the comparisons were
done using arithmetic registers lots of shifting and masking would be
necessary.  During assignments, the address registers automatically
perform various security checks on the ring number field (the ring
number loaded into the address register is forced to be the maximum of
the ring number of the pointer, the ring number of the segment it is
read from, and the current ring of execution).


Barry Margolin
Thinking Machines Corp.

barmar@think.com
{uunet,harvard}!think!barmar

dolf@idca.tds.PHILIPS.nl (Dolf Grunbauer) (09/08/89)

In article <2054@munnari.oz.au> ok@cs.mu.oz.au (Richard O'Keefe) writes:
>In article <10971@smoke.BRL.MIL>, gwyn@smoke.BRL.MIL (Doug Gwyn) writes:
>In article <246@ssp1.idca.tds.philips.nl> dolf@idca.tds.PHILIPS.nl (Dolf Grunbauer) writes:
>> -I agree with Dick, as the original statement is:
>> -   if (ptr == 0) ...
>It is perfectly true that loading a (formerly valid, now invalid) address
>into an address register might cause a trap.  BUT there is no reason why
>a compiler has to translate "if (ptr == 0)" by loading ptr into an
>address register.  Presumably, whatever bit pattern represents the NULL
>pointer on such machines may _also_ cause a trap if loaded into an
>address register (several versions of UNIX, for example, invalidate the
>first page of a virtual memory precisely to catch dereferencing NULL),
>so a compiler has to be prepared to handle "ptr = 0;" without loading an
>invalid address into such a register.  It also has to be prepared to handle
>"ptr2 = ptr1;" and "if (ptr2 == ptr1)" where ptr1 may be NULL (and thus
>might cause a trap if loaded into an address register).  So what's so
>special about other (pointer values that may not be dereferenced)?

What I don't understand from the answer of Richard is that according to
him the compiler takes care of these cases so where is our problem ?
Let me explain what I mean. The original posting asked why:
   ptr = malloc(1);
   free(ptr);
   if (ptr == 0)
is illegal or could cause (portability) problems. Now Richard states that
the compiler sovles the problem. Hence there is no problem. On the other
hand there are some people who state that the "if (ptr == 0)" is still
illegal (which I interpret now as: the compiler did not take care of this
case and the generated code raises some sort of a trap).

What about the case when ptr is already in a register
(i.e. definition of ptr: register char *ptr) ? Will there be an address trap
right after the free as some address register now holds an invalid address ?
By the way: what is the effect of the address trap: does the "if (ptr == 0)"
always evaluate to FALSE or is there a signal (SIGSEGV) ?
If so: how can I check in my program whether ptr is still valid (after all
that's why we had the "if (ptr == 0)" in the first place :-) ?
If "if (ptr == 0)" cases some sort of a trap or is illegal, is the expression
"if ((long)ptr == 0)" legal, as ptr will now be loaded in a data register
instead of an address register (assuming: sizeof(cahr *) == sizeof(long)) ?

A final question: how valid is this discussion ? Is there any CPU (commercial
available) which has this sort of address checking ? As far as I have read
from the net only such CPU's are hypothetical, c.f.
Doug Gwyn: (1) cause an exception on some reasonable architectures. 
           (2) There aren't many common architectures where this would be a
               problem, but it can happen.
Martin Jourdan: Imagine a segmented memory architecture with protections ...
-- 
Dolf Grunbauer          Tel: +31 55 432764  Internet dolf@idca.tds.philips.nl
Philips Telecommunication and Data Systems  UUCP ....!mcvax!philapd!dolf
Dept. SSP, P.O. Box 245, 7300 AE Apeldoorn, The Netherlands

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/08/89)

In article <2054@munnari.oz.au> ok@cs.mu.oz.au (Richard O'Keefe) writes:
>So what's so special about other (pointer values that may not be
>dereferenced)?

You'll never grasp what we're trying to say so long as you try to interpret
it in terms of conventional architectures, because we're talking about a
hardware trap that occurs NOT when an invalid pointer is used to REFERENCE
an object, by when the invalid pointer itself is merely INSPECTED.  This
occurs only for certain special architectures, such as "tagged" ones.  It
can be a useful software reliability feature when properly exploited, which
is why SWARD-like designs may well implement it.

You won't have the problem we're talking about on your VAX or Macintosh.

ok@cs.mu.oz.au (Richard O'Keefe) (09/08/89)

In article <247@ssp1.idca.tds.philips.nl>, dolf@idca.tds.PHILIPS.nl (Dolf Grunbauer) writes:
> In article <2054@munnari.oz.au> ok@cs.mu.oz.au (Richard O'Keefe) writes:
> >It is perfectly true that loading a (formerly valid, now invalid) address
> >into an address register might cause a trap.  BUT there is no reason why
> >a compiler has to translate "if (ptr == 0)" by loading ptr into an
> >address register.  [much deleted]

> What I don't understand from the answer of Richard is that according to
> him the compiler takes care of these cases so where is our problem ?

Eeek.  I didn't say that, not exactly.  What I was basically arguing is
that NULL is a similar special case, so that it is harder for a compiler
writer to foul comparison-and-assignment-of-not-necessarily-valid-pointers
up than to get it right.  Chris Torek showed how a really nasty compiler
could indeed make a special case of NULL and still foul everything else
up, and still be legal.  In the absence of a current draft of the
standard (repeatedly paying US$70+postage out of my own pocket does not
appeal to me) I can only take his word for it.

There are two questions which remain:

1.  there are machines on which it is _possible_ to foul up like this and
    still be legal.  But it is _advantageous_ on those machines?  That is,
    are there code sequences which
	(a) can handle ptr1==ptr2 where ptr1 or ptr2 is NULL and NULL
	    corresponds to an invalid address
	(b) can NOT handle ptr1==ptr2 where one of them used to be a valid
	    address but isn't just at the moment
	(c) are faster or more compact than sequences which do not trap
	    in this case?

    If the answer is "yes", then we're stuck.  But if the answer is "no",
    then there would be no harm to people writing compilers for machines
    with trapping-load-into-address-register if they were required to
    make ptr == NULL legitimate at all times if it ever was legitimate.

    I believe that it is technically possible to have a C system with a
    relocating garbage collector, so the standard couldn't require that
    other relationships which the pointer might once have had to other
    pointers should stay constant, but it does seem reasonable to keep
    ptr == NULL defined.

2.  Is it too late to put this requirement in the standard?  Was it
    _really_ intended that this case should be allowed to trap?

CCDN@levels.sait.edu.au (DAVID NEWALL) (09/08/89)

jourdan@seti.inria.fr (jourdan martin joseph) writes:
>In article <16022@vail.ICO.ISC.COM> rcd@ico.ISC.COM (Dick Dunn) writes:
>>bill@twwells.com (T. William Wells) writes:
>>>     char    *ptr;
>>>     ptr = malloc(1);
>>>     free(ptr);
>>>     if (ptr == 0) ...
>>>
>>> The if might cause a trap when the value of ptr is accessed.
>>
>>Not true.  The "if" only examines the value of the pointer, not what it
>>points to.
>
> merely loading the value of "ptr" in a register to test it against 0 will
> cause a invalid-address trap.

Why?  Loading the value of a pointer is not the same as dereferencing that
pointer?  I can't see why you should get an invalid-address trap.  In fact,
a machine that *did* trap in such a case, would be decidedly unfriendly.


David Newall                     Phone:  +61 8 343 3160
Unix Systems Programmer          Fax:    +61 8 349 6939
Academic Computing Service       E-mail: ccdn@levels.sait.oz.au
SA Institute of Technology       Post:   The Levels, South Australia, 5095

tneff@bfmny0.UU.NET (Tom Neff) (09/08/89)

In article <10973@smoke.BRL.MIL> gwyn@brl.arpa (Doug Gwyn) writes:
>In article <1010@m3.mfci.UUCP> karzes@mfci.UUCP (Tom Karzes) writes:
>>Nonsense.  There are no indirect or indexed references in the code above,
>>hence no opportunities for invalid address traps.  Period.
>
>Loading an unmapped address descriptor into an address register may
>cause an exception on some reasonable architectures.  Exclamation point.

For this to be true, NULL itself would have to be a "mapped address
descriptor" on those "reasonable architectures," or else you couldn't
even do the compare *before* the free() call.  But if NULL is a mapped
address descriptor, it beats me how you're going to trap dereferences.

I would like to hear one real world example of an architecture where it
can be illegal to compare the VALUE of a pointer variable to 0 after
passing the CONTENTS of the variable to some OS routine.  Also whether
there's a C compiler for that architecture. :-)
-- 
Annex Canada now!  We need the room,	\)	Tom Neff
    and who's going to stop us.		(\	tneff@bfmny0.UU.NET

rhg@cpsolv.UUCP (Richard H. Gumpertz) (09/08/89)

Re: loading the value of the pointer into a register to test it may cause a
fault ...

Seems to me that a compiler should avoid loading the pointer into a register
if that might cause a fault!  The pointer comparison context is fundamentally
different from the pointer indirection context; the former should be able to
work with invalid pointer values (at least when the comparison is to NULL)
while the latter may fault.

Surely the compiler implementor can find a code sequence that avoids faults
when comparing invalid pointers to NULL!  (For example, load the pointer into
an appropriately sized data register instead of an address register.)

By the way, the standard (3.3.9) lists all sorts of constraints on pointer
comparison of the form "operand is a pointer to an object" but then leaves off
"to an object" in the clause mentioning comparisons to NULL.  That may be
significant (although they may have only been allowing the pointer to be NULL
as opposed to invalid).

garys@bunker.UUCP (Gary M. Samuelson) (09/08/89)

In article <248@seti.inria.fr> jourdan@minos.inria.fr (Martin Jourdan) writes:
>Someone else already pointed it out, but let me
>make it clear again.  Imagine a segmented memory architecture with
>protections, and imagine that the call to "free" above frees the last
>used block in the segment and that "free" is clever enough to
>determine it and decides to release the segment to the OS, thus making
>the whole segment invalid (this is perfectly conceivable, and not
>forbidden by any ``standard''; actually I'd love to see some
>programs/processes decrease their memory size when they no longer use
>it).  Then merely loading the value of "ptr" in a register to test it
>against 0 will cause a invalid-address trap.

I find this very hard to believe.  Are you being hypothetical, or
is there a machine that really does this?  I can't think of any
justification for causing an invalid-address trap when some value
is put into a register.  The trap should only occur when an attempt
is made to actually use the invalid address.  Suppose I want to
prevent invalid address traps by examining the pointer -- how could
I determine if a pointer contained a valid address, if the trap
occurs as soon as I try to look at the pointer (never mind what it
points to)??

Gary Samuelson

scott@bbxeng.UUCP (Engineering) (09/08/89)

 In article <10973@smoke.BRL.MIL> gwyn@brl.arpa (Doug Gwyn) writes:
 >In article <1010@m3.mfci.UUCP> karzes@mfci.UUCP (Tom Karzes) writes:
 >>Nonsense.  There are no indirect or indexed references in the code above,
 >>hence no opportunities for invalid address traps.  Period.
 >
 >Loading an unmapped address descriptor into an address register may
 >cause an exception on some reasonable architectures.  Exclamation point.

In other words - you cannot even *test* a pointer unless you are *sure*
there is a valid address in it.  I don't think so.  Is NULL some kind
of special exception?  Before we waste anymore bandwidth with this topic,
does anybody actually know a specific architecture and C compiler combination
that would cause a problem on a pointer compare?



-- 

---------------------------------------
Scott Amspoker
Basis International, Albuquerque, NM
505-345-5232

barmar@think.COM (Barry Margolin) (09/08/89)

In article <247@ssp1.idca.tds.philips.nl> dolf@idca.tds.PHILIPS.nl (Dolf Grunbauer) writes:
>On the other
>hand there are some people who state that the "if (ptr == 0)" is still
>illegal (which I interpret now as: the compiler did not take care of this
>case and the generated code raises some sort of a trap).

It *could* raise a trap.  The standard doesn't define the behavior, so
anything is permitted.

>What about the case when ptr is already in a register
>(i.e. definition of ptr: register char *ptr) ? Will there be an address trap
>right after the free as some address register now holds an invalid address ?

An implementation that does this would not conform to the standard.
If some hardware does this, then the C implementation would have to
generate code to work around it.  I think such an implementation would
be extremely unlikely, though -- it's likely that the address has to
be loaded into a register in the process of making the system call
that frees the segment, so it would be an extremely bad idea to fault
in this case.

>By the way: what is the effect of the address trap: does the "if (ptr == 0)"
>always evaluate to FALSE or is there a signal (SIGSEGV) ?

As I said, the effect is undefined.  A conforming program should never
try to use a pointer variable if it doesn't know whether it is valid.

>If so: how can I check in my program whether ptr is still valid (after all
>that's why we had the "if (ptr == 0)" in the first place :-) ?

There's no portable way to check whether a pointer is valid.
Even if there were, comparing it to the null pointer probably wouldn't
be the way.

>If "if (ptr == 0)" cases some sort of a trap or is illegal, is the expression
>"if ((long)ptr == 0)" legal, as ptr will now be loaded in a data register
>instead of an address register (assuming: sizeof(cahr *) == sizeof(long)) ?

I doubt that it's valid.  The cast of the pointer variable might be
implemented using address operations.  Also, even if ptr were null,
there's no guarantee that ((long)ptr) == 0) is true; the result of
casting a pointer to an integer is implementation-dependent.

>A final question: how valid is this discussion ? Is there any CPU (commercial
>available) which has this sort of address checking ? 

I answered this in my posting last night.  The Honeywell DPS-88 checks
addresses during loads.

Barry Margolin
Thinking Machines Corp.

barmar@think.com
{uunet,harvard}!think!barmar

andyj@eriador.prime.com (09/09/89)

scott@bbxeng (Scott Amspoker) writes:
> In article <10973@smoke.BRL.MIL> gwyn@brl.arpa (Doug Gwyn) writes:
> >In article <1010@m3.mfci.UUCP> karzes@mfci.UUCP (Tom Karzes) writes:
> >>Nonsense.  There are no indirect or indexed references in the code above,
> >>hence no opportunities for invalid address traps.  Period.
> >
> >Loading an unmapped address descriptor into an address register may
> >cause an exception on some reasonable architectures.  Exclamation point.
>
>In other words - you cannot even *test* a pointer unless you are *sure*
>there is a valid address in it.  I don't think so.  Is NULL some kind
>of special exception?  Before we waste anymore bandwidth with this topic,
>does anybody actually know a specific architecture and C compiler combination
>that would cause a problem on a pointer compare?

Yes, the Prime 50-series architecture also must use special instructions to
load pointers.  The reasons are many:
  1.  The Prime is also a segmented architecture, and many segment addresses
      are not accessible to the user.
  2.  The Prime supports rings of protection.  While the ring bits are
      important in protecting access, they are not part of "the address" of
      the object, and must therefore be masked out.
  3.  The Prime allows for dynamic linking.  This is accomplished by creating
      a "faulted" pointer, which is resolved to the actual object at run-time
      (if it exists).  Thus, a pointer must be "resolved" to its destination
      before it can be used in any operation (assignment, comparision, or
      dereference).  Any comparison with the faulted pointer will yield an
      incorrect result.  Note that the compiler makes a special check for
      comparison or assignment with a null pointer constant, since a null 
      pointer would give an addressing failure if it went through the special
      instructions.

E. Andrew Johnson  (andyj@enx.prime.com)
Prime Computer, Inc.

ray@philmtl.philips.ca (Raymond Dunn) (09/09/89)

In article <10971@smoke.BRL.MIL> gwyn@brl.arpa (Doug Gwyn) writes:
>
>No, Martin tried to explain why if(ptr==0) could fail after the free().
>There aren't many common architectures where this would be a problem,
>but it can happen.

Perhaps it _can_ happen, but _would_ it.

Various posters have alluded to the use of ptr being a possible problem after
free(ptr).  On reflection, I don't believe any useful architecture could have
this restriction.  If the very fact of having a non-valid address in an address
register caused a violation, is it possible to implement free?

Is anyone aware of a real example of such a problem architecture, current or
historical?

On the assumption that such an architecture does exist however, wouldn't it
be trivial for its 'C' compiler to ensure that such an expression doesn't
cause a problem, by, for example, doing the test using data registers rather
than address registers (even though verbose code just might be necessary to
achieve that)?

Wouldn't the 'C' compiler be *expected* to do that?
-- 
Ray Dunn.                    | UUCP: ..!uunet!philmtl!ray
Philips Electronics Ltd.     | TEL : (514) 744-8200  Ext: 2347
600 Dr Frederik Philips Blvd | FAX : (514) 744-6455
St Laurent. Quebec.  H4M 2S9 | TLX : 05-824090

john@ontek.UUCP (John Stanley) (09/09/89)

In article <248@seti.inria.fr>, jourdan@seti.inria.fr (jourdan martin joseph) writes:
> The morale of the whole story is: DO NOT DO ANYTHING WITH A FREED
> POINTER.  To make things safer, each time I free a pointer variable,
> the next statement is to copy some valid pointer value (usually NULL)
> into it.  Of course, that does not solve the aliasing problem which
> was the basis of the whole discussion, but that alleviates many
> problems.

What I usually do is to NULL out the entire structure (or whatever) that is
being pointed at before freeing it.  This does not prevent an aliased access,
but it does increase the probability of it being noticed as soon as possible.

	 JAS

scott@bbxeng.UUCP (Engineering) (09/09/89)

In article <10817@riks.csl.sony.co.jp> diamond@riks. (Norman Diamond) writes:
>bill@twwells.com (T. William Wells) writes:
>
>
>To cover all cases though:  IT IS MADNESS TO DO ANYTHING WITH A FREED
>POINTER.
>

I figure it is time someone kind of summerized the different points
people have been making in this thread in order to reduce future
use of bandwidth on this topic.

1)  A free()'ed pointer may or may not be pointing to a valid memory
    location.

2)  It is possible that the freed memory block has been returned to the
    system and is no longer in the process's address map.

3)  Therefore, to de-reference such a pointer might cause a trap.

4)  However, on some architectures (i.e. 80286), to merely *load* an
    invalid address *without* de-referencing it could still cause a
    trap.  So, a simple compare operation on an invalid pointer is
    not always safe.

5)  However, a C compiler that attempts to load a segment register (or
    overly sensitive address register) in order to simply *compare*
    addresses is brain damaged.

6)  Therefore, we all agree to avoid those compilers thus driving the
    vendors out of business and we have nothing to worry about.

7)  A list of such vendors would be most welcome so we can begin our
    boycott.  :-)

How about it, folks?

-- 

---------------------------------------
Scott Amspoker
Basis International, Albuquerque, NM
505-345-5232

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/09/89)

In article <247@ssp1.idca.tds.philips.nl> dolf@idca.tds.PHILIPS.nl (Dolf Grunbauer) writes:
>What about the case when ptr is already in a register
>(i.e. definition of ptr: register char *ptr) ? Will there be an address trap
>right after the free as some address register now holds an invalid address ?

No, provided that you don't try to copy the now-defunct address into some
other variable or to use it in any other way.  Storing a valid address in
place of the defunct one would of course be okay.

>By the way: what is the effect of the address trap: does the "if (ptr == 0)"
>always evaluate to FALSE or is there a signal (SIGSEGV) ?

If there is a trap, in a UNIX environment a signal would be synchronously
posted for the process.  Which signal number is used is left up to the OS
implementor.  In other environments, other things might happen.
Undoubtedly it would not be something the programmer would WANT to happen.

>If so: how can I check in my program whether ptr is still valid (after all
>that's why we had the "if (ptr == 0)" in the first place :-) ?

You don't have to check.  You know that it will be invalid after free(ptr).

For other uses, you can set a boolean flag before the free(ptr) then rely
on the flag later to control further flow of execution.

>If "if (ptr == 0)" cases some sort of a trap or is illegal, is the expression
>"if ((long)ptr == 0)" legal, as ptr will now be loaded in a data register
>instead of an address register (assuming: sizeof(cahr *) == sizeof(long)) ?

No, reading the contents of ptr at all after free(ptr) is a non-portable
operation.  "Data" and "address" registers are mentioned merely in this
discussion of possible ways the operations might be implemented.  They
are not required to be implemented like this.

>A final question: how valid is this discussion ? Is there any CPU (commercial
>available) which has this sort of address checking ?

One of the Multics programmers mentioned a Honeywell machine like that.
I believe Intel's iAPX432 and IBM's System/38 architectures may support
implementations such as we've mentioned.

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/09/89)

In article <21952@cup.portal.com> Tim_CDC_Roberts@cup.portal.com writes:
>Isn't this a quality of implementation issue?  ...  Thus, in the case above,
>I would insist that the compiler writer move the address to a general-purpose
>register and do the comparison there.

It runs contrary to the "spirit of C" to require the implementation to
generate code that does operations the hard way when a more natural way
would suffice.

Personally, I would consider the "quality" issue as being in the opposite
sense:  Trapping on invalid address manipulation would be beneficial in
my view.

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/09/89)

In article <2059@munnari.oz.au> ok@cs.mu.oz.au (Richard O'Keefe) writes:
>1.  there are machines on which it is _possible_ to foul up like this and
>    still be legal.  But it is _advantageous_ on those machines?  That is,
>    are there code sequences which
>	(a) can handle ptr1==ptr2 where ptr1 or ptr2 is NULL and NULL
>	    corresponds to an invalid address

NULL should NOT be thought of as corresponding to ANY address, valid or
otherwise.  I've mentioned twice now in recent weeks how an implementor
can always implement null pointers in any environment where the rest of
C is implementable.  In my suggestion, a null pointer was always actually
a valid external (run-time library) object address; however in the general
case it is up to the implementor's ingenuity just how he represents and
operates with null pointers.

>	(b) can NOT handle ptr1==ptr2 where one of them used to be a valid
>	    address but isn't just at the moment

Yes, that's exactly what we've been talking about for over a week now.

>	(c) are faster or more compact than sequences which do not trap
>	    in this case?

Yes, in a trapping architecture, all pointer comparisons (for example)
would have to perform additional operations, assuming it were even
possible which it may not be, in the generated code in order to avoid
such a trap.

>2.  Is it too late to put this requirement in the standard?  Was it
>    _really_ intended that this case should be allowed to trap?

I believe it was intentional.  I know that members of X3J11 would
occasionally mention such architectures, and even more frequently
80*86 segmentation schemes, during discussions about pointer operations.

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/09/89)

In article <247@bbxeng.UUCP> scott@bbxeng.UUCP (Scott-Engineering) writes:
>In other words - you cannot even *test* a pointer unless you are *sure*
>there is a valid address in it.  I don't think so.

There is no way a strictly conforming C program can create an invalid
pointer value, apart from free()ing a valid one that it got from malloc()
or realloc().  The moment an improper pointer value is created via
arithmetic operations, the program has violated the guarantees of the
Standard, and the implementation would be free to trap immediately.

>Is NULL some kind of special exception?

Yes, as has been mentioned many times previously, null pointers (and null
pointer constants, not the same thing!) are special critters with their
own unique rules.

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/09/89)

In article <1465@levels.sait.edu.au> CCDN@levels.sait.edu.au (DAVID NEWALL) writes:
>a machine that *did* trap in such a case, would be decidedly unfriendly.

while letting you continue to play with pointers that point absolutely
nowhere would be friendly??

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/09/89)

In article <10817@riks.csl.sony.co.jp> diamond@riks. (Norman Diamond) writes:
>Well, because a freed pointer is still valid in a call to realloc
>(as long as there are no intervening *alloc calls), ...

Nope.  That guarantee that you've read in some MALLOC(3) UNIX manual
page, was documenting an arguably useful feature of a particular
implementation.  There have been malloc/free/realloc implementations
for many years that had no such guarantee, and X3J11 were not convinced
that there was sufficient need for such a guarantee to justify the
heavy implementation constraints imposed by it.  Therefore the Standard
makes realloc(free(ptr),size) explicitly undefined behavior.

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/09/89)

In article <10990@smoke.BRL.MIL> gwyn@brl.arpa (Doug Gwyn) writes:
>realloc(free(ptr),size) explicitly undefined behavior.

Well, actually realloc((free(ptr),ptr),size), but you knew what I meant.

chasm@attctc.Dallas.TX.US (Charles Marslett) (09/09/89)

In article <19474@mimsy.UUCP>, chris@mimsy.UUCP (Chris Torek) writes:
:: In article <2054@munnari.oz.au> ok@cs.mu.oz.au (Richard O'Keefe) writes:
:: >It is perfectly true that loading a (formerly valid, now invalid) address
:: >into an address register might cause a trap.  BUT there is no reason why
:: >a compiler has to translate "if (ptr == 0)" by loading ptr into an
:: >address register.
:: 
:: True enough.  (Indeed, all `if (ptr0 <compare> ptr1)' operations could
:: be done without loading either pointer into an address register, on all
:: machines of which I have ever heard.)  The problem is that the proposed
:: ANSI standard does not force compiler writers to do this.

It appears from an earlier posting that only certain very baroque architectures
could have this idiosyncracy (sp?). . .  Because one special invalid pointer
has to be acceptable in all expressions except dereferencing (NULL, that is),
the processor would have to have built-in NULL pointer detection, as well
as the normal protection mechanisms.  This is likely to have serious performance
penalties.

:: Since everyone seems to want an example of a system on which
:: 
:: 	ptr = malloc(size);
:: 	if (ptr != NULL) {
:: 		free(ptr);
:: 		if (ptr == NULL) ... never gets here ...
:: 		else ... never gets here either! ...
:: 	}
:: 
:: might be the case, perhaps we should think for a moment and construct
:: one (an example, not a system).
:: 
:: First, we need a relatively common architecture that traps when
:: loading an invalid address into an address register.  How about the
:: 80286?  It has `address' registers called CS, DS, ES, and SS, and
:: these trap when a bad segment is loaded and the processor is in
:: protected mode.

Except for the magic segment numbers 0x0000-0x0007, which can be loaded
into any segment register (maybe not CS?) and will not generate a trap.

:: Now we need a way to have an invalid address happen.  So:
:: 
:: 	void *malloc(size_t size) {
:: 		segment_t seg = __syscall(_GET_SEGMENT);
:: 		if (seg == _NO_SEGMENT) return NULL;
:: 		return _SEG_TO_ADDR(seg);
:: 	}
:: 
:: 	void free(void *p) {
:: 		if (__syscall(_RELEASE_SEGMENT(_ADDR_TO_SEG(p))))
:: 			__runtime_abort("bad argument to free(): %lx",
:: 			    (long)p);
:: 	}
:: 
:: Now all we need is a sufficiently stupid compiler.  That is not
:: hard to write.  We construct one that, for every pointer construct,
:: compiles to something like this:
:: 
:: 	; do something with a pointer
:: 	xor	ax,ax			; check for segment 0 => nil
:: 	or	ax,[bp + stackoff + 2]
:: 	jz	Lptr_was_nil		; it was nil, so do not load it
:: 	mov	es,[bp + stackoff + 2]	; not nil, load it.
:: 	mov	di,[bp + stackoff]
:: 	; what are we doing?  `ptr == nil'?  Oh, we already did that.
:: 	jump	Lptr_was_not_nil
::  Lptr_was_nil:
:: 	; code...
:: 
:: (Please ignore any assembly syntax errors above; I have never used an
:: 80x86 for any value of x, except perhaps as embedded controllers, where
:: I have never had to program them.  My only experience with 8086
:: assembly comes from looking over the operating systems class
:: assignments back when they were using IBM PCs.  [Now they are using
:: PS-halves, er, PS/2s, for that class.]  But I will say that I think the
:: 8085 was nicer, and the Z80 had more reasonable opcodes.  [Both of
:: these were also descendents of the 4004.])
:: 
:: The only question the proposed ANSI standard can answer about the above
:: is whether this is a conforming implementation, as far as the specification
:: goes.  That is, is it conforming even though it does in fact generate
:: a trap on
:: 
:: 	ptr = malloc(size); free(ptr); if (ptr == (char *)0)
:: 
:: even though we are not looking at *ptr?
:: 
:: The only *answer* the proposed standard gives is silence.  That is,
:: it does not say that this implementation is non-conformant (i.e., wrong).
:: Thus we must conclude that it does conform, and that the trap is
:: legal according to the proposed standard.

And it shows just how baroque the microcode in the 80286 really must be!
(Since all this code is really part of loading a segment register when
running in protected mode!)

:: Standards (proposed or otherwise) generally do not say anything about
:: implementation quality, but I would have to agree that the implementation
:: described above is horrible.  It is not, however, outright illegal.
:: -- 
:: In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
:: Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

I would have to say it is rather unlikely as well (or I would have said so
before I ran into Intel CPUs ;^).

Why don't we just say is it lousy looking code that does this sort of thing --
so don't do it (even though it will work 99999 times out of 10000, or whatever
the fraction really is.

BTW, I find the example code to be a good argument that referencing invalid
pointers ought to be legal (since without that ability, a "safe" malloc like
the one above, with reasonable error messages, becomes non-portable).

===========================================================================
Charles Marslett
STB Systems, Inc.  <== Apply all standard disclaimers
Wordmark Systems   <== No disclaimers required -- that's just me
chasm@attctc.dallas.tx.us

chasm@attctc.Dallas.TX.US (Charles Marslett) (09/09/89)

Now that we have seen code that can really generate a trap on (most) invalid
segments, and could have been generated by a (very bad) compiler, another
point has been raised: what about register variables?

If Chris Torek's pointer, passed to free(), were a register variable, and
presumably saved on the stack when the system routine free() was called,
it will be reloaded when the calling routine is resumed.  This would be a
VERY-BAD-THING!  And probably non-standard.

I think the probability of a standard-conforming compiler generating a trap
here, and not generating a trap under all legal conditions, is approaching
NULL ;^).

Of course, segment registers need not be used to hold register pointers,
but it makes more sense than using them as paths to the ALU.

Charles
===========================================================================
Charles Marslett
STB Systems, Inc.  <== Apply all standard disclaimers
Wordmark Systems   <== No disclaimers required -- that's just me
chasm@attctc.dallas.tx.us

chris@mimsy.UUCP (Chris Torek) (09/09/89)

In article <247@bbxeng.UUCP> scott@bbxeng.UUCP (Engineering) writes:
>In other words - you cannot even *test* a pointer unless you are *sure*
>there is a valid address in it.

More or less.

>I don't think so.

You could send comments to the next ANSI C standards committee asking that
this be changed.

>Is NULL some kind of special exception?

Yes.  Nil pointers have always been special exceptions on many machines.

>Before we waste anymore bandwidth with this topic,

Too late.

>does anybody actually know a specific architecture and C compiler combination
>that would cause a problem on a pointer compare?

No, no one knows of any such combination.  It is highly unlikely that
any such combination exists, or ever would be written, although I did
describe how to construct one quite recently.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

chris@mimsy.UUCP (Chris Torek) (09/09/89)

In article <10817@riks.csl.sony.co.jp> diamond@csl.sony.co.jp (Norman Diamond)
writes:
>... a freed pointer is still valid in a call to realloc
>(as long as there are no intervening *alloc calls), and the data still
>have to be sitting in limbo, not really quite freed yet.

This is a feature of certain Unix implementations, and is not true in
general.  (It is even false in some Unix implementations.  It was, more
or less, a bit of sloppiness that got documented and hence labelled
Official, and then it was too late to fix it painlessly.)
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/09/89)

In article <9278@attctc.Dallas.TX.US> chasm@attctc.Dallas.TX.US (Charles Marslett) writes:
>Because one special invalid pointer has to be acceptable in all expressions
>except dereferencing (NULL, that is), ...

No!  Null pointers do NOT have to be represented by invalid addresses.
They merely have to be distinct from any address of valid objects that
a strictly conforming C program could access.  There are no performance
penalties required to implement null pointers even in an architecture
that traps on loading invalid addresses.

>Why don't we just say is it lousy looking code that does this sort of thing --
>so don't do it (even though it will work 99999 times out of 10000, or whatever
>the fraction really is.

Good advice; since it is not necessary to rely on the nonportable
ability to continue to play with a pointer after it is free()d,
it is best to simply avoid doing so.

scs@hstbme.mit.edu (Steve Summit) (09/09/89)

In articles too numerous to mention, hordes of people get badly
upset at the prospect that

	p = malloc(size);
	free(p);
	if(p == NULL)
		...

might cause ugly, undefined behavior such as traps or exceptions.

Look, folks, there are only two valid ways of obtaining valid
pointers in C:

	1. take the address of an object
	2. use malloc()

(well, you can also perform valid arithmetic on already-valid
pointers).  But the line

	if(ptr == NULL)

would not "blow up" for just any values of ptr; it could only
blow up for invalid values of ptr.  The only way for a
previously-valid pointer to become invalid is to pass it to free.
(Objects don't move around; pointers to objects stay valid.)
Having passed a pointer to free, there's no reason to be doing
anything more with it (comparing it to NULL, or anything else).

In article <2059@munnari.oz.au> ok@cs.mu.oz.au (Richard O'Keefe) writes:
>    are there code sequences which
>	(b) can NOT handle ptr1==ptr2 where one of them used to be a valid
>	    address but isn't just at the moment

Would it bother you if there were?  When would such a situation
realistically come up?  It's not every pointer operation in a
program that becomes suspect under this stricter interpretation;
it's only any (meaningless) operations on pointers after calling
free().  You shouldn't be doing anything with a freed pointer,
other than perhaps setting it to NULL to remind yourself that
you've freed it.

You can, of course, also get an invalid pointer by using an
uninitialized automatic pointer variable, or by doing invalid
pointer arithmetic, but I hope nobody wants those cases to
"work."  (As has been suggested, having the hardware trap such
invalid uses would aid development of correct software.)

I'd say that, if you were using this hypothetical machine that
invalidated pointers upon free and that could later trap
operations (not necessarily dereferencing) on the now-invalid
pointer, you would never notice it (unless your code was already
badly broken.)  I doubt you'd be tempted to boycott the authors
of such compilers or the vendors of such machines (unless there
were mere inefficiencies introduced by, say, allowing comparisons
against NULL pointers while disallowing comparisons against
completely invalid pointers, but that's another story).

Richard mentioned garbage collection; that's a good example of a
situation in which an (avowedly nonportable) program can make
good use of undefined pointer comparisons.  However, even if you
thought your garbage collector was "semi portable," it certainly
wouldn't have worked, without modification, on the sorts of
segmented architectures that are likely to disallow invalid
pointer operations.

                                                Steve Summit

bill@twwells.com (T. William Wells) (09/09/89)

In article <10817@riks.csl.sony.co.jp> diamond@riks. (Norman Diamond) writes:
: Well, because a freed pointer is still valid in a call to realloc
: (as long as there are no intervening *alloc calls), and the data still
: have to be sitting in limbo, not really quite freed yet.  (Much to the
: chagrin of efficiency experts everywhere.)

In the latest versions of Unix, realloc is not guaranteed to work on a
freed pointer.

Moreover, my SysV has a version of malloc that *does* trash the
memory on free.

---
Bill                    { uunet | novavax | ankh | sunvice } !twwells!bill
bill@twwells.com

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/09/89)

In article <549@ontek.UUCP> john@ontek.UUCP (John Stanley) writes:
>What I usually do is to NULL out the entire structure (or whatever) that is
>being pointed at before freeing it.  This does not prevent an aliased access,
>but it does increase the probability of it being noticed as soon as possible.

We also did this in our humongous project, in debugging mode
(which also turns on very extensive validity checks throughout
the code).  It turned out that changing the "zap" to fill the
blocks with all ON bits caught several bugs that zapping the
blocks to all OFF bits (i.e. 0) didn't.  I'm sure the reverse
would have been true too if we'd done it in the opposite order.

It may be worth mentioning that the sensible way to do this
stuff is in a package of library routines that all your projects
use.  That way it happens globally and doesn't require a lot of
extra typing on the coder's part.

ok@cs.mu.oz.au (Richard O'Keefe) (09/09/89)

In article <2054@munnari.oz.au> ok@cs.mu.oz.au I wrote
>  Presumably, whatever bit pattern represents the NULL
>pointer on such machines may _also_ cause a trap if loaded into an
>address register

In article <29011@news.Think.COM>, barmar@think.COM (Barry Margolin) writes:
> Why would an implementation want to do pointer comparisons and
> assignments using address registers?  In some architectures, pointers
> contain additional fields besides just the address, and the address
> register data paths automatically take the address apart properly.
> For instance, Multics pointers have a ring number field and several
> bits called "fault tags", as well as some unused bits.  When comparing
> address registers, the extra fields are automatically ignored.  There
> are also two formats of pointers (one-word "packed" pointers that omit
> the above fields, and two-word full pointers), with different
> instructions for loading and storing each.  If the comparisons were
> done using arithmetic registers lots of shifting and masking would be
> necessary.

The PR1ME 50-series machines are surprisingly similar.  (I wonder if some
ex-MULTICS people had something to do with that design?)  A pointer has
    -  1 "validity" bit
    -  1 "length" bit (0 -> 32 bit, 1 -> 48 bit)
    -  2 "ring" bits (0, 1, and 3=user, as I recall)
    - 12 "segment" bits (four quarter-spaces; 1 OS, 1 shared libraries,
	 1 user stuff, 1 CLI & per-process OS info)
    - 16 "word offset within segment" bits
and if the length bit was 1, there is another 16-bit word with
    -  1 "byte within word" bit.
The extension word nominally had 4 "bit within word" bits, but there
were no instructions for bit addressing on the PR1ME 400 I used.

    Barry Margolin raised a point I hadn't thought of:  there is a
distinction between "these pointers point to the same place", and
"these are the *same* pointer".  A 32-bit pointer and a 48-bit pointer
with 0 extension word on the P400 would be different pointers, but they
could point to the same place.

    This leads me to wonder:  if a compiler sees a statement like
	if (x == y) { .... use x .... }
then when x and y belong to the same *numeric* type and neither x nor y
is changed, it is entitled to translate the statement _as if_ it had been
	if (x == y) { .... use y .... }
This can be handy if y is in a register and x isn't.  Since the conceptual
model for a C pointer is (the identity of an array, an index within the
array), I assume that x == y in C means "x and y point to the same place".

1.  Does the current draft actually say this?

If that is so, then a C compiler which exploits x == y to use y where x
was written is _not_ guaranteed to preserve things like a ring number,
which could be Bad News for people trying to write operating system
kernels in C.  In user-level code on a PR1ME 50 series machine it makes
no difference because the ring is always weakened to an "effective" 3
in user mode anyway, but even in user mode it might make a considerable
difference if the validity bit were ignored in pointer comparison, yet
the "equal if point to same place" semantics would seem to permit that.

2.  According to the current draft, are C compilers allowed to use
    y instead of x if x == y and it is known that x and y cannot have
    changed since that was established?

If, in order to avoid the problem of misteriously losing access rights,
getting pointers with the invalid bit on, and so on, compiler writers
adopt the "same pointer" semantics rather than the "same place" semantics,
the argument for doing pointer comparison in address registers goes away.

Perhaps the answer is that compiler writers for such machines ought to
have a local _SamePtr(x,y) test, and should do optimisation relative
to that rather than relative to x==y.  _SamePtr() should not be in the
standard, of course, but it's worth having a clear comment about this
somewhere in the Rationale.

ok@cs.mu.oz.au (Richard O'Keefe) (09/09/89)

In article <2054@munnari.oz.au> ok@cs.mu.oz.au I wrote
>So what's so special about other (pointer values that may not be
>dereferenced)?
In article <10976@smoke.BRL.MIL>, gwyn@smoke.BRL.MIL (Doug Gwyn) writes:

> You'll never grasp what we're trying to say so long as you try to interpret
> it in terms of conventional architectures, because we're talking about a
> hardware trap that occurs NOT when an invalid pointer is used to REFERENCE
> an object, by when the invalid pointer itself is merely INSPECTED.

Don't try to guess how I'm interpreting things, because you guess wrong.
I understand the source of the problem perfectly well.  The problem is
that some machines have *some* ways of inspecting pointers which cause
traps when given an invalid pointer.  I was mistaken about NULL, that's
true:  NULL has to be a value that can't point to a valid C object,
but that doesn't mean that it has to be an invalid address.  Having a
special "null_obj" in the library that NULL points to will work fine on
those machines without requiring any special magic, so my argument fails.
The point remains that on those machines it is not *NECESSARY* to do a
pointer comparison using load-into-address-register-and-maybe-trap.
Barry Margolin explained lucidly why it may be *CONVENIENT*.

Even a machine like the B6700 (Unisys A series) with its tags provided
means of inspecting tagged words without causing the tags to be interpreted.
(Implementing BCPL on those machines was sufficient of a nightmare -- not
mine, thank goodness -- that you'd have to be crazy to try to implement C.)

Can a capability-based machine support a C implementation at all?
Might that provide an example where a pointer can't be accessed as data
(thus making pointer-comparison-via-trapping-methods obligatory)?
What would that do to pointer subtraction, pointers in unions, and so on?

ok@cs.mu.oz.au (Richard O'Keefe) (09/09/89)

In article <10984@smoke.BRL.MIL>, gwyn@smoke.BRL.MIL (Doug Gwyn) writes:
> In article <2059@munnari.oz.au> ok@cs.mu.oz.au (Richard O'Keefe) writes:
> >    Are there code sequences on trapping machiens which
> >	(a) can handle ptr1==ptr2 where ptr1 or ptr2 is NULL and NULL
> >	    corresponds to an invalid address

> NULL should NOT be thought of as corresponding to ANY address, valid or
> otherwise.  I've mentioned twice now in recent weeks how an implementor
> can always implement null pointers in any environment where the rest of
> C is implementable.

Irrelevant.  My question explicitly pertains to implementations where
NULL is mapped to an invalid address.  Other implementations do whatever
they do; I wasn't asking about them.

> >	(b) can NOT handle ptr1==ptr2 where one of them used to be a valid
> >	    address but isn't just at the moment

> Yes, that's exactly what we've been talking about for over a week now.

NO, I was asking are there code sequences for which (a) and (b) and (c)
are *ALL* true *TOGETHER*.  Yes, we have been talking about machines which
have code sequences where (b) is true.  I am asking are there code
sequences on those machines where (a) the implementor chose to make
dereferencing NULL trap, but in such a way that ptr1==ptr2 doesn't trap
on NULLs *AND* (b) the implementor chose to make comparing other invalid
addresses trap *AND* (c) under the constraint that the invalid address
chosen for NULL doesn't trap but others do, those code sequences are
faster than ones that don't trap at all.

> >	(c) are faster or more compact than sequences which do not trap
> >	    in this case?

> Yes, in a trapping architecture, all pointer comparisons (for example)
> would have to perform additional operations

On an 80*86, it is _possible_ to compare pointers using the data
[e]{a,b,c,d}x registers rather than the segment registers.  It's been a
while since I hacked 80386 code, and I've never hacked an 80286 at all,
and segment registers are not quite the same as address registers.  My
recollection is that a full pointer comparison on an 80286 would be the
same number of instructions whether you used segment registers or not,
and would be significantly faster if you didn't (precisely because you
would bypass the checking).  [I've heard stories about programs running
much slower on 80286s than 8086s because they put things into segment
registers.]  It would be helpful if someone who knows the answer for 80*86s
would post it.  

chris@mimsy.UUCP (Chris Torek) (09/09/89)

In article <2065@munnari.oz.au> ok@cs.mu.oz.au (Richard O'Keefe) writes:
>Can a capability-based machine support a C implementation at all?
>Might that provide an example where a pointer can't be accessed as data
>(thus making pointer-comparison-via-trapping-methods obligatory)?
>What would that do to pointer subtraction, pointers in unions, and so on?

Some Lisp machines implement pointers as <array, size, index> triples.
The code for `*p = 3', on such a machine, is (in pseudo-assembly)

	load	reg1,p+8	| get index
	load	reg2,p+4	| get size
	compare	reg1,reg2
	jltu	ok		| if (unsigned)index < size, is OK
	load	reg1,<"invalid pointer reference">
	jump	runtime_error_abort
ok:	load	reg2,p+0	| get array
	store	#3,reg2[reg1]	| array[index] = 3
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

shaw@paralogics.UUCP (Guy Shaw) (09/09/89)

In article <11008@smoke.BRL.MIL>, gwyn@smoke.BRL.MIL (Doug Gwyn) writes:
> In article <9278@attctc.Dallas.TX.US> chasm@attctc.Dallas.TX.US (Charles Marslett) writes:

> >Why don't we just say is it lousy looking code that does this sort of thing --
> >so don't do it (even though it will work 99999 times out of 10000, or whatever
> >the fraction really is.
> 
> Good advice; since it is not necessary to rely on the nonportable
> ability to continue to play with a pointer after it is free()d,
> it is best to simply avoid doing so.

Do any of the C development/run-time environments, such as Sabre-C,
have run-time pointer checking code that is as strict as the kinds
of hardware that has been discussed here recently?  I know there are
C interpreters or run-time environments that will check array bounds
and will take care of dereferencing bad pointers, even when the usual
run-time code would just keep slashing away and surprise you later,
on many machines.  But, are there any that can simulate the the kind
of hardware that says, "don't even think about bad pointers"?

I don't have any objection to coding safely, but I don't trust myself
to catch every potential portability problem, by hand.  I don't think
lint could catch this.  As a matter of fact, I can't think of a way
to make some new version of lint catch this in ALL cases.  Offhand,
it seems that generating code to do this at run time is the only way
to get that kind of enforcement.  Perhaps, another cc switch.
Yes, I know, that does not prove your program doesn't have bugs
in unexercised parts of your program, but neither does running it on
strict hardware.

I don't want to buy a Unisys A-series machine (or whatever) just
to run my code to make sure it is maximally portable.  But if I
had the option handy in software, I would use it to supplement lint.

Thanks in advance.
-- 
Guy Shaw
Paralogics
paralogics!shaw@uunet.uu.net  or  uunet!paralogics!shaw

barmar@think.COM (Barry Margolin) (09/10/89)

In article <172@cpsolv.UUCP> rhg@cpsolv.uucp (Richard H. Gumpertz) writes:
>The pointer comparison context is fundamentally
>different from the pointer indirection context; the former should be able to
>work with invalid pointer values (at least when the comparison is to NULL)
>while the latter may fault.

Why do you feel that comparing an invalid pointer to NULL is some sort
of special case?  If it were required not to fault, it would also
probably not be equal to NULL.  I imagine that code that begins with
"if (ptr != NULL)" is likely to go on to dereference the pointer (why
else would it bother checking the nullness?).  So what's the point of
making the comparison valid?

What may be needed to satisfy everyone is a built-in operation that
tells whether a pointer is valid to use.  It would have to be an
operator, not a function, so that it could get around the invalidity
of accessing freed pointers.  The definition of it would have to be
loose enough to permit efficient implementation on systems without
memory protection hardware (you don't want it running through malloc's
data structures to see whether it points into an allocated or free
block).


Barry Margolin
Thinking Machines Corp.

barmar@think.com
{uunet,harvard}!think!barmar

barmar@think.COM (Barry Margolin) (09/10/89)

In article <2069@munnari.oz.au> ok@cs.mu.oz.au (Richard O'Keefe) writes:
>I am asking are there code
>sequences on those machines where (a) the implementor chose to make
>dereferencing NULL trap, but in such a way that ptr1==ptr2 doesn't trap
>on NULLs *AND* (b) the implementor chose to make comparing other invalid
>addresses trap *AND* (c) under the constraint that the invalid address
>chosen for NULL doesn't trap but others do, those code sequences are
>faster than ones that don't trap at all.

I was almost going to answer that I couldn't think of any, but then I
thought of an easy way to implement it.  The machine is one where
loading invalid pointers causes a trap, and where pages or segments
have access modes.  The null pointer would then be implemented as a
valid pointer to a page whose read and write bits were both off.
Loading the null pointer into an address register would not cause a
fault, but indirecting through it would cause a protection violation.

Barry Margolin
Thinking Machines Corp.

barmar@think.com
{uunet,harvard}!think!barmar

evans@ditsyda.oz (Bruce Evans) (09/10/89)

In article <10982@smoke.BRL.MIL> gwyn@brl.arpa (Doug Gwyn) writes:
>In article <247@ssp1.idca.tds.philips.nl> dolf@idca.tds.PHILIPS.nl (Dolf Grunbauer) writes:
>>What about the case when ptr is already in a register
>>(i.e. definition of ptr: register char *ptr) ? Will there be an address trap
>>right after the free as some address register now holds an invalid address ?
>
>No, provided that you don't try to copy the now-defunct address into some
>other variable or to use it in any other way.  Storing a valid address in

	register char *ptr;
	...
	free(ptr);

The compiler will have difficulty maintaining the register across the function
call. It might have to forbid pointers in registers, or they might not fit.
A loss either way.
-- 
Bruce Evans		evans@ditsyda.oz.au

ok@cs.mu.oz.au (Richard O'Keefe) (09/10/89)

In article <14177@bloom-beacon.MIT.EDU>, scs@hstbme.mit.edu (Steve Summit) writes:
> In article <2059@munnari.oz.au> ok@cs.mu.oz.au (Richard O'Keefe) writes:
> >    are there code sequences which
> >	(b) can NOT handle ptr1==ptr2 where one of them used to be a valid
> >	    address but isn't just at the moment

One more time:  the great names of comp.lang.c have clearly demonstrated
that there *are*.  That wasn't my question.  My question was (a) *AND*
(b) *AND* (c).  When someone asks whether there is a machine on which
(a) and (b) and (c) don't isolate one of the points as if that was the
whole question.

> In articles too numerous to mention, hordes of people get badly
> upset at the prospect that
> 	p = malloc(size);
> 	free(p);
> 	if (p == NULL)
> might cause ugly, undefined behavior such as traps or exceptions.

Yes, we do.  Before the call to free() we were guaranteed that p was
not NULL.  We had no guarantee about other ordering relations (it was
not defined whether p > NULL, for example), but we _were_ guaranteed
that p == NULL was defined, meaningful, and false.  Furthermore, the
free() procedure has no access to the variable p.  One normally
expects as a matter of course that
	if <expression> is defined and has value <v> here,
	and nothing here has access to any of the variables
	which occur in <expression>,
	then <expression> is still defined and still has value <v> here.
It's as if
	p = 1;
	printf("%d\n", p);
	if (p == 0)
were allowed to blow up.  There are languages (such as Euclid) where this
analogy is precise.  (Yes, I know that C is not Euclid.)  I don't know
about other people.  My perspective is that if a language violates such
an obvious invariance property, I am afraid to use it because I don't
know what other obvious properties it may break.  This particular property
is one which is very important to optimising compilers.  I don't expect to
write one, but I _do_ expect that C compilers I use in the future will have
optimisers which assume this invariance property, and it won't be true.

There is of course the correspondence to IEEE signalling NaNs and
VAX Invalid Operands, but that analogy fails, because those things are
_born_ invalid; you can't get one in the first place without using
machine-specific code.

I can think of several reasonable applications for testing p==NULL after
a call to free(p).  For each of them I can see how to do it safely another
way.  But then any program can be rewritten with one while(), one switch(),
and no gotos or user-defined procedures.

Let's have a bit of a think about the hardware issue.  We would like
free() to be able to release a segment to the operating system, so that
dereferencing it is not legal, but because the address was once legal
we would rather not have future _loads_ trap.  Is this doable?  It depends
on the MMU.  A Page Map Entry on the ELXSI, for example, has three
accessRights bits: readAllowed, writeAllowed, and executeAllowed; these
bits are independent of other bits like inResidentSet, inPhysicalMemory,
and so on.  If I were designing a C system for that machine, I would take
one page of physical memory, and map page 0 of the virtual address space
to that page.  The Page Map Entry for page 0 would refer to a physically
present page, but with no access rights.  Pages which were given back to
the operating system I would treat just the same: there would be a page
map entry indicating that the page existed in the address space but that
the program had no rights to it.  One physical page will serve for all
such "holes" in the page map.  If the ELXSI were to fault when pointer
to a page not in the user's address space were loaded, that would be fine;
a free()d pointer would continue to be valid in _that_ sense, but any
attempt to dereference it would be invalid, and the operating system would
be able to tell whether the page was _really_ there or not by checking
whether it was mapped to the one reserved page.

The problem with address loads trapping is not a hardware problem; it is a
hardware/OS problem.  I have outlined one approach which requires some OS
cooperation, but if the standard required the usual invariance property to
hold, OS vendors just might see an advantage in making C implementation
straightforward.  In the absence of such support, there is no reason why a
C implementation _has_ to give a free()d page or segment back to the 
operating system, thus invalidating the pointer.  I would expect that a
good run-time library would wait for a bit in case the space was needed
again soon.

Give me more information about particular machines, and I'll try to come
up with implementation strategies that don't require the standard to
break one of the few optimiser invariants C _was_ thought to possess.

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/10/89)

In article <29110@news.Think.COM> barmar@think.COM (Barry Margolin) writes:
>What may be needed to satisfy everyone is a built-in operation that
>tells whether a pointer is valid to use.

My opinion is that if the programmer doesn't know that without applying
such a test, his code is out of control.

You might think that such a test would be useful in implementing
library routines, where the implementor has no control over the
user of his function and wants to "be gentle" when it is abused.
However, just because a pointer is valid does not mean that the
caller is using the function correctly.  So I think that even in
this case it would be of limited utility.

peter@ficc.uu.net (Peter da Silva) (09/10/89)

In article <2071@munnari.oz.au>, ok@cs.mu.oz.au (Richard O'Keefe) writes:
> It's as if
> 	p = 1;
> 	printf("%d\n", p);
> 	if (p == 0)
> were allowed to blow up.

Try this:
	foo = GetPointerToSharedMemory();
	DeleteSharedMemory();
	if(foo==0);

There will always be cases like this. It just so happens that it may be
desirable to make free() one of these cases. So the standard does not preclude
it.

> The problem with address loads trapping is not a hardware problem; it is a
> hardware/OS problem.

Yes.

> I have outlined one approach which requires some OS
> cooperation, but if the standard required the usual invariance property to
> hold, OS vendors just might see an advantage in making C implementation
> straightforward.

The standard deliberately avoids doing this sort of thing. How about the
limitation in significant characters in extyernal symbols? That one's much
more irritating.
-- 
Peter da Silva, *NIX support guy @ Ferranti International Controls Corporation.
Biz: peter@ficc.uu.net, +1 713 274 5180. Fun: peter@sugar.hackercorp.com. `-_-'
"...the TV reporters, who are as intelligent as electric toasters"         'U`
	-- Clayton E. Cramer

merlin@mic.UUCP (Merlin Wilkerson) (09/11/89)

In article <11018@smoke.BRL.MIL> gwyn@brl.arpa (Doug Gwyn) writes:
>In article <29110@news.Think.COM> barmar@think.COM (Barry Margolin) writes:
>>What may be needed to satisfy everyone is a built-in operation that
>>tells whether a pointer is valid to use.
>
>My opinion is that if the programmer doesn't know that without applying
>such a test, his code is out of control.
>
I agree.  I have sort of followed this conversation.  I can't think
of any reason why you would test to see if you had freed allocated 
space.  If need for the space was in fact conditional, I would still
want to get (and free) it explicitly.

built-in operations == built-in bullshit.

--------
merlin

tneff@bfmny0.UU.NET (Tom Neff) (09/11/89)

It's true that a well written application program should never be in
doubt whether a pointer is obsolete, but OS code doesn't always have
this luxury.  An OS is by definition "out of control" -- ask any
sysprog. :-)
-- 
Annex Canada now!  We need the room,	\)	Tom Neff
    and who's going to stop us.		(\	tneff@bfmny0.UU.NET

chasm@attctc.Dallas.TX.US (Charles Marslett) (09/13/89)

In article <10988@smoke.BRL.MIL>, gwyn@smoke.BRL.MIL (Doug Gwyn) writes:
> In article <1465@levels.sait.edu.au> CCDN@levels.sait.edu.au (DAVID NEWALL) writes:
> >a machine that *did* trap in such a case, would be decidedly unfriendly.
> 
> while letting you continue to play with pointers that point absolutely
> nowhere would be friendly??

Most assuredly yes -- I have often kept lists of freed pointers (to identify
which were "not really" pointers for example), and the necessity of doing
a large number of (error prone) operations to avoid traps laid by the
computer makers and compiler standards writers does make me decidedly
unfriendly.  So I must assume that the inciting action was also unfriendly
;^).

===========================================================================
Charles Marslett
STB Systems, Inc.  <== Apply all standard disclaimers
Wordmark Systems   <== No disclaimers required -- that's just me
chasm@attctc.dallas.tx.us

jdr+@andrew.cmu.edu (Jeff Rosenfeld) (09/13/89)

> Excerpts from netnews.comp.lang.c: 18-Aug-89 Re: effect of free()
> jourdan m. joseph@seti.i (1729)

> >bill@twwells.com (T. William Wells) writes:
> >> ...For example, the following code fragment is nonportable:
> >> 
> >> 	char    *ptr;
> >> 
> >> 	ptr = malloc(1);
> >> 	...
> >> 	free(ptr);
> >> 	...
> >> 	if (ptr == 0) ...
> >> 
> >> The if might cause a trap when the value of ptr is accessed.
> >
> >Not true.  The "if" only examines the value of the pointer, not what it
> >points to.

> You're wrong, Dick.  Someone else already pointed it out, but let me
> make it clear again.  Imagine a segmented memory architecture with
> protections, and imagine that the call to "free" above frees the last
> used block in the segment and that "free" is clever enough to
> determine it and decides to release the segment to the OS, thus making
> the whole segment invalid (this is perfectly conceivable, and not
> forbidden by any ``standard''; actually I'd love to see some
> programs/processes decrease their memory size when they no longer use
> it).  Then merely loading the value of "ptr" in a register to test it
> against 0 will cause a invalid-address trap.


I can't believe this went so long without a response. Dick is correct;
@i[you] are mistaken, Jourdan. Keep in mind that pointers are stored in
memory just like any other data types and can be loaded into
general-purpose registers. No architecture of which I am aware does
protection checking when loading data into a general purpose register.
Similarly, the only segemented architecture with which I am familiar
(80*86) does even allow any operations on segment registers save load,
push, and validate (or some such).  In other words, there is no fear of
protection violation (even with a segemented architecture) until you
attempt to dereference a pointer. The key is that with segments,
dereferencing begins by loading a segement register. Get it?

BTW, the "non-portable" code given is unlikely to work in any event
because if the malloc() call fails then you're calling free(NULL) which
is very likely to cause a seg fault in any protected environment.

			- Jeff.

diamond@csl.sony.co.jp (Norman Diamond) (09/13/89)

In article <247@bbxeng.UUCP> scott@bbxeng.UUCP (Scott-Engineering) writes:
>>In other words - you cannot even *test* a pointer unless you are *sure*
>>there is a valid address in it.  I don't think so.

In article <10985@smoke.BRL.MIL> gwyn@brl.arpa (Doug Gwyn) writes:

>There is no way a strictly conforming C program can create an invalid
>pointer value, apart from free()ing a valid one that it got from malloc()
>or realloc().  The moment an improper pointer value is created via
>arithmetic operations, the program has violated the guarantees of the
>Standard, and the implementation would be free to trap immediately.

Another way to create a pointer that does not point to an object is to
assign a NULL constant to it.

A pointer variable containing a valid pointer value may be compared to
a NULL constant.

Scott-Engineering's posting implies these questions (among others):
(1) if a pointer variable contains a NULL value, then may that pointer
     be compared to a NULL constant?
(2) if a pointer variable contains a NULL value, then may that pointer
     be compared to a valid pointer value?

Scott-Engineering's reading of the standard implied negative answers,
which he finds hard to believe.  (I still can't get a copy of the
proposed standard and cannot form an opinion.)  Mr. Gwyn, perhaps you
could please post a better answer.

--
-- 
Norman Diamond, Sony Corporation (diamond@ws.sony.junet)
  The above opinions are inherited by your machine's init process (pid 1),
  after being disowned and orphaned.  However, if you see this at Waterloo or
  Anterior, then their administrators must have approved of these opinions.

diamond@csl.sony.co.jp (Norman Diamond) (09/13/89)

In article <254@bbxeng.UUCP> scott@bbxeng.UUCP (Scott-Engineering) writes:

>5)  However, a C compiler that attempts to load a segment register (or
>    overly sensitive address register) in order to simply *compare*
>    addresses is brain damaged.

Even if the code subsequently USES the pointer when it's non-null?
Try:  a C compiler that attempts to load a segment register (or
overly sensitive address register) in order to simply *compare*
addresses has taken the first trivial step towards having an optimizer.

>6)  Therefore, we all agree to avoid those compilers thus driving the
>    vendors out of business and we have nothing to worry about.

If you wish to personally boycott all compilers that do optimization,
feel free.

--
-- 
Norman Diamond, Sony Corporation (diamond@ws.sony.junet)
  The above opinions are inherited by your machine's init process (pid 1),
  after being disowned and orphaned.  However, if you see this at Waterloo or
  Anterior, then their administrators must have approved of these opinions.

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/14/89)

In article <10828@riks.csl.sony.co.jp> diamond@ws.sony.junet (Norman Diamond) writes:
>Scott-Engineering's posting implies these questions (among others):
>(1) if a pointer variable contains a NULL value, then may that pointer
>     be compared to a NULL constant?
>(2) if a pointer variable contains a NULL value, then may that pointer
>     be compared to a valid pointer value?

No, it doesn't.  If you were following this thread, it should have been
clear that we were discussing invalid pointers that were "poison" to
inspect.  Null pointers are something entirely different, and of course
the Standard allows you to do the obvious things with null pointers.

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/14/89)

In article <gZ3LTWu00Xc8M0Nm0l@andrew.cmu.edu> jdr+@andrew.cmu.edu (Jeff Rosenfeld) writes:
-> Excerpts from netnews.comp.lang.c: 18-Aug-89 Re: effect of free()
-> jourdan m. joseph@seti.i (1729)
-> >bill@twwells.com (T. William Wells) writes:
-> >> 	free(ptr);
-> >> 	if (ptr == 0) ...
-> >> The if might cause a trap when the value of ptr is accessed.
-> >Not true.  The "if" only examines the value of the pointer, not what it
-> >points to.
-> You're wrong, Dick.  Someone else already pointed it out, but let me
-> make it clear again.  ...
-I can't believe this went so long without a response. Dick is correct;
-@i[you] are mistaken, Jourdan. Keep in mind that pointers are stored in
-memory just like any other data types and can be loaded into
-general-purpose registers. No architecture of which I am aware does
-protection checking when loading data into a general purpose register.

I'm not sure the attributions are correct (this -> >> stuff gets
confusing), but the very mention of "loading into a general-purpose
register" implies a certain limited class of architectures.  The
discussion was about what MIGHT legally occur in a C implementation
on SOME architecture.  And it appears that it is legal for an
implementation to get heartburn over continued access of a pointer
(NOT just of what it presumably would point to) once the associated
storage has been free()d.

chris@mimsy.UUCP (Chris Torek) (09/14/89)

In article <gZ3LTWu00Xc8M0Nm0l@andrew.cmu.edu> jdr+@andrew.cmu.edu
(Jeff Rosenfeld) writes:
>I can't believe this went so long without a response.

It went about 15 minutes without a response.  CMU really ought to have
all the followups on this already....

>Similarly, the only segemented architecture with which I am familiar
>(80*86) does even allow any operations on segment registers save load,
>push, and validate (or some such).

Here you are wrong: the 80x86 has `load', `store', `push', and `pop'
for segment registers.  Unfortunately, `load' implies `validate' (presumably
`pop' does as well).

>The key is that with segments, dereferencing begins by loading a
>segement register. Get it?

No, the key is that with segments, merely loading the segment register
can cause a trap, and merely mentioning the value of a pointer variable
(such as in `if (ptr == NULL)') may load a segment register.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

jdr+@andrew.cmu.edu (Jeff Rosenfeld) (09/14/89)

> Excerpts from netnews.comp.lang.c: 13-Sep-89 Re: effect of free() Doug
> Gwyn@smoke.BRL.MIL (1384)

> the very mention of "loading into a general-purpose
> register" implies a certain limited class of architectures.  The
> discussion was about what MIGHT legally occur in a C implementation
> on SOME architecture. 

OK - then consider the following code fragment (no pointer/int flames,
please - they're not relevant here):

union pi {
    char *ptr;
    unsigned long num;
} x;

x.ptr = malloc(AMOUNT);
if (x.ptr != NULL) free(x.ptr);
foo(x.num);

This is perfectly legal code (despite that x.num contains nothing of
guaranteed usefulness) and any compiler that generates code that causes
a seg fault on the call to foo() has some serious problems. 

If you accept the previous statement, then it follows that any
architecture capable of supporting an implementation of C must be able
to perform a test on an arbitrary pointer. The test operation might be
implemented through GP registers or with memory operands or whatever.
But it HAS to be doable if the architecture can support C. 

Of course, if the ANSI committee deems my example illegal then I guess
it's time to find another language-of-choice. In the meantime, I'd
appreciate if someone can prove by counter-example using any real or
imagined architecture that my claim is bogus.

			- Jeff (fearing days of strongly-typed assemblers).

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/14/89)

In article <YZ3p34a00XcA04hUZt@andrew.cmu.edu> jdr+@andrew.cmu.edu (Jeff Rosenfeld) writes:
>union pi {
>    char *ptr;
>    unsigned long num;
>} x;
>x.ptr = malloc(AMOUNT);
>if (x.ptr != NULL) free(x.ptr);
>foo(x.num);
>This is perfectly legal code (despite that x.num contains nothing of
>guaranteed usefulness) ...

No, it isn't.  x.num has no value, and accessing it has indeterminate
results.  This is not a valid technique for converting pointers to
corresponding integer representations; you must use a cast for that,
and if you did use a cast, you still are accessing an invalid pointer
value and should expect trouble.

>and any compiler that generates code that causes
>a seg fault on the call to foo() has some serious problems. 

I would say that any programmer who expects such code to work is the
one who has problems.

If you had written foo(x), then I would agree that that is legal,
but still any attempt to use x.ptr (itself, not just what it points
to) within foo would be "illegal" (i.e. not strictly conforming).  It
does show that the implementation would need to keep unions in generic
data space, not in pointer space, but that was pretty obvious anyway.
It is the loading of an address (pointer, offset, base, whatever)
register that would trigger the trap.  Getting back to your attempted
example, referencing x.num would cause a data, not an address, register
to be loaded in the kind of implementations we've been discussing.

>Of course, if the ANSI committee deems my example illegal then I guess
>it's time to find another language-of-choice.

I don't speak for the committee, but speaking personally, feel free
(maybe you should contact Herman Rubin and offer to help design a
language you both can enjoy).

>In the meantime, I'd appreciate if someone can prove by counter-example
>using any real or imagined architecture that my claim is bogus.

I've now seen at least four such examples posted in this newsgroup.

CCDN@levels.sait.edu.au (DAVID NEWALL) (09/14/89)

In article <10988@smoke.BRL.MIL>, gwyn@smoke.BRL.MIL (Doug Gwyn) writes:
> In article <1465@levels.sait.edu.au> CCDN@levels.sait.edu.au (DAVID NEWALL) writes:
> >a machine that *did* trap in such a case, would be decidedly unfriendly.
>
> while letting you continue to play with pointers that point absolutely
> nowhere would be friendly??

I think you're looking at this from the wrong angle.  Don't ask why it
would be friendly to allow invalid pointers (remember that I'm not talking
about dereferencing of invalid pointers), ask instead why it would be
unfriendly to disallow it.  I can think of a few reasons why one might
legitimately want to process such a pointer.  In fact, the definitions for
SIG_DFL and SIG_IGN would seem to be excellent examples of this:
        #define SIG_DFL (int (*)())0
        #define SIG_IGN (int (*)())1

I have a philosophy which I am going to share with you:  Avoid unnecessary
restrictions.  Is it truly necessary that one not be able to *look* at a
pointer without first knowing that it is valid?  I claim that it is not.


David Newall                     Phone:  +61 8 343 3160
Unix Systems Programmer          Fax:    +61 8 349 6939
Academic Computing Service       E-mail: ccdn@levels.sait.oz.au
SA Institute of Technology       Post:   The Levels, South Australia, 5095

henry@utzoo.uucp (Henry Spencer) (09/14/89)

In article <YZ3p34a00XcA04hUZt@andrew.cmu.edu> jdr+@andrew.cmu.edu (Jeff Rosenfeld) writes:
>union pi {
>    char *ptr;
>    unsigned long num;
>} x;
>
>x.ptr = malloc(AMOUNT);
>if (x.ptr != NULL) free(x.ptr);
>foo(x.num);

Section 3.3.2.3, Oct 88 draft:  "With one [irrelevant] exception, if a
member of a union object is accessed after a value has been stored in a
different member of the object, the behavior is implementation-defined."

>This is perfectly legal code...

It will compile, but an implementation is entirely within its rights
to generate a core dump when you try to execute it.

Incidentally, why do you assume that x.num has the same contents as x.ptr?
There are a number of machines on which they aren't the same size.
-- 
V7 /bin/mail source: 554 lines.|     Henry Spencer at U of Toronto Zoology
1989 X.400 specs: 2200+ pages. | uunet!attcan!utzoo!henry henry@zoo.toronto.edu

cpcahil@virtech.UUCP (Conor P. Cahill) (09/14/89)

In article <YZ3p34a00XcA04hUZt@andrew.cmu.edu>, jdr+@andrew.cmu.edu (Jeff Rosenfeld) writes:
> OK - then consider the following code fragment (no pointer/int flames,
> please - they're not relevant here):
> 
> union pi { char *ptr; unsigned long num; } x;
> 
> x.ptr = malloc(AMOUNT);
> if (x.ptr != NULL) free(x.ptr);
> foo(x.num);
> 
> This is perfectly legal code (despite that x.num contains nothing of
> guaranteed usefulness) and any compiler that generates code that causes
> a seg fault on the call to foo() has some serious problems. 


Yes a compiler should not generate a seg fault with the use of x.num
because x.num is not a pointer and the compiler should not treat it
as such.  However, had you called foo() with x.ptr, the compiler
would treat the value as a pointer and could possibly fail because the
pointer does not have a valid address in it.


> If you accept the previous statement, then it follows that any
> architecture capable of supporting an implementation of C must be able
> to perform a test on an arbitrary pointer. The test operation might be
> implemented through GP registers or with memory operands or whatever.
> But it HAS to be doable if the architecture can support C. 


I don't accept the previous statement so this whole paragraph is mute.


> Of course, if the ANSI committee deems my example illegal then I guess
> it's time to find another language-of-choice. In the meantime, I'd
> appreciate if someone can prove by counter-example using any real or
> imagined architecture that my claim is bogus.



You're example is not illegal (although it is of little use).  But you are
not touching the "element" that is at an issue here: whether or not you
can examine an invalid pointer.  You did not examine a pointer.  You
examined an unsigned long.










-- 
+-----------------------------------------------------------------------+
| Conor P. Cahill     uunet!virtech!cpcahil      	703-430-9247	!
| Virtual Technologies Inc.,    P. O. Box 876,   Sterling, VA 22170     |
+-----------------------------------------------------------------------+

flaps@dgp.toronto.edu (Alan J Rosenthal) (09/15/89)

jdr+@andrew.cmu.edu (Jeff Rosenfeld) writes:
>union pi {
>    char *ptr;
>    unsigned long num;
>} x;
>
>x.ptr = malloc(AMOUNT);
>if (x.ptr != NULL) free(x.ptr);
>foo(x.num);
>
>This is perfectly legal code (despite that x.num contains nothing of
>guaranteed usefulness) and any compiler that generates code that causes
>a seg fault on the call to foo() has some serious problems.

Well, without agreeing that it's legal code (can't there be invalid int values
as well as invalid pointer values?%), I would like to point out that x.num == 0
may not be the same as x.ptr == NULL, even if null pointers are represented by
all bits zero.

Suppose pointers are 48 bits and int and long are both 32 bits.  Then x.num
might be only the high or only the low 32 bits of the pointer's representation.
These might all be zero even if the pointer is not all zero.

So, wrt the previous discussion about loading pointer values into address
registers, it might not be possible to load pointer values into data registers
because they might not fit.

ajr

% in other words, can "int x; printf("%d\n", x);" dump core?  I would think
it could.  If it couldn't, that would be bad, because then environments which
caused an abort in this situation wouldn't be ansi-compliant, and I think that
aborting on the access of undefined values can be useful for debugging.

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/15/89)

In article <1641@levels.sait.edu.au> CCDN@levels.sait.edu.au (DAVID NEWALL) writes:
>I have a philosophy which I am going to share with you:  Avoid unnecessary
>restrictions.  Is it truly necessary that one not be able to *look* at a
>pointer without first knowing that it is valid?  I claim that it is not.

I'm of course well acquainted with that "philosophy".  However, in the
real world there are systems designed by people using different
philosophies.  In particular, there is a school of thought that says
machine architecture should be designed to assist in program reliability.
That school occasionally influences computer architectures such that
actions like merely continuing to shuffle around invalid pointers cause
an error trap to be taken.

Given that some systems do this, is it necessary for the C standard to
deliberately make implementation in such environments harder or slower
than necessary?  So far we haven't seen compelling reasons to.

ok@cs.mu.oz.au (Richard O'Keefe) (09/15/89)

In article <1989Sep14.145420.5658@jarvis.csri.toronto.edu>, flaps@dgp.toronto.edu (Alan J Rosenthal) writes:
> % in other words, can "int x; printf("%d\n", x);" dump core?  I would think
> it could.  If it couldn't, that would be bad, because then environments which
> caused an abort in this situation wouldn't be ansi-compliant, and I think that
> aborting on the access of undefined values can be useful for debugging.

I actually had this happen to me in Pascal on a certain machine.  Clever
Hans had written the Pascal equivalent of
	union {
	    int pre_hash;
	    char the_bytes[sizeof (int)];
	} hack;
	int hash;
	... move first few characters of token into hack.the_bytes ...
	hash = hack.pre_hash % MODULUS;
The assumption was that every bit-pattern of the right size was a valid
integer, and on the machine in question that was _not_ true.  This will
give similar trouble on machines with VAX arithmetic (some bit patterns
when considered as floats are "invalid operands" which trap when/if you
touch them) and on machines with IEEE arithmetic and traps enabled (the
IEEE default is _not_ to enable traps!) where some bit patterns (called
Signalling NaNs) trap.

There is however a significant difference between this case and the one
we have been discussing:  these numbers are _born_ invalid; there is no
way to get your hands on them without inherently non-portable hackery.

[BTW: claims about free() and segmented architectures turn out _not_ to
apply to the 80*86: you wouldn't implement if (ptr == 0) by loading ptr
into a segment register because you _can't_ compare segment registers.]

mcdonald@uxe.cso.uiuc.edu (09/15/89)

In article <YZ3p34a00XcA04hUZt@andrew.cmu.edu> jdr+@andrew.cmu.edu (Jeff Rosenfeld) writes:
>union pi {
>    char *ptr;
>    unsigned long num;
>} x;
>x.ptr = malloc(AMOUNT);
>if (x.ptr != NULL) free(x.ptr);
>foo(x.num);
>This is perfectly legal code (despite that x.num contains nothing of
>guaranteed usefulness) ...

Doug Gwyn replies:

>No, it isn't.  x.num has no value, and accessing it has indeterminate
>results.  This is not a valid technique for converting pointers to
>corresponding integer representations; you must use a cast for that,
>and if you did use a cast, you still are accessing an invalid pointer
>value and should expect trouble.

I don't think that "illegal code" should apply to the example.
It should be "implementation defined behaviour". It is clearly not
portable.

But somewhere there has to be a way to diddle the bits in objects
that are not integers (where of course there are the legal & and |
operators. Somebody, somewhere has to be able to diddle the bits
in a double or float, in order to construct it out of an integer

double d; int i;   ......      d = i;

and somebody has to diddle the bits in a pointer (on machines with
segments or read-write-execute bits stuck onto pointers) when it
is created (by malloc or the OS).

One can diddle bits in pointers by using integer types and then
casting to pointers, but doing it in float types requires unions -
because casting an integer type to a float type preserves the VALUE,
not the bits. Why would someone want to construct a float themselves,
rather than casting?  Well, perhaps they desire to construct
an illegal value (i.e. IEEE float format NaN). This might occur
inside a floating exception handler, or a math routine ( sqrt(-6) ).
 
Perhaps some might say "do it in assembler", but 
sometimes I would like to do it in C.  When I say "somebody" I am
including explicitly the routines in the OS that pass pointers
to "malloc".

If this sort of stuff is not literally IMPOSSIBLE on a given
machine, using a union should work. If it IS impossible -
don't buy the machine. If it doesn't work - don't buy the compiler.

Is not most of Unix written in C?  Doesn't this sort of stuff
happen there?

Doug McDonald

barmar@think.COM (Barry Margolin) (09/16/89)

In article <YZ3p34a00XcA04hUZt@andrew.cmu.edu> jdr+@andrew.cmu.edu (Jeff Rosenfeld) writes:
>any architecture capable of supporting an implementation of C must be able
>to perform a test on an arbitrary pointer. The test operation might be
>implemented through GP registers or with memory operands or whatever.
>But it HAS to be doable if the architecture can support C. 

Yes, it may be possible to compare pointers using data registers.
However, that may not be the best way to compare pointers on a
particular machine, so the compiler implementor should be permitted to
use the more optimal address registers when doing pointer operations.

Barry Margolin
Thinking Machines Corp.

barmar@think.com
{uunet,harvard}!think!barmar

news@ism780c.isc.com (News system) (09/16/89)

In article <1641@levels.sait.edu.au> CCDN@levels.sait.edu.au (DAVID NEWALL) writes:
>I have a philosophy which I am going to share with you:  Avoid unnecessary
>restrictions.  Is it truly necessary that one not be able to *look* at a
>pointer without first knowing that it is valid?  I claim that it is not.

I don't understand this discussion.  Inspecting a pointer variable that is
not initialzed is no different from inspecting ANY uninitialized variable.  A
pointer that has free applied to it is unitialized.  How in the world could
any programming language be defined to assign a useful meaning to the
inspection uninitialized data?  Hint: defining the state of local data at
procedure entry can be very expensive.

    Marv Rubinstein

ok@cs.mu.oz.au (Richard O'Keefe) (09/16/89)

In article <33383@ism780c.isc.com>, news@ism780c.isc.com (News system) writes:
> I don't understand this discussion.  Inspecting a pointer variable that is
> not initialzed is no different from inspecting ANY uninitialized variable.  A
> pointer that has free applied to it is unitialized.  How in the world could
> any programming language be defined to assign a useful meaning to the
> inspection uninitialized data?  Hint: defining the state of local data at
> procedure entry can be very expensive.
> 
>     Marv Rubinstein

The context of the discussion is
	p = NULL;
	...
	if (hairy-test-expression) {
	    ...
	    p = malloc(some_size);
	    assert(p != NULL);
	    ...
	    free(p);
	    ...
	}
	if (p != NULL) ...   <- may go BANG! here.

Look at it carefully:  p is NOT "uninitialised".  Whether program execution
took the "p = malloc()" path or just fell straight through, p has in fact
been initialised to a meaningful value.  free() does not have any access to
p, only to the value of p.  There may be fifteen million variables all with
the same value as p; free() has no access to them either.  Hint: locating
all the variables with the same value as p and making them uninitialised
can be very expensive.

It appears that the draft standard DOES allow the program to do anything
the implementor pleases at the marked point, and that that was a careful
and deliberate choice based on the belief that there might be machines where
that might make sense (though not 680x0s, VAXen, /370s, 80*86s, or PR1MEs).
However, this has nothing to do with uninitialised variables.

The longer I use C, the better I like Ada, and I started out _hating_ Ada.

msb@sq.sq.com (Mark Brader) (09/16/89)

This discussion belongs in comp.std.c by now, but as I think it ought
to be winding down, I won't trouble to redirect it.

> OK - then consider the following code fragment (no pointer/int flames,
> please - they're not relevant here):

Oh yes they are.  But I'll get to that in a moment.

> union pi {
>     char *ptr;
>     unsigned long num;
> } x;
> x.ptr = malloc(AMOUNT);
> if (x.ptr != NULL) free(x.ptr);
> foo(x.num);
> This is perfectly legal code (despite that x.num contains nothing of
> guaranteed usefulness) and any compiler that generates code that causes
> a seg fault on the call to foo() has some serious problems. 

Correct.  But that final statement doesn't necessarily access all the bytes
of x!  There's no guarantee that sizeof(unsigned long) >= sizeof(char *).

It might be the case that the only registers *large enough* to contain
pointers are those which also have the effect of trapping when loaded
with an invalid pointer value.  Then if you want to ensure that "if (ptr
== 0)" does not trap, you have to implement it in some more complicated
way that a simple register load and test.  For X3J11 to have required
such a time penalty just to save some programs that are broken anyway
would not have been reasonable.

For people coming in late, two things should be pointed out.  One is that
comparison involving a legitimately created null pointer is not at issue;
a machine such as just described can implement "if (ptr == 0)" in the
obvious way, without fear of trapping if ptr is a null pointer, by choosing
to implement null pointers as pointers to an allocated but otherwise unused
piece of actual memory.  Of course, the 0 is a null pointer constant in this
context and would be converted at compile time to such a pointer.

The second thing was in the article to which the one quoted above was
a response:
| The discussion was about what MIGHT legally occur in a C implementation
| on SOME architecture. 

-- 
Mark Brader, Toronto	"If the standard says that [things] depend on the
utzoo!sq!msb		 phase of the moon, the programmer should be prepared
msb@sq.com		 to look out the window as necessary."  -- Chris Torek

This article is in the public domain.

barmar@think.COM (Barry Margolin) (09/17/89)

In article <225800221@uxe.cso.uiuc.edu> mcdonald@uxe.cso.uiuc.edu writes:
>But somewhere there has to be a way to diddle the bits in objects
>that are not integers (where of course there are the legal & and |
>operators. Somebody, somewhere has to be able to diddle the bits
>in a double or float, in order to construct it out of an integer
>
>double d; int i;   ......      d = i;

Many machines with floating point hardware have instructions that do
such conversions.  No one has to "diddle the bits".

>and somebody has to diddle the bits in a pointer (on machines with
>segments or read-write-execute bits stuck onto pointers) when it
>is created (by malloc or the OS).

In some architectures, the bits of a pointer may only be diddled by
privileged instructions.  User code can only create pointers by
modifying existing pointers (e.g. adding offsets).  Pointers are
created by performing system calls to allocate new segments, for
instance.

>One can diddle bits in pointers by using integer types and then
>casting to pointers, but doing it in float types requires unions -
>because casting an integer type to a float type preserves the VALUE,
>not the bits.

Where is it written that casting between pointers and integers
preserves the bits?  I think that under the Symbolics C compiler, the
result of any cast from integer to pointer is a null pointer.  The
pANS says that the result of such a cast is implementation-dependent,
so this is OK.  Since Symbolics C pointers aren't memory addresses
(they are a pair of a array (whose address could change due to garbage
collection) and an index), a value-preserving cast between integers
and pointers would be prohibitively expensive.

> Why would someone want to construct a float themselves,
>rather than casting?  Well, perhaps they desire to construct
>an illegal value (i.e. IEEE float format NaN). This might occur
>inside a floating exception handler, or a math routine ( sqrt(-6) ).
>Perhaps some might say "do it in assembler", but 
>sometimes I would like to do it in C.  When I say "somebody" I am
>including explicitly the routines in the OS that pass pointers
>to "malloc".

Any code that diddles bits in the representation of an object is by
definition not portable, so what does it matter what the standard
says?  If the hardware allows it, then you can probably get the C
implementation for that hardware to do it.  But since not all
hardwares permit such operations, it would be a mistake for the C
standard to mandate a way to do it.

>Is not most of Unix written in C?  Doesn't this sort of stuff
>happen there?

There are a few system-dependent modules in the Unix kernel.  There's
even some assembly code in there in most implementations.  For
instance, the kernel must change interrupt priority levels at times,
but there's no standard C syntax for this.  Are you proposing that
there be a standard C syntax for everything that any machine can do?
ANSI C isn't intended to be a replacement for assembly language.

Barry Margolin
Thinking Machines Corp.

barmar@think.com
{uunet,harvard}!think!barmar

chasm@attctc.Dallas.TX.US (Charles Marslett) (09/17/89)

In article <11063@smoke.BRL.MIL>, gwyn@smoke.BRL.MIL (Doug Gwyn) writes:
> I've now seen at least four such examples posted in this newsgroup.
[With reference to architectures that fail to support access to pointers
after the memory pointed to is deallocated.]

I have seen two inaccurate examples (in that there do not exist, and probably
could not exist) of such -- assuming very poor quality code generation for
Intel 80x86 processors.

Neither could survive an environment with interrupts (and therefore, could
not exist on any OS I know of for the Intel chips).

A third example, for a fictitous processor, does indeed have the same
characteristic, and it may in fact be able to support C.  Without any
details, I would not assert it to be possible, however, and I would be very
inclined to disbelieve any assertion that both a standard version of C (one
that would accept anything like the language we know today) and Unix (or
Multics) could coexist on the machine.

I missed the fourth example.  Sorry :^(.

Would the next poster who wishes to address the issue either provide some
reasonable archtecture (say a tagged one with no registers, only memory, for
example -- I cannot think of another possibility, yet) where comparision of
an invalid pointer to 0 can generate a trap, OR provide evidence that such
a failure would keep the machine from handling the C language.

I would like to lear a bit from the discussion, but it seems to have
degenerated to a "does so, does not, DOES SO, DOES NOT, ...".

This is much like the religous wars between the Mac and IBM folk, or the Atari
and Amiga folk -- lots of useless noise, so far.

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/17/89)

In article <225800221@uxe.cso.uiuc.edu> mcdonald@uxe.cso.uiuc.edu writes:
>If this sort of stuff is not literally IMPOSSIBLE on a given
>machine, using a union should work. If it IS impossible -
>don't buy the machine. If it doesn't work - don't buy the compiler.

It might or might not be possible to do it via C unions,
depending both on the machine architecture and on the C implementation.

>Is not most of Unix written in C?  Doesn't this sort of stuff happen there?

UNIX grew over many years, starting from a single machine implementation
in assembly language, progressing through a single machine implementation
primarily in C, today primarily implemented in portable C.  Apart from
vestiges of its evolution that could be cleaned up and made portable,
there is little need for such bit diddling in the universal part of UNIX.
Device drivers etc. are inherently implementation-specific, and they do
sometimes rely on aspects of the C implementation that are not guaranteed
to be portable.  But so what?

scott@bbxsda.UUCP (Scott Amspoker) (09/18/89)

In article <10829@riks.csl.sony.co.jp> diamond@ws.sony.junet (Norman Diamond) writes:
>
>>6)  Therefore, we all agree to avoid those compilers thus driving the
>>    vendors out of business and we have nothing to worry about.
>
>If you wish to personally boycott all compilers that do optimization,
>feel free.
>

Thank you, we do feel free.  We have over 60000 lines of 'C' code that
runs unmodified and over 40 different systems (different OS and
hardware archetecture).  The nature of our application is such that
we do a *lot* of pointers-to-structures (so yes, we benifit from
pointer optimization).  In order to earn a living and feed our families
we have had to make sure our C code is as safe and conforming as possible 
(no tricky stuff).  I would hazard a guess that many other software 
developers would get bit long before we do (based on some of the postings
I've seen in this group).

However, once in a blue moon a C compiler comes along that appears to 
go out of it's way to make trouble.  We have turned down brain-damaged 
compilers before and it hasn't hurt us one bit.  Although I have never seen a
compiler/architecture combination that has unsafe pointer compares
I have seen some other bizzare things.  Fortunatly, these things happen
very rarely and *never* with a well known company (although well-known
companies do have bugs).

For example, since we run on Intel cpu's we use the "near" and "far"
keywords.  When we compile on other environments we must remove
those keywords.  We do so with:

#define near
#define far

So far, so good.  Now along comes a C compiler that doesn't support
"near" and "far" but also WON'T LET US REDEFINE NEAR AND FAR either.
(because near and far are reserved).  For crying out loud!  In this case
it was easy for us to do the port anyway because it required a simple text 
string substitution across all of our source.

Perhaps we will run into something that is the result of some obscure
assumption we made that, technically, is non-comforming.  While we
would admit that we were wrong we would also remind ourselves that we
have been doing extremely well so far and wouldn't necessarily take
the expense to re-write all our code.  That's life.  I realize that
this group is much more academic.  If you want to sell a C compiler
in today's market you better make sure it can run a lot of existing code
without any trouble.  Where one draws that line is a matter of personal
taste.  I personally don't think that every little bit of optimization
has infinite value when there are so many other things to consider.

CCDN@levels.sait.edu.au (DAVID NEWALL) (09/19/89)

In article <11070@smoke.BRL.MIL>, gwyn@smoke.BRL.MIL (Doug Gwyn) writes:
> In particular, there is a school of thought that says machine architecture
> should be designed to assist in program reliability.  That school
> occasionally influences computer architectures such that actions like
> merely continuing to shuffle around invalid pointers cause an error trap
> to be taken.

That is supposed to make programs reliable?

Testing parameters for "correctness", rather than blindly hoping that they
are "correct", is one way to make programs more reliable.  Does that school
of yours suggest that it's OK to believe all (non-null) pointers?


David Newall                     Phone:  +61 8 343 3160
Unix Systems Programmer          Fax:    +61 8 349 6939
Academic Computing Service       E-mail: ccdn@levels.sait.oz.au
SA Institute of Technology       Post:   The Levels, South Australia, 5095

barmar@think.COM (Barry Margolin) (09/19/89)

In article <1693@levels.sait.edu.au> CCDN@levels.sait.edu.au (DAVID NEWALL) writes:
>In article <11070@smoke.BRL.MIL>, gwyn@smoke.BRL.MIL (Doug Gwyn) writes:
>> In particular, there is a school of thought that says machine architecture
>> should be designed to assist in program reliability.  That school
>> occasionally influences computer architectures such that actions like
>> merely continuing to shuffle around invalid pointers cause an error trap
>> to be taken.
>That is supposed to make programs reliable?

Yes.  The idea is that a program that tries to manipulate invalid
pointers is doing so inadvertently.  The hope is that the trap will be
invoked while the program is being tested, and the bug will be fixed.
And even if the trap isn't triggered during testing, it might be
triggered by an end user, who should report that the program crashed
under such-and-such a circumstance, which will permit the developers
to fix the bug.  An architecture that doesn't trap is allowing the
program to perform a presumably-unintended operation.

>Testing parameters for "correctness", rather than blindly hoping that they
>are "correct", is one way to make programs more reliable.

Unfortunately, there is no C operation that tests parameters for
"correctness".  Supposing C allowed invalid pointers to be referenced,
what would you use to test parameters for correctness?  The program
fragment "if (ptr != NULL)" has been bandied about quite a bit in this
chain, but it isn't useful for determining whether a pointer is valid
or not; it would do the same thing for an invalid pointer and a valid,
non-null pointer.

Barry Margolin
Thinking Machines Corp.

barmar@think.com
{uunet,harvard}!think!barmar

kim@kim.misemi (Kim Letkeman) (09/19/89)

In article <1693@levels.sait.edu.au>, CCDN@levels.sait.edu.au (DAVID NEWALL) writes:
> In article <11070@smoke.BRL.MIL>, gwyn@smoke.BRL.MIL (Doug Gwyn) writes:
> > In particular, there is a school of thought that says machine architecture
> > should be designed to assist in program reliability.  That school
> > occasionally influences computer architectures such that actions like
> > merely continuing to shuffle around invalid pointers cause an error trap
> > to be taken.
> That is supposed to make programs reliable?
> Testing parameters for "correctness", rather than blindly hoping that they
> are "correct", is one way to make programs more reliable.  Does that school
> of yours suggest that it's OK to believe all (non-null) pointers?
> 
I think Doug was alluding to a situation where the compiler and
hardware cooperate to attempt to eliminate bugs very early in the
development cycle.

As an example, the hardware architecture might be designed to trap an
out of range pointer (e.g. bus error on a 68000) and the compiler (or,
more correctly, run time library) could cooperate by stuffing
$FFFFFFFF into a pointer when free() has been called. This would have
the effect of trapping use of the (now) invalid pointer.

This can be of critical importance in an environment where many tens
of software designers are pouring code into a single software load for
a real-time telecommunications system (as just one example).
Eliminating stupid bugs (like using pointers after they have been
discarded) right up front pays huge dividends in field reliability.







-- 
Kim Letkeman    uunet!mitel!spock!kim

afscian@violet.waterloo.edu (Anthony Scian) (09/19/89)

In article <29624@news.Think.COM> barmar@think.COM (Barry Margolin) writes:
>In article <1693@levels.sait.edu.au> CCDN@levels.sait.edu.au (DAVID NEWALL) writes:
>>In article <11070@smoke.BRL.MIL>, gwyn@smoke.BRL.MIL (Doug Gwyn) writes:
>>> In particular, there is a school of thought that says machine architecture
>>> should be designed to assist in program reliability.  That school
>>> occasionally influences computer architectures such that actions like
>>> merely continuing to shuffle around invalid pointers cause an error trap
>>> to be taken.
>>That is supposed to make programs reliable?
>
>Yes.  The idea is that a program that tries to manipulate invalid
>pointers is doing so inadvertently.  The hope is that the trap will be
>invoked while the program is being tested, and the bug will be fixed.
>And even if the trap isn't triggered during testing, it might be
>triggered by an end user, who should report that the program crashed
>under such-and-such a circumstance, which will permit the developers
>to fix the bug.  An architecture that doesn't trap is allowing the
>program to perform a presumably-unintended operation.

The "trap on load of a bad pointer" feature sounds good initially
but it can lead to severe problems with CORRECT code.
Here is an example that occurs with OS/2 on the 286:

; optimizer has ptr in ES:DI but never references ptr afterwards
push es
push di
call dealloc
... ; never reaches this point!

dealloc: ; dealloc needs ES,DI,CX so they are saved
push es
push di
push cx
; frees segment as it frees the memory
...
pop cx
pop di
pop es	<-- EXCEPTION OCCURS HERE!
ret 4

The generalization of this tells us that "trap on load of bad pointer"
is a bad feature. It is impossible to use a calling convention that
saves registers with this feature (say A0-A7 on the 68K).
This severly limits what an optimizer can do across calls
(i.e., all A0-A7 regs must be NULLified before calls).
The restrictions placed on optimizers effectively renders
them useless unless there are plenty of non-trapping data registers.
The shuffling and clearing of address registers before calls
would severely comprimise performance.

Address registers should trap on USE not LOAD!
--
Anthony
//// Anthony Scian afscian@violet.uwaterloo.ca afscian@violet.waterloo.edu ////
"I can't believe the news today, I can't close my eyes and make it go away" -U2

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/20/89)

In article <1693@levels.sait.edu.au> CCDN@levels.sait.edu.au (DAVID NEWALL) writes:
-In article <11070@smoke.BRL.MIL>, gwyn@smoke.BRL.MIL (Doug Gwyn) writes:
-> In particular, there is a school of thought that says machine architecture
-> should be designed to assist in program reliability.  That school
-> occasionally influences computer architectures such that actions like
-> merely continuing to shuffle around invalid pointers cause an error trap
-> to be taken.
-That is supposed to make programs reliable?

Not in itself; it's a side effect of the architecture design.

-Testing parameters for "correctness", rather than blindly hoping that they
-are "correct", is one way to make programs more reliable.  Does that school
-of yours suggest that it's OK to believe all (non-null) pointers?

It's not "my school"; I'm just telling you about it.  There is far more
to this than error traps on invalid pointer reference.  If you have a
reasonable textbook on computer architectures, try looking there.
Otherwise Glen Myer's "Advances in Computer Architecture" would be a
good starting point for enlightenment.

msb@sq.sq.com (Mark Brader) (09/20/89)

> >union pi {
> >    char *ptr;
> >    unsigned long num;
> >} x;
> >x.ptr = malloc(AMOUNT);
> >if (x.ptr != NULL) free(x.ptr);
> >foo(x.num);

> ... an implementation is entirely within its rights
> to generate a core dump when you try to execute it.

This assertion startled me, because I thought I knew everything that
the pANS (proposed Standard) says about integer types.  However, it's
true, and I thought I'd better point out why.

The pANS requires that integer types be represented in a pure binary
numeration system; a footnote, which I think is substantive and should
therefore have been in the main text, in effect amends this by saying
"except for the high bit which may mean anything" (thus allowing 2's
complement, 1's complement, etc.).

But while it thus almost-specifies the representation of each possible
value, it does NOT specify that all possible representations have to
correspond to values; only a minimal range of values is guaranteed to
exist for each type.  For instance, ints must include the values -32767
to +32767; there is no requirement that a 65536th distinct value be
supported.  So even on a 2's complement machine with 16-bit ints, the
bit pattern 0x8000 could legitimately be used for "undefined" instead
of for -32768 as usual, and an operation such as 1^0x8001 could
legitimately dump core.

It is for similar reasons that foo(x.num); could dump core.  The union
could have been used to load the unsigned long with a bit pattern not
legitimate for unsigned longs on that machine; such bit patterns may
exist if unsigned longs are longer than 32 bits.

(By the way, this is not true for characters.  The pANS in essence defines
a character as a byte-sized bit pattern, so no "undefined" one is allowed.)


-- 
Mark Brader, SoftQuad Inc., Toronto, utzoo!sq!msb, msb@sq.com
	A standard is established on sure bases, not capriciously but with
	the surety of something intentional and of a logic controlled by
	analysis and experiment. ... A standard is necessary for order
	in human effort.				-- Le Corbusier

This article is in the public domain.

idall@augean.OZ (Ian Dall) (09/20/89)

In article <10983@smoke.BRL.MIL> gwyn@brl.arpa (Doug Gwyn) writes:
>In article <21952@cup.portal.com> Tim_CDC_Roberts@cup.portal.com writes:
>>Isn't this a quality of implementation issue?  ...  Thus, in the case above,
>>I would insist that the compiler writer move the address to a general-purpose
>>register and do the comparison there.
>
>It runs contrary to the "spirit of C" to require the implementation to
>generate code that does operations the hard way when a more natural way
>would suffice.

It violates the law of least surprises to have variables suddenly made
inaccessable by being a call by value argument to a function.

C used to be a language where I felt comfortable because I had a pretty
good idea of its basic principles. The concept of variables becoming
invalidated by function calls seems to my very counter-intuitive and
unless there is a STRONG efficiency argument on a reasonably desirable
architecture, then I think compilers should be required to do the comparison
in such a way that they don't generate traps.

.
.
.

-- 
 Ian Dall           life (n). A sexually transmitted disease which afflicts
                              some people more severely than others.
idall@augean.oz

scott@bbxsda.UUCP (Scott Amspoker) (09/20/89)

In article <591@augean.OZ>  writes:
>
>It violates the law of least surprises to have variables suddenly made
>inaccessable by being a call by value argument to a function.
>
>C used to be a language where I felt comfortable because I had a pretty
>good idea of its basic principles...

This is why there has been an excessive amount of noise on this thread.
Programmers are being told that merely *handling* an invalid pointer could
cause their program to crash.  In the legal world this is called "prior
restraint".  This is where you forbid somebody from doing something not
because it is wrong but because you believe it could eventually *lead* to
something wrong.  Ideas like that don't go over very well in a free society.
Even though very little production software, if any, would *ever* get zapped 
by this pointer thing; there is a fear that our old friend, C, has become a 
Marxist dictator (for our own good of course).  So, we are seeing posting 
after posting expressing disbelief.  Many readers are waiting for someone 
to say it's all a joke.

Nobody wants to be told that their well-written code will suddenly fail.
It is too bad that bad coding examples have been use to make arguments.
I guess that's what happens when you try to come up with trivial examples.

Fortunately, the real world is market driven.  I've said it before (and
got flamed for it) and I'll say it again:  If you don't like it, don't
buy it.  You have to decide for yourself how reasonable your expectations
are.  If you consistantly have trouble porting C code then you have a 
problem.  If, say, one compiler out of 20 gives you a bad time then
screw it.  Everything may be black and white in computer science but
not in business.  On rare occasion, our expectations have not been met.
I doesn't matter who's right or wrong, it is purely a business decision.

The masses will ultimately decide what the de facto standards will be.
A compiler vendor will either please the masses or it won't.  If you
think that the masses are stupid or beneath you then that's good.  That
means your competition is stupid and beneath you.

I know that comp.lang.c is more a place for theoretical discussion rather
than real world discussion, but when readers see their gigantic investment
in C code going out the window, the real world quickly enters into it.

But remember - you *do* have some voice in this.

-- 
Scott Amspoker
Basis International, Albuquerque, NM
(505) 345-5232

kim@kim.misemi (Kim Letkeman) (09/21/89)

In article <16507@watdragon.waterloo.edu>, 
   afscian@violet.waterloo.edu (Anthony Scian) writes:
>[Deleted several stacked articles discussing]
>[trap on invalid pointer issues             ]
> 
> The "trap on load of a bad pointer" feature sounds good initially
> but it can lead to severe problems with CORRECT code.
> Here is an example that occurs with OS/2 on the 286:
> 
>[Deleted example where free() breaks because it has]
>[to reference es register just one more time on    ]
>[exit but the RAM has already been freed.          ]
> 
> The generalization of this tells us that "trap on load of bad pointer"
> is a bad feature. It is impossible to use a calling convention that
> saves registers with this feature (say A0-A7 on the 68K).
> This severly limits what an optimizer can do across calls
> (i.e., all A0-A7 regs must be NULLified before calls).
> The restrictions placed on optimizers effectively renders
> them useless unless there are plenty of non-trapping data registers.
> The shuffling and clearing of address registers before calls
> would severely comprimise performance.
> 
> Address registers should trap on USE not LOAD!

Can you tell me how you would go about causing an 80286 to trap on
load of a "bad" value in the ES That's? This register is internal to
the CPU and I doubt that there are any values that would cause a trap
to be raised simply by loading them.

Ditto for the 68000. A0-A7 can handle values up to $FFFFFFFF (which is
well outside of the M68000's 16mb address space) without trapping on
load. (Mind you, you can't reference the RAM, but you can load the
value into the address register.)

You cannot create an example using existing architectures that behave
a certain way and then say "but if they did this other thing ..."

If they actually did that other thing, they would not be using your
general purpose compiler.

The point I am making is that your example is narrow of focus and has
no basis in any practical reality. These chips do not do what you are
worried about. Chips that do would require a specific compiler (or at
last run time library) implementation anyway.

I prefer my sweeping generalities with a little reality thrown in.

-- 
Kim Letkeman    uunet!mitel!spock!kim

barmar@think.COM (Barry Margolin) (09/21/89)

In article <125@bbxsda.UUCP> scott@bbxsda.UUCP (Scott Amspoker) writes:
>In article <591@augean.OZ>  writes:
>>C used to be a language where I felt comfortable because I had a pretty
>>good idea of its basic principles...
>there is a fear that our old friend, C, has become a 
>Marxist dictator (for our own good of course).

As has been said umpteen times in this thread, C hasn't changed (at
least not in this respect).  It has always been true that the results
of handling an invalid pointer were implementation-dependent.  The
only thing that has changed as a result of the pANS is the fact that
this portability problem is now documented; it used to be an unstated
fact.

To users, the pANS isn't a dictator, it's a mentor.  It's a
portability guide.  It doesn't say "you'll go to jail if you access
pointer variables that point to freed storage".  It says that programs
that do so could encounter portability problems.  This was true before
X3J11 was formed, and they've merely acknowledged it.  Prior to
X3J11's work such programs weren't maximally portable, but you didn't
know it; the only difference is that now you know it.

About the only bad effect this point in the standard could have is
that it may give unfortunate ideas to some implementors, who might
then go out of their way to make free() do weird things.  The
lattitude exists in the standard to allow C to map into the most
natural mechanism for a system, not to encourage intentionally
confusing behavior.  Referencing invalid pointers is allowed to trap
for the same reason that positive/negative is allowed to round either
up or down (personally, I think the implementation-dependent behavior
of "/" and "%" is one of C's biggest warts, but I know that it's way
too late to change it).

Barry Margolin
Thinking Machines Corp.

barmar@think.com
{uunet,harvard}!think!barmar

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/21/89)

In article <125@bbxsda.UUCP> scott@bbxsda.UUCP (Scott Amspoker) writes:
>...  This is where you forbid somebody from doing something not
>because it is wrong but because you believe it could eventually *lead* to
>something wrong.

Nobody is "forbidding" you to continue to access pointers after
they're free()d.  We're pointing out that it's not portable to do so.

>Nobody wants to be told that their well-written code will suddenly fail.

Nobody wants to hear that all the world's not a VAX, either..

I agree that there is a market issue here.  However, if you really
want to strive for widest availability of your software product,
it behooves you to pay attention to these portability matters.
Otherwise, your product will be confined to environments not too
dissimilar to the specific one that you assumed as a model.

It's not very hard to factor in the notion that a pointer is "poison"
when it no longer points to valid storage.  One would think that
"well-written code" would already follow that model, which is
compatible with a wider range of environments.

scott@bbxsda.UUCP (Scott Amspoker) (09/21/89)

In article <11121@smoke.BRL.MIL> gwyn@brl.arpa (Doug Gwyn) writes:
 >I agree that there is a market issue here.  However, if you really
 >want to strive for widest availability of your software product,
 >it behooves you to pay attention to these portability matters.
 >Otherwise, your product will be confined to environments not too
 >dissimilar to the specific one that you assumed as a model.
 >
 >It's not very hard to factor in the notion that a pointer is "poison"
 >when it no longer points to valid storage.  One would think that
 >"well-written code" would already follow that model, which is
 >compatible with a wider range of environments.


You missed the point of my posting.  Someone else recently made
an excellent posting on this issue (I forgot who, sorry).  It
was pointed out that there are no new portability issues that haven't
been there all along.  In other words, this pointer thing is not new.
However, nobody has been able to come up with an example where this
was actually a problem (unless I missed something).  Maybe it is 
because of the way people write their programs.  Maybe it is because
C implementers didn't have a standard to hide behind and kept a wide
safety margin in their products.  

Now that certain things are being *officially* declared as "implementation
dependent" there is a potential that things could be abused.  The implementer
only needs to tell everyone that their code is improperly written.  That
implementer only needs to hope that his competitors are equally arrogant.

There is currently a thread over in comp.std.c asking the question 
"Do non-trivial conforming programs exist?"  That is a valid question.
My point was, "As long as you are running on a sufficiently wide range
of environments (assuming that is your goal) who cares if fail on, say,
1 out of 20.  Sometimes the reason is simply and you should go ahead
and make the necessary change.  Other times the change would go far
deeper into the code and cost to much to change.

My alarm went off when I used the term *well-written code* in my
previous posting.  I was opening a can of worms but I did it
anyway because I *really* meant it.  There is *well-written* code
out there that is not necessarily 100% conforming.  Some people
may argue that I am stating a contridiction.  It all depends on
how you measure it.  If the code work is clean, maintainable,
effecient, and runs on "wide range of environments" (subjective)
then it is probably pretty good stuff.  Yet it might not be 100%
conforming.  The company's bottom line is the final arbitrator.
-- 
Scott Amspoker
Basis International, Albuquerque, NM
(505) 345-5232

scott@bbxsda.UUCP (Scott Amspoker) (09/22/89)

In article <990@kim.misemi> kim@kim.misemi (Kim Letkeman) writes:
>
>Can you tell me how you would go about causing an 80286 to trap on
>load of a "bad" value in the ES That's? This register is internal to
>the CPU and I doubt that there are any values that would cause a trap
>to be raised simply by loading them.

When you load a segment register on an 80286/80386 in *protected* mode
the CPU actually goes into an in-memory table an load a segment
descriptor in to an on-chip cache so that subsequent address translation
is reasonably fast.  If the segment register is loaded with a value
that does not have an associated segment descriptor then a trap
occurs.  This normally doesn't cause a problem.  ES is not
preserved across CALLs.  C compilers on the 80286 are not inclined
to load ES just to perform a pointer move or compare because

   1) segment register loads are expensive
   2) there is no segment register compare instruction

In *real* mode (MS-DOS) there is no possibility of a bad address trap.

-- 
Scott Amspoker
Basis International, Albuquerque, NM
(505) 345-5232

bill@twwells.com (T. William Wells) (09/22/89)

In article <591@augean.OZ>  writes:
: It violates the law of least surprises to have variables suddenly made
: inaccessable by being a call by value argument to a function.

There is no such thing as "the law of least surprise". What is
surprising to one person is unarguable common sense to another.

I find it surprising that some fools think that the pointer must have
any kind of validity after it is freed. Once freed it's GONE, folks.

---
Bill                    { uunet | novavax | ankh | sunvice } !twwells!bill
bill@twwells.com

bill@twwells.com (T. William Wells) (09/22/89)

In article <11121@smoke.BRL.MIL> gwyn@brl.arpa (Doug Gwyn) writes:
: It's not very hard to factor in the notion that a pointer is "poison"
: when it no longer points to valid storage.  One would think that
: "well-written code" would already follow that model, which is
: compatible with a wider range of environments.

Mine always did. Even before I learned to think about portability. It
just never occured to me that a freed pointer was anything but
nonsense.

---
Bill                    { uunet | novavax | ankh | sunvice } !twwells!bill
bill@twwells.com

dhesi@mv1.UUCP (Rahul Dhesi) (09/22/89)

In article <137@bbxsda.UUCP> scott@bbxsda.UUCP (Scott Amspoker) writes:
>It
>was pointed out that there are no new portability issues that haven't
>been there all along.  In other words, this pointer thing is not new.
>However, nobody has been able to come up with an example where this
>was actually a problem (unless I missed something).  Maybe it is 
>because of the way people write their programs.  Maybe it is because
>C implementers didn't have a standard to hide behind and kept a wide
>safety margin in their products.  

It's probably because portability is not a boolean attribute, and some
things -- like examining a pointer after it has been given to free() --
correspond closely enough to the way most computer systems work that
they are as near being perfectly portable as makes little difference.

The purist point of view seems to be that portability an absolute
attribute, and a C construct either "is" or "isn't" portable.  The
result is the typical "is!" "isn't!" portability war that you often see
here.

In actual practice, the question one really ought to ask is not "is it
portable?" but "how portable is it?"

So how portable is a C program that examines a freed pointer without
dereferencing it?  Probably not 100.000% portable, but close enough
that it makes little difference.

Before worrying about architectures that might not like the type of
thing being discussed, worry about defining too many long macros, or
having long literal strings, or having long external names, or using
#if rather than #ifdef, or doing any type of token-pasting (whether or
not ANSI-conformant), or using data structures that are bigger than 64
kilobytes, or using a word inside a literal string after defining a
macro called by the same name, or continuing a macro definition to the
next line, or any of hundreds of apparently-innocent things that make
C programs less than perfectly portable in the real world.  Then, if
you have any time left, you can relax over lunch and dwell on largely
theoretical endeavors...such as wondering if examining a freed pointer
will give you a C program that might not run on *every* possible
computer system that might exist in the entire world for the next
century.
--
Rahul Dhesi <dhesi%cirrusl@oliveb.ATC.olivetti.com>
UUCP:  oliveb!cirrusl!dhesi

scott@bbxsda.UUCP (Scott Amspoker) (09/22/89)

In article <871@cirrusl.UUCP> dhesi%cirrusl@oliveb.ATC.olivetti.com (Rahul Dhesi) writes:
>In actual practice, the question one really ought to ask is not "is it
>portable?" but "how portable is it?"
>
>So how portable is a C program that examines a freed pointer without
>dereferencing it?  Probably not 100.000% portable, but close enough
>that it makes little difference.

Thank you for putting it in plain language (I seem to have a problem
with that at times).  It is difficult to develop a complex application
without treading into the twilight zone of "implementation dependent"
features.  That's why so many of us have "locals.h" files.
Sooner or later you have to quit being paranoid and start getting some
real work done.

I think many readers of this newsgroup are afraid that their 99% portable
programs will become 50% portable overnight because the ANSI draft
officially tells C implementors that anything goes in certain areas that
were once relatively safe.  However, I don't think that will happen
(that gets back to market pressure).

-- 
Scott Amspoker
Basis International, Albuquerque, NM
(505) 345-5232

scott@bbxsda.UUCP (Scott Amspoker) (09/22/89)

 >: It's not very hard to factor in the notion that a pointer is "poison"
 >: when it no longer points to valid storage.  One would think that
 >: "well-written code" would already follow that model, which is
 >: compatible with a wider range of environments.

 >Mine always did. Even before I learned to think about portability. It
  ^^^^^^^^^^^^^^^
 >just never occured to me that a freed pointer was anything but
 >nonsense.

How do you know?  Although a bad example using the free() call was
what started all of this the discussion has turned to the more general
problems of handling pointers.  I don't doubt that your calls to 
free() are clean.  What about every other place that pointers are
used?  How many other programmers are working on your project with
you?  If you did handle a bad pointer at one point (even though
program logic would not deference that pointer under those conditions)
how would you know (without personally examining every line of code)?
Chances are your machine is allowing it so there would be no trap
generated.

This isn't a flame.  :-)  :-)  <- see, I'm smiling

It's just that there is a lot more at issue here than a call to free().
There is a comment above about being compatible with a "wider range
of environments".  Since nobody has yet given a real world example
of "pointer poison", just how much "wider" a range are we talking about?

-- 
Scott Amspoker
Basis International, Albuquerque, NM
(505) 345-5232

kim@kim.misemi (Kim Letkeman) (09/23/89)

In article <138@bbxsda.UUCP> scott@bbxsda.UUCP (Scott Amspoker) writes:
> In article <990@kim.misemi> kim@kim.misemi (Kim Letkeman) writes:
> >
> >Can you tell me how you would go about causing an 80286 to trap on
> >load of a "bad" value in the ES That's? This register is internal to
> >the CPU and I doubt that there are any values that would cause a trap
> >to be raised simply by loading them.

> When you load a segment register on an 80286/80386 in *protected* mode
> the CPU actually goes into an in-memory table an load a segment
> descriptor in to an on-chip cache so that subsequent address translation
> is reasonably fast.  If the segment register is loaded with a value
> that does not have an associated segment descriptor then a trap
> occurs.  This normally doesn't cause a problem.  ES is not
> preserved across CALLs.  C compilers on the 80286 are not inclined
> to load ES just to perform a pointer move or compare because

>    1) segment register loads are expensive
>    2) there is no segment register compare instruction

> In *real* mode (MS-DOS) there is no possibility of a bad address trap.

Makes sense. This does seem to add a bit of support for one of the
main themes of this thread (that one should not use pointers after
free() has been called.) 

C compilers run on so many different chips and it is impossible for
every one to know the peculiarities of every implementation. In the
portion of my previous article that is quoted here, I drew a
conclusion based on years of experience with M68000's and with 80x86's
in *real* mode.

I still feel, as posted early in this thread (Aug 24 approx) that it
is not worth the risk to use a pointer once you have disposed of it.

Thanks Scott




-- 
Kim Letkeman    uunet!mitel!spock!kim

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/23/89)

In article <871@cirrusl.UUCP> dhesi%cirrusl@oliveb.ATC.olivetti.com (Rahul Dhesi) writes:
>So how portable is a C program that examines a freed pointer without
>dereferencing it?  Probably not 100.000% portable, but close enough
>that it makes little difference.

People who have implementations like that wouldn't agree with you.

>Then, if
>you have any time left, you can relax over lunch and dwell on largely
>theoretical endeavors...such as wondering if examining a freed pointer
>will give you a C program that might not run on *every* possible
>computer system that might exist in the entire world for the next
>century.

This is NOT an ivory-tower issue.  I certainly wouldn't be wasting
effort trying to explain it to you guys if it were.

I personally believe that architectures for which this is a real issue
SHOULD be designed, not just for dead-pointer access trapping, but in
order to obtain other benefits of tagged architectures.  The more code
there is that will fail miserably on a reasonable tagged architecture,
the more excuse there is for computer companies to not produce such
architectures.  Not too long ago I had a design engineer at one such
company inform me that the opportunity to produce a bit-addressable
architecture (which would be immensely helpful for many applications)
was being lost due to the Catch-22 argument that existing code would
not be able to use the bit-addressability feature.

Thus, arguing that computer should continue to look just like the
crufty pieces of junk you're currently used to can be to the long-term
detriment of the computing industry.  There is room for numerous
architectural improvements, and to the extent that they can be
anticipated and accommodated in programming language specifications,
the better off we all will be.

bill@twwells.com (T. William Wells) (09/23/89)

In article <154@bbxsda.UUCP> scott@bbxsda.UUCP (Scott Amspoker) writes:
:  >: It's not very hard to factor in the notion that a pointer is "poison"
:  >: when it no longer points to valid storage.  One would think that
:  >: "well-written code" would already follow that model, which is
:  >: compatible with a wider range of environments.

I do sort of object to the sequence you imply here: first, you did
not properly attribute it. Second, you made it appear that I said that
my code never dealt properly with freed pointers. I doubt that your
intent was malicious, but I'd appreciate more care in the future.

:  >Mine always did. Even before I learned to think about portability. It
:   ^^^^^^^^^^^^^^^
:  >just never occured to me that a freed pointer was anything but
:  >nonsense.
:
: How do you know?  Although a bad example using the free() call was
: what started all of this the discussion has turned to the more general
: problems of handling pointers.  I don't doubt that your calls to
: free() are clean.  What about every other place that pointers are
: used?  How many other programmers are working on your project with
: you?  If you did handle a bad pointer at one point (even though
: program logic would not deference that pointer under those conditions)
: how would you know (without personally examining every line of code)?
: Chances are your machine is allowing it so there would be no trap
: generated.

You missed the point. I did *not* say that my programs never
referenced a pointer after it was freed. Don't I wish. What I'm saying
is that I never *designed* a program to do that. And it never would
have occured to me to even try. A freed pointer is *gone*.

---
Bill                    { uunet | novavax | ankh | sunvice } !twwells!bill
bill@twwells.com

scott@bbxsda.UUCP (Scott Amspoker) (09/25/89)

Someone whose name I keep losing writes:
>I do sort of object to the sequence you imply here: first, you did
>not properly attribute it. Second, you made it appear that I said that
>my code never dealt properly with freed pointers. I doubt that your
>intent was malicious, but I'd appreciate more care in the future.

I apologize for the confusion about source of the orginal quote, which was
from neither you (whoever you are) nor me.  For some reason, everytime
I try to followup one of your postings my 'rn' program aborts back to
the shell.  This happens only with your postings (???).  What you are
reading now is not a "followup" per se but an attempt to look like a
followup via the Save command.  I obviously did a poor job at it.

>You missed the point. I did *not* say that my programs never
>referenced a pointer after it was freed. Don't I wish. What I'm saying
>is that I never *designed* a program to do that.

Then we are in agreement.  How would you feel, then, if your ported
your code to some machine that wasn't so forgiving; and when you
complained, someone told you that you were lazy and shouldn't have
written the code that way in the first place?  None of us want to
pay so dearly for something so trivial whether we intended it or
not.  Remember, we're talking about more than free() calls here.

BTW: Since I am having so much trouble with your postings (not
me personally :-) but 'rn' is having trouble.) I will have to
avoid further followups.  If we have more to discuss let's try
e-mail.

-- 
Scott Amspoker
Basis International, Albuquerque, NM
(505) 345-5232

idall@augean.OZ (Ian Dall) (09/26/89)

In article <11131@smoke.BRL.MIL> gwyn@brl.arpa (Doug Gwyn) writes:
>
>This is NOT an ivory-tower issue.  I certainly wouldn't be wasting
>effort trying to explain it to you guys if it were.
>
>I personally believe that architectures for which this is a real issue
>SHOULD be designed, not just for dead-pointer access trapping, but in
>order to obtain other benefits of tagged architectures.

I agree with your logic, but am unsure about the premise. If
disallowing traps on examining pointers to non-existant memory would
prevent the introduction of better machines, then I agree it is
worthwhile to allow the traps. But, I am unconvinced about the
advantages of such an architecture.

Please explain how allowing these traps allows for "better" hardware.
Follow up to comp.arch if it seems appropriate.

-- 
 Ian Dall           life (n). A sexually transmitted disease which afflicts
                              some people more severely than others.
idall@augean.oz

bill@twwells.com (T. William Wells) (09/28/89)

In article <165@bbxsda.UUCP> scott@bbxsda.UUCP (Scott Amspoker) writes:
: Someone whose name I keep losing writes:

Me. Bill Wells.

:                                             For some reason, everytime
: I try to followup one of your postings my 'rn' program aborts back to
: the shell.  This happens only with your postings (???).

I doubt it. The problem is probably with the long References: line.
My system, since I'm running C news, generates these looooong message
ids which quickly result in an oversized References: line. I'm going
to edit that line in this message, I'll bet you can follow up now.

: >You missed the point. I did *not* say that my programs never
: >referenced a pointer after it was freed. Don't I wish. What I'm saying
: >is that I never *designed* a program to do that.
:
: Then we are in agreement.  How would you feel, then, if your ported
: your code to some machine that wasn't so forgiving; and when you
: complained, someone told you that you were lazy and shouldn't have
: written the code that way in the first place?  None of us want to
: pay so dearly for something so trivial whether we intended it or
: not.  Remember, we're talking about more than free() calls here.

I'm not sure what you are talking about. The original posting in this
thread (mine) said that the C standard permits the compiler writer to
assume that you don't use a pointer after a free, and thus he can
have the compiler generate code that trashed the pointer value after
the free.

If I write the program so that it is not designed to reference
pointers after they are freed, assuming I get it right, it doesn't
matter whether the generated code trashes the pointer. If I get it
wrong, I have a bug, which I should assume will show up sooner or
later (a day later after the cost of fixing it goes astronomical :-),
and it still doesn't matter whether the compiler does this.

Actually, it does, a little: if in the compiler's C model freed
pointers still have valid values, my error won't cause the program to
violate that model immediately. But if the code does trash the
pointer, my error makes the program violate its model, such programs
are generally harder to debug. That is one of the reasons why, if I
do have a compiler that generates this kind of code, I'd like to be
able to turn it off. Of course, turning off optimizations is often a
good idea when debugging, anyway.

On the other hand, if I write my code assuming that the value of a
freed pointer doesn't get trashed, I'm OK on "normal" compilers (if
I'm reasonably careful, there are still things that will break even
on those), but I've a big loser if I port the program to one that
does this optimization.

The conclusion is that I should avoid referencing the pointer after
it is freed, so that this perfectly legit optimization won't break my
code.

Also, as I've implied elsewhere, there is a much better reason than
this for not touching the pointer after it is freed: *conceptually*,
once a pointer is freed, its value is meaningless. Pointers (other
than null pointers) have meaning only insofar as they have an object
at the other end.

: BTW: Since I am having so much trouble with your postings (not
: me personally :-) but 'rn' is having trouble.) I will have to
: avoid further followups.  If we have more to discuss let's try
: e-mail.

Let's see if this fixes the problem.

---
Bill                    { uunet | novavax | ankh | sunvice } !twwells!bill
bill@twwells.com