ed@mtxinu.COM (Ed Gould) (10/03/89)
Is the following code conformant? It's clear that it's not legal to
dereference the pointer in its "illegal" state, but is the p++ line
guaranteed to return it to a valid value? What would it (be expected
to) print?
void
f() {
char bug[100];
char *p;
p = buf;
p--; /* p contains an illegal value: &buf[-1] */
p++; /* hopefully, now p == &buf[0] */
if(p == buf)
printf("It worked\n");
else
printf("It failed\n");
}
henry@utzoo.uucp (Henry Spencer) (10/03/89)
In article <1009@mtxinu.UUCP> ed@mtxinu.COM (Ed Gould) writes: >Is the following code conformant? It's clear that it's not legal to >dereference the pointer in its "illegal" state, but is the p++ line >guaranteed to return it to a valid value? ... > p = buf; > p--; /* p contains an illegal value: &buf[-1] */ > p++; /* hopefully, now p == &buf[0] */ The effect of the `p--' line is undefined, so all bets are off. Pointer arithmetic (not dereferencing) is guaranteed to be well-behaved when a pointer just past the *end* of the array is involved, but no such promises are made about pointers just before the *beginning*. It may work; it may dump core; it may yield random results. -- Nature is blind; Man is merely | Henry Spencer at U of Toronto Zoology shortsighted (and improving). | uunet!attcan!utzoo!henry henry@zoo.toronto.edu
gwyn@smoke.BRL.MIL (Doug Gwyn) (10/04/89)
In article <1009@mtxinu.UUCP> ed@mtxinu.COM (Ed Gould) writes: >Is the following code conformant? It's clear that it's not legal to >dereference the pointer in its "illegal" state, but is the p++ line >guaranteed to return it to a valid value? It's not even "legal" to compute an invalid address, whether or not it is dereferenced. Your example has implementation-dependent behavior; it is not too unlikely that it would even abort under some circumstances. Pointers one past the end of an array are valid, but not pointers before the beginning of an array.
scjones@sdrc.UUCP (Larry Jones) (10/04/89)
In article <1009@mtxinu.UUCP>, ed@mtxinu.COM (Ed Gould) writes: > Is the following code conformant? It's clear that it's not legal to > dereference the pointer in its "illegal" state, but is the p++ line > guaranteed to return it to a valid value? What would it (be expected > to) print? > > p = buf; > p--; /* p contains an illegal value: &buf[-1] */ > p++; /* hopefully, now p == &buf[0] */ Nope. Once you've decremented p you've entered the twilight zone of undefined behaviour where your program could dump core, play the Star Spangled Banner, or continue on. If it continued, the increment may or may not set it pointing to buf again. ---- Larry Jones UUCP: uunet!sdrc!scjones SDRC scjones@SDRC.UU.NET 2000 Eastman Dr. BIX: ltl Milford, OH 45150-2789 AT&T: (513) 576-2070 "I have plenty of good sense. I just choose to ignore it." -Calvin
doug@letni.UUCP (Doug Davis) (10/04/89)
In article <1009@mtxinu.UUCP> ed@mtxinu.COM (Ed Gould) writes: > void > f() { > char bug[100]; ^^^^^^^^^^ I assume you mean buf here, right? > char *p; > > p = buf; > > p--; /* p contains an illegal value: &buf[-1] */ ^^^ Opps, You have now entered the land of illegal addresses, normally called the twilight zone, incrementing the pointer is no longer guarenteed to be == &buf[0], that is installation dependent. Most likely it will == something really weird and you get a core dump. Which one you get is left as an exercise to the reader. doug __ Doug Davis/1030 Pleasant Valley Lane/Arlington/Texas/76015/817-467-3740 {sys1.tandy.com, motown!sys1, uiucuxc!sys1 lawnet, attctc, texbell} letni!doug "ack, pifft! <ZOT> RRREEEEAAAAAWWWWOOOOOO!" -- Sound effects from the "Bill The Cat'lprod(TM)" Commercial, Available in better hardware stores near you.
walter@hpclwjm.HP.COM (Walter Murray) (10/05/89)
Ed Gould: > Is the following code conformant? It's clear that it's not legal to > dereference the pointer in its "illegal" state, but is the p++ line > guaranteed to return it to a valid value? Henry Spencer: > The effect of the `p--' line is undefined, so all bets are off. Doug Gwyn: > It's not even "legal" to compute an invalid address, whether or not > it is dereferenced. I agree with Henry and Doug (generally a safe bet!), but I do think the wording of 3.3.6 might be misleading: "Unless both the pointer operand and the result point to elements of the same array object, or the pointer operand points one past the last element of an array object and the result points to an element of the same array object, the behavior is undefined if the result is used as an operand of the unary * operator." Doesn't this imply rather strongly that it IS legal to compute an invalid address, as long as it isn't dereferenced? Walter Murray walter@hpda.HP.COM ---
scott@bbxsda.UUCP (Scott Amspoker) (10/06/89)
In article <4199@letni.UUCP> doug@letni.LawNet.Com (Doug Davis) writes: >In article <1009@mtxinu.UUCP> ed@mtxinu.COM (Ed Gould) writes: >> void >> f() { >> char bug[100]; > ^^^^^^^^^^ I assume you mean buf here, right? >> char *p; >> >> p = buf; >> >> p--; /* p contains an illegal value: &buf[-1] */ > ^^^ Opps, You have now entered the land of illegal addresses, > normally called the twilight zone, incrementing the > pointer is no longer guarenteed to be == &buf[0], that > is installation dependent. Most likely it will == something > really weird and you get a core dump. Which one you get > is left as an exercise to the reader. Agreed, it is undefined. However *most likely* it will work just fine (based on the several dozen systems I've worked with). I would recommend avoiding it though. -- Scott Amspoker Basis International, Albuquerque, NM (505) 345-5232
davidsen@crdos1.crd.ge.COM (Wm E Davidsen Jr) (10/06/89)
In article <12570028@hpclwjm.HP.COM>, walter@hpclwjm.HP.COM (Walter Murray) writes: | Doug Gwyn: | | > It's not even "legal" to compute an invalid address, whether or not | > it is dereferenced. While this is obviously true, I have never understood the rationale of this decision. Given that (a) there are existing programs which do this, for reasons other than sloppy programming, (b) most implementations happily allow this, and (c) if you are allowed to declare an auto pointer at all then obviously the hardware supports uninitialized pointers, I fail to see what benefit is gained. Yes, I do understand the diference between calculating such a pointer and dereferencing it, and obviously dereferencing it is obviously non-portable. The only reason I have heard is the argument that there might be (or is) a machine such that (a) it checks addresses stored in address registers when stored, (b) the checking can't be turned off by software, and (c) the machine is incapable of storing the address in memory or any other pointer and loading it into the pointer register only when dereference is needed. Could someone clarify this, since it certainly is not prevailing practice? -- bill davidsen (davidsen@crdos1.crd.GE.COM -or- uunet!crdgw1!crdos1!davidsen) "The world is filled with fools. They blindly follow their so-called 'reason' in the face of the church and common sense. Any fool can see that the world is flat!" - anon
henry@utzoo.uucp (Henry Spencer) (10/06/89)
In article <12570028@hpclwjm.HP.COM> walter@hpclwjm.HP.COM (Walter Murray) writes: >the wording of 3.3.6 might be misleading: "Unless both the pointer >operand and the result point to elements of the same array object, >or the pointer operand points one past the last element of an array >object and the result points to an element of the same array object, >the behavior is undefined if the result is used as an operand of >the unary * operator." Doesn't this imply rather strongly that it >IS legal to compute an invalid address, as long as it isn't >dereferenced? Read the previous sentence in 3.3.6: "If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise the behavior is undefined." -- Nature is blind; Man is merely | Henry Spencer at U of Toronto Zoology shortsighted (and improving). | uunet!attcan!utzoo!henry henry@zoo.toronto.edu
gwyn@smoke.BRL.MIL (Doug Gwyn) (10/07/89)
In article <12570028@hpclwjm.HP.COM> walter@hpclwjm.HP.COM (Walter Murray) writes:
-the wording of 3.3.6 might be misleading: "Unless both the pointer
-operand and the result point to elements of the same array object,
-or the pointer operand points one past the last element of an array
-object and the result points to an element of the same array object,
-the behavior is undefined if the result is used as an operand of
-the unary * operator." Doesn't this imply rather strongly that it
-IS legal to compute an invalid address, as long as it isn't
-dereferenced?
Not the way I read it in context, and certainly not the way it was
intended. There are a lot of constraints on pointer arithmetic in
the same paragraph; the cited sentence is merely one more constraint.
It IS undefined behavior if you try to access some place outside the
actual array. There are many other ways to obtain undefined behavior
during pointer arithmetic itself.
gwyn@smoke.BRL.MIL (Doug Gwyn) (10/07/89)
In article <868@crdos1.crd.ge.COM> davidsen@crdos1.UUCP (bill davidsen) writes: >In article <12570028@hpclwjm.HP.COM>, walter@hpclwjm.HP.COM (Walter Murray) writes: >| Doug Gwyn: >| > It's not even "legal" to compute an invalid address, whether or not >| > it is dereferenced. > While this is obviously true, I have never understood the rationale of >this decision. Given that (a) there are existing programs which do this, >for reasons other than sloppy programming, How could they? If a computation produces a genuinely meaningless result, how can a reasonable program rely on it? >(b) most implementations happily allow this, You mean, most implementations don't provide any sensible check for this. The worst bugs I've found in UNIX software were due to computing invalid pointers as an intermediate step in an algorithm. When it happened to work, it was BY ACCIDENT. When it failed, it was a MYSTERY (until I finally tracked it down). If you think that this is desirable behavior, then you're nuts. >(c) if you are allowed to declare an auto pointer at all then obviously >the hardware supports uninitialized pointers, Obviously? Allocation of storage is not the same as accessing its contents. >I fail to see what benefit is gained. The "benefit" is that faster, more natural C implementations are permitted on architectures where this is an issue. Another benefit is that we do not have to figure out rules for assigning meanings to inherently meaningless address-arithmetic results, as we would be obliged to do if they were guaranteed legitimate by the standard. The final benefit is to bring the non-portable (and often unsafe) nature of such operations clearly before the eyes of the C programming public. >Could someone clarify this, since it certainly is not prevailing >practice? I have no idea what you mean by "prevailing practice". The only significant prevailing C "standard" was K&R 1st Edition Appendix A. It certainly did not promise that randomly computed addresses would always be valid pointer values.
scott@bbxsda.UUCP (Scott Amspoker) (10/07/89)
In article <868@crdos1.crd.ge.COM> davidsen@crdos1.UUCP (bill davidsen) writes: >| Doug Gwyn: >| >| > It's not even "legal" to compute an invalid address, whether or not >| > it is dereferenced. > While this is obviously true, I have never understood the rationale of >this decision. Given that (a) there are existing programs which do this, >for reasons other than sloppy programming, (b) most implementations >happily allow this, and (c) if you are allowed to declare an auto >pointer at all then obviously the hardware supports uninitialized >pointers, I fail to see what benefit is gained. We just got through an *extremely* long thread in comp.lang.c regarding this issue (it eventually just fizzled out). The basic idea is that a pointer variable containing a bad (or uninitialized) address could be loaded into an address register which, on some architectures, could cause a "bad address" trap. For example, the 286/386 CPUs will trap if you load just any 'ol garbage into a segment register. Since handling a pointer variable might cause such a load operation you could get a trap even though you did not de-reference the pointer. This bothered many readers while other readers attempted to jusitfy such an "implementation specific" detail. What was once bad coding style now was considered a bug. Take the following code fragment as an example: my_proc() { register char *p; p = (char*)malloc(1000); free(p); /* free never returns but core dumps instead - why? */ } This seemingly innocent code could possibly error out according to the "rules of comformance" presented by some readers. Here's why: a) pointer p is possibly in an address register which might be sensitive to bad addresses. b) p is assigned a valid address by malloc() - no problem. c) the free() call may return the freed memory block to the host system. It was agreed that a conforming implementation had that right. Therefore, the address in p might no longer be part of the process's address map and therefore invalid. d) Upon return from free() the (now invalid) value in p is popped off the stack back into its address register and... TRAP! Don't worry - all is not lost. No one was able to come up with a real world example of something like this. In other words - standards and ANSI drafts aside - you probably will not get into trouble unless you actually try to *de-reference* a bad pointer. -- Scott Amspoker Basis International, Albuquerque, NM (505) 345-5232
gwyn@smoke.BRL.MIL (Doug Gwyn) (10/07/89)
In article <217@bbxsda.UUCP> scott@bbxsda.UUCP (Scott Amspoker) writes:
-my_proc()
- {
- register char *p;
- p = (char*)malloc(1000);
- free(p); /* free never returns but core dumps instead - why? */
- }
-This seemingly innocent code could possibly error out according to the
-"rules of comformance" presented by some readers.
NO NO NO. You have mispresented the argument. So long as malloc()
(assumed to be properly declared!) doesn't return a null pointer,
the above will work in ALL conforming implementations. The trouble
arises only when after the free() the pointer p (NOT what it points
to, that's inarguably invalid) continues to be examined or otherwise
manipulated by the program.
-Don't worry - all is not lost. No one was able to come up with a real
-world example of something like this. In other words - standards and
-ANSI drafts aside - you probably will not get into trouble unless you
-actually try to *de-reference* a bad pointer.
Nobody came up with YOUR example, but there were several examples
posted of genuine computer architectures where continued access of
an invalid pointer value would cause problems.
ok@cs.mu.oz.au (Richard O'Keefe) (10/07/89)
In article <868@crdos1.crd.ge.COM> davidsen@crdos1.UUCP (bill davidsen) writes: >| Doug Gwyn: >| > It's not even "legal" to compute an invalid address, whether or not >| > it is dereferenced. > While this is obviously true, I have never understood the rationale of >this decision. Given that (a) there are existing programs which do this, >for reasons other than sloppy programming, (b) most implementations >happily allow this, and (c) if you are allowed to declare an auto >pointer at all then obviously the hardware supports uninitialized >pointers, I fail to see what benefit is gained. (b) The implementations which allowed it don't have to stop allowing it. (c) Not so; the compiler might quietly initialise every pointer variable to a valid address. (Auto variables might be allocated by pushing the current value of the SP on the stack, for example.) In article <217@bbxsda.UUCP>, scott@bbxsda.UUCP (Scott Amspoker) writes: > We just got through an *extremely* long thread in comp.lang.c regarding > this issue (it eventually just fizzled out). ... For example, the > 286/386 CPUs will trap if you load just any 'ol garbage into a segment > register. Since handling a pointer variable might cause such a load > operation you could get a trap even though you did not de-reference the > pointer. I participated in that thread. Basically it fizzled out because it was quite clear that the restriction isn't going to go away, so there wasn't any point in talking further. But it also became clear that the 80286 and 80386 are *NOT* examples of the problem; you wouldn't do pointer comparisons or pointer arithmetic in a segment register on an 80286 because you _can't_. > Don't worry - all is not lost. No one was able to come up with a real > world example of something like this. There is a practical case where this _is_ likely to come up. C interpreters. There are already C interpreters which do things like checking that a pointer doesn't point to anywhere wild and that pointer comparisons involve pointers into the same array. It would not be surprising if ANSI C interpreters make this check too. You can, if necessary, make your array one element longer than you need.
chris@mimsy.UUCP (Chris Torek) (10/07/89)
[original question was about code similar to int array[K]; p = &array[0]; /* ok */ p--; /* invalid */ ] >In article <217@bbxsda.UUCP>, scott@bbxsda.UUCP (Scott Amspoker) writes: >>We just got through an *extremely* long thread in comp.lang.c regarding >>this issue ... Actually, that thread was arguing about the invalid pointer value produced by freeing a valid malloc()-derived pointer. These are in fact entirely different things: the freed pointer became invalid by the freeing action, even though its bit pattern did not change, while the pointer above became invalid by a pointer-arithmetic operation (which presumably changed its bit pattern). In article <2322@munnari.oz.au> ok@cs.mu.oz.au (Richard O'Keefe) writes: >I participated in that thread. Basically it fizzled out because it was >quite clear that the restriction isn't going to go away, so there wasn't >any point in talking further. But it also became clear that the 80286 >and 80386 are *NOT* examples of the problem; you wouldn't do pointer >comparisons or pointer arithmetic in a segment register on an 80286 >because you _can't_. Actually, in huge model, p-- really does do arithmetic on a value that might be automatically reloaded into a segment register. For instance, void f(void) { extern char foo[]; extern int g(int); register char *p = foo + g(0); while (p != foo + g(1)) p--; /* strange timing loop, perhaps */ *p++ = 'a'; *p++ = 'b'; *p = 'c'; } is as likely to use `es:di' for p as any other register pair (after all, it is *a* pointer, and is the *only* pointer, and is used *as a* pointer). The comparison and decrement are in fact better done from memory or from si:di, but some compilers are likely to simply say `this looks like a pointer; we are in huge model; use es:di'. >You can, if necessary, make your array one element longer than you need. This is in fact the only certain solution for for (p = &array[K]; --p >= array;) (i.e., change it to some_type array_[K+1]; /* dummy element at 0 */ #define array (array_ + 1) /* subscript range [-1..K-1] */ for (p = &array[K]; --p >= array;) or something equivalent). Otherwise, if some_type is, e.g., typedef struct enormous { char a[60000]; } sometype; the operation `--p' will subtract 60,000 bytes from p on a VAX or Sun; this could easily produce an underflow (32k of text, e.g.). On a VAX, it is possible to trap on integer underflow, including pointer underflow, so you could rig a system to produce a runtime trap here. -- In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163) Domain: chris@cs.umd.edu Path: uunet!mimsy!chris
flaps@dgp.toronto.edu (Alan J Rosenthal) (10/08/89)
scott@bbxsda.UUCP (Scott Amspoker) writes: >What was once bad coding style now was considered a bug. Take the following >code fragment as an example: > >my_proc() > { > register char *p; > > p = (char*)malloc(1000); > free(p); /* free never returns but core dumps instead - why? */ > } No, no, no. It was never said that this code fragment wasn't conforming. It is, other than the missing declaration of malloc() which I assume you assumed was present previously in the file. free() has to work correctly. The user cannot dereference invalid pointers, but the compiler can if it knows that it is safe to do so, and must not dereference invalid pointers if it doesn't know whether or not it is safe. And this fragment was never considered bad coding style, again apart from the fact that you are mallocing memory and not doing anything with it. You seem to be claiming that use of free() is now non-portable and was always considered bad coding style. ajr
scott@bbxsda.UUCP (Scott Amspoker) (10/09/89)
In article <1989Oct7.131404.656@jarvis.csri.toronto.edu> flaps@dgp.toronto.edu (Alan J Rosenthal) writes: >scott@bbxsda.UUCP (Scott Amspoker) writes: >>What was once bad coding style now was considered a bug. Take the following >>code fragment as an example: >> >>my_proc() >> { >> register char *p; >> >> p = (char*)malloc(1000); >> free(p); /* free never returns but core dumps instead - why? */ >> } >No, no, no. It was never said that this code fragment wasn't conforming. It It was never said that *this* particular example wasn't conforming. However, as I pointed out in my posting, the various "rules" that were stated in the prior thread would lead to the conclusion that this example was non-conforming. I presented each rule leading to that conclusion. Please explain which of the rules you disagree with. >is, other than the missing declaration of malloc() which I assume you assumed >was present previously in the file. free() has to work correctly. The user ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Precisely - so is there something wrong with the ANSI draft or what? >cannot dereference invalid pointers, but the compiler can if it knows that it >is safe to do so, and must not dereference invalid pointers if it doesn't know >whether or not it is safe. No pointer dereferencing took place at all in the example. (Now you know why the prior thread went on so long :-). >And this fragment was never considered bad coding style, again apart from the >fact that you are mallocing memory and not doing anything with it. You seem to This was a trivial example trying to make a point. I don't see any reason to clutter it with "busy code" to make it look real. It is not too hard to imagine that something "comforming" was done between malloc() and free(). >be claiming that use of free() is now non-portable and was always considered >bad coding style. I am saying that it is possible to come to the conclusion that free() is not portable depending on how you interpret the ANSI draft. I was pointing out something I thought was absurd. I expect the above program (assuming the proper declaration of malloc() and that there is available memory) to *always* work no matter what. -- Scott Amspoker Basis International, Albuquerque, NM (505) 345-5232
msb@sq.sq.com (Mark Brader) (10/10/89)
> > It's not even "legal" to compute an invalid address, whether or not > > it is dereferenced. > ... if you are allowed to declare an auto pointer at all > then obviously the hardware supports uninitialized pointers ... No; the implementation could elect to initialize everything, say to a null pointer, as part of the action of bringing the variable into existence in addressable space. (And remember, a null pointer could be implemented as a pointer to a particular anonymous object somewhere.) -- Mark Brader "You have a truly warped mind. SoftQuad Inc., Toronto I admire that in a person." utzoo!sq!msb, msb@sq.com -- Bill Davidsen This article is in the public domain.
scott@bbxsda.UUCP (Scott Amspoker) (10/10/89)
In article <11234@smoke.BRL.MIL> gwyn@brl.arpa (Doug Gwyn) writes: >In article <217@bbxsda.UUCP> scott@bbxsda.UUCP (Scott Amspoker) writes: >-my_proc() >- { >- register char *p; >- p = (char*)malloc(1000); >- free(p); /* free never returns but core dumps instead - why? */ >- } >-This seemingly innocent code could possibly error out according to the >-"rules of comformance" presented by some readers. > >NO NO NO. You have mispresented the argument. So long as malloc() >(assumed to be properly declared!) doesn't return a null pointer, >the above will work in ALL conforming implementations. The trouble >arises only when after the free() the pointer p (NOT what it points >to, that's inarguably invalid) continues to be examined or otherwise >manipulated by the program. As my original posting explained, the pointer *is* manipulated by popping it off the stack upon return from free(). The original posting explained every step that takes place leading up to the core dump. If you believe that any of those steps violates the ANSI draft then please state which one. >-Don't worry - all is not lost. No one was able to come up with a real >-world example of something like this. >Nobody came up with YOUR example, but there were several examples >posted of genuine computer architectures where continued access of >an invalid pointer value would cause problems. I won't rehash this since this whole pointer thing was a topic off the longest thread I have ever seen in comp.lang.c. Unless I missed a posting on the other thread, *no one* was able to provide a specific example and an architecture *and* an associated C implementation on *that* architecture that created such a problem for C programs. In other words, the code generated by the C compiler did not tempt Murphy's Law even though the architecture had the potential. If someone out there knows of such an implementation please share it with us. I am interested in seeing the assembly code output of a C routine the bombs by *assigning* or *comparing* bad pointer. -- Scott Amspoker Basis International, Albuquerque, NM (505) 345-5232 unmvax.cs.unm.edu!bbx!bbxsda!scott
davidsen@crdos1.crd.ge.COM (Wm E Davidsen Jr) (10/11/89)
In article <11232@smoke.BRL.MIL>, gwyn@smoke.BRL.MIL (Doug Gwyn) writes: | [ read the original ] | | How could they? If a computation produces a genuinely meaningless | result, how can a reasonable program rely on it? Consider: a program is going to read some LARGE number of values in the range 30000-30006. It wants to count the number of occurences of each value. Therefore: long ShortVect[10] = {0,0,0,0,0,0,0,0,0,0}, *Ptr, Val; Ptr = &ShortVect[-30000]; while ((Val = MyRead()) > 0) Ptr[Val]++; Note that by creating the pointer to an invalid address we now can add the value as a subscript. The address modified is *always* valid (if the input is in range), the vector doesn't need to be 30007 in length, and you don't have to subtract 30000 each time. I make no claim that this is the only way to do this, simple that there are programs around which actually do use this trick (I saw it in a net program). I submit that this is neither sloppy programming or meaningless, and that it is "not illegal" by K&R 1st Ed. It's a trick to save some space and CPU, something anyone on a small machine tries to do. | | was a MYSTERY (until I finally tracked it down). If you think that | this is desirable behavior, then you're nuts. That's certainly keeping the discussion on a high technical plane. | >I fail to see what benefit is gained. | | The "benefit" is that faster, more natural C implementations are | permitted on architectures where this is an issue. Please identify the machines in question and quantify the saving. | | >Could someone clarify this, since it certainly is not prevailing | >practice? | | I have no idea what you mean by "prevailing practice". Prevailing practice means what it says, that before compilers were modified to make it illegal such code would work on most machines, such as Sun, Vax, PC, Cray, PDP-11, etc. I think that fairly represents at least 80% of the machines and users running pre-ANSI C. -- bill davidsen (davidsen@crdos1.crd.GE.COM -or- uunet!crdgw1!crdos1!davidsen) "The world is filled with fools. They blindly follow their so-called 'reason' in the face of the church and common sense. Any fool can see that the world is flat!" - anon
terryl@tekcrl.LABS.TEK.COM (10/11/89)
In article <231@bbxsda.UUCP> scott@bbxsda.UUCP (Scott Amspoker) writes: +In article <11234@smoke.BRL.MIL> gwyn@brl.arpa (Doug Gwyn) writes: + >In article <217@bbxsda.UUCP> scott@bbxsda.UUCP (Scott Amspoker) writes: + >-my_proc() + >- { + >- register char *p; + >- p = (char*)malloc(1000); + >- free(p); /* free never returns but core dumps instead - why? */ + >- } + >-This seemingly innocent code could possibly error out according to the + >-"rules of comformance" presented by some readers. + > + >NO NO NO. You have mispresented the argument. So long as malloc() + >(assumed to be properly declared!) doesn't return a null pointer, + >the above will work in ALL conforming implementations. The trouble + >arises only when after the free() the pointer p (NOT what it points + >to, that's inarguably invalid) continues to be examined or otherwise + >manipulated by the program. + +As my original posting explained, the pointer *is* manipulated by +popping it off the stack upon return from free(). The original +posting explained every step that takes place leading up to the core +dump. If you believe that any of those steps violates the ANSI draft +then please state which one. Um, err, how about the one that says C arguments are "call by value, and not by reference". If I remember correctly, your initial argument said that p could live in a special address register, then placed on the stack for the call to free(), and then popped off of the stack and put back in that special address register; that's call by value/result, not call by value, so the last step (put back in that special address register) should NOT be done at all.....
flaps@dgp.toronto.edu (Alan J Rosenthal) (10/11/89)
scott@bbxsda.UUCP (Scott Amspoker) writes: >The various "rules" that were stated in the prior thread would lead to the >conclusion that this example was non-conforming. I presented each rule leading >to that conclusion. Please explain which of the rules you disagree with. I don't recall a rule saying that the internal workings of the argument or register stack had to be implemented in a portable way, but if there was such a rule, that is the one I disagreed with. No (correct) rules about C describe the manner in which this stuff is implemented. >>The user cannot dereference invalid pointers, but the compiler can if ... >No pointer dereferencing took place at all in the example. (Now you know >why the prior thread went on so long :-). Oops, sorry, I meant manipulate invalid pointers. >>And this fragment was never considered bad coding style, again apart from the >>fact that you are mallocing memory and not doing anything with it. You seem >>to be claiming that use of free() is now non-portable and was always >>considered bad coding style. > >This was a trivial example trying to make a point. I don't see any reason >to clutter it with "busy code" to make it look real. It is not too hard >to imagine that something "conforming" was done between malloc() and free(). This appears to be a deliberate misinterpretation of my words. I was NOT complaining about the allocating of memory for no purpose. I assumed that something reasonable would be done between the malloc() and free(). My complaint was that Mr Amspoker was making wild assertions about free() having historically been considered bad coding style. ajr
scott@bbxsda.UUCP (Scott Amspoker) (10/11/89)
In article <4813@tekcrl.LABS.TEK.COM> terryl@tekcrl.LABS.TEK.COM writes: + +As my original posting explained, the pointer *is* manipulated by +popping it off the stack upon return from free(). The original +posting explained every step that takes place leading up to the core +dump. If you believe that any of those steps violates the ANSI draft +then please state which one. Um, err, how about the one that says C arguments are "call by value, >and not by reference". If I remember correctly, your initial argument said >that p could live in a special address register, then placed on the stack >for the call to free(), and then popped off of the stack and put back in >that special address register; that's call by value/result, not call by >value, so the last step (put back in that special address register) should >NOT be done at all..... It is customary for subroutines (such as free()) to save registers on the stack and restore their values upon return. NOTHING IS BEING PASSED BACK TO THE CALLER! The subroutine is merely restoring a register to its original value. The value of the pointer is being pushed as an argument to the subroutine. That value ultimately is discarded. However, the register containing the pointer may also be pushed to preserve its value. Please check some assembly code output of practically any C compiler of a function that uses register variables. -- Scott Amspoker Basis International, Albuquerque, NM (505) 345-5232 unmvax.cs.unm.edu!bbx!bbxsda!scott
mwm@eris.berkeley.edu (Mike (I'll think of something yet) Meyer) (10/11/89)
In article <976@crdos1.crd.ge.COM> davidsen@crdos1.UUCP (bill davidsen) writes:
< Prevailing practice means what it says, that before compilers were
<modified to make it illegal such code would work on most machines, such
<as Sun, Vax, PC, Cray, PDP-11, etc. I think that fairly represents at
<least 80% of the machines and users running pre-ANSI C.
I've as yet to run across a compiler that was modifed to match ANSI
that made code with undefined behavior illegal. Issue a warning, yes,
but not an error. I'm even used to them issuing only a warning if ANSI
says the situation in question should be an error.
So, can you provide a code sample with undefined behavior, and the
name of a compiler that used to accept such code, and now considers it
an error?
<mike
--
When logic and proportion have fallen softly dead, Mike Meyer
And the white knight is talking backwards, mwm@berkeley.edu
And the red queen's off her head, ucbvax!mwm
Remember what the dormouse said. mwm@ucbjade.BITNET
gwyn@smoke.BRL.MIL (Doug Gwyn) (10/11/89)
In article <976@crdos1.crd.ge.COM> davidsen@crdos1.UUCP (bill davidsen) writes: > Ptr = &ShortVect[-30000]; There are so many implementation assumptions required to make this work that it really cannot be considered sane portable programming practice. >I submit that this is neither sloppy programming or >meaningless, and that it is "not illegal" by K&R 1st Ed. I disagree on both counts. > That's certainly keeping the discussion on a high technical plane. Matters of surprise, unreliability, etc. are hardly very technical. > Please identify the machines in question and quantify the saving. No. I'll conduct my argument on my own terms, thank you very much. > Prevailing practice means what it says, that before compilers were >modified to make it illegal such code would work on most machines, such >as Sun, Vax, PC, Cray, PDP-11, etc. I think that fairly represents at >least 80% of the machines and users running pre-ANSI C. Nobody "modified compilers to make it illegal". The Standard simply makes it clearer than it may have previously been that you're asking for trouble when you write such code. On some implementations it will work all the time, on others it will work some of the time, and on others it will never work. That's due to variations in computer architecture, and to require that such dubious practice be made to work in ALL cases as you seem to expect it to would burden some implementations for insufficient reason. (Plus it would be exceedingly difficult to specify such behavior in the Standard; we had a hard enough time getting the wording for last+1 right.)
cpcahil@virtech.UUCP (Conor P. Cahill) (10/11/89)
In article <238@bbxsda.UUCP>, scott@bbxsda.UUCP (Scott Amspoker) writes: > It is customary for subroutines (such as free()) to save registers > on the stack and restore their values upon return. NOTHING IS > BEING PASSED BACK TO THE CALLER! The subroutine is merely > restoring a register to its original value. The value of the > pointer is being pushed as an argument to the subroutine. That > value ultimately is discarded. However, the register containing > the pointer may also be pushed to preserve its value. Please check > some assembly code output of practically any C compiler of a function > that uses register variables. Normally the subroutine only saves and restores register variables that it uses within the subroutine. The person writing the compiler and free() for a system wherein free() gives the space back to the system and a program would blow up if an invalid address is loaded into a register could do one of the following: 1. write free() so that it does not make use of any pointer registers that would be used for a "register char *p". so that it would not have to save/restore said register. 2. free() could examine the pointer registers and invalidate any of them that would now point to nonsense (before it makes them point to nonsense. This could be done automatically by the system, since it knows what the adress vars are and what the users address space is. 3. disreguard the "register" on "register char *p" or some sub-set thereof (like disreguard it if the pointer is passed out of the subroutine). These are just three mechanisms that I thought of off the top of my head and I ain't no compiler writer. So I would conclude that your example would not be a problem (any compiler that had you're behavior would be useless, so this discussion is really a waste of bandwidth). -- +-----------------------------------------------------------------------+ | Conor P. Cahill uunet!virtech!cpcahil 703-430-9247 ! | Virtual Technologies Inc., P. O. Box 876, Sterling, VA 22170 | +-----------------------------------------------------------------------+
scott@bbxsda.UUCP (Scott Amspoker) (10/11/89)
In article <1989Oct10.163732.3241@jarvis.csri.toronto.edu> flaps@dgp.toronto.edu (Alan J Rosenthal) writes: > My >complaint was that Mr Amspoker was making wild assertions about free() having >historically been considered bad coding style. Sorry if my point was not clear. I was refering to the notion of handling bad pointers - not the use of free(). The other thread I was refering to *began* with a bad example of the use of free(). Then one thing led to another. The next thing I knew people were saying that any handling of a bad pointer, whether it is dereferenced or not, is a *bug*. I always just considered it bad style. Several readers attempted to justify the notion by referencing various passages in the ANSI draft. I merely used those references in an absurd (but correct) way to show that things don't always add up correctly. Your indignation about my posting proves the point I was trying to make - it *is* absurd. Since free() is probably the only possible way for a *valid* pointer to suddenly become *invalid* there are some interesting possibilities. My example showed a case where a register variable contained a valid pointer that became invalid by a call to free(). Even though the program never uses the pointer again there is still the possibility of the pointer being implicitly handled - if anything, by a *signal handler* which must save and restore *all* registers. It seems reasonable that an implementer would have to take all of these things into account before making it a capital offense to handle a bad pointer (i.e. core dump). Arrangements must be made to allow special bad pointers such as NULL and the various values retuned by signal(). It strikes me as unlikely that such an implementation exists in this particular manner and is probably not worth the effort. -- Scott Amspoker Basis International, Albuquerque, NM (505) 345-5232 unmvax.cs.unm.edu!bbx!bbxsda!scott
afscian@violet.waterloo.edu (Anthony Scian) (10/11/89)
In article <238@bbxsda.UUCP> scott@bbxsda.UUCP (Scott Amspoker) writes: >In article <4813@tekcrl.LABS.TEK.COM> terryl@tekcrl.LABS.TEK.COM writes: > + > +As my original posting explained, the pointer *is* manipulated by > +popping it off the stack upon return from free(). The original ^^^^^^^^ > +posting explained every step that takes place leading up to the core > +dump. If you believe that any of those steps violates the ANSI draft > +then please state which one. > >> [erroneous argument about call-by-reference] >>that special address register; that's call by value/result, not call by >>value, so the last step (put back in that special address register) should >>NOT be done at all..... > >It is customary for subroutines (such as free()) to save registers >on the stack and restore their values upon return. NOTHING IS >BEING PASSED BACK TO THE CALLER! The subroutine is merely >restoring a register to its original value. The value of the >pointer is being pushed as an argument to the subroutine. That >value ultimately is discarded. However, the register containing >the pointer may also be pushed to preserve its value. Being the author of the posting in question, let me say that the example was supposed to show that it was possible to core dump even if the "freed" pointer was never explicitly referenced or dereferenced. Unfortunately, there is an important distinction between a system-dependent "free" and a pANSI "free". The example for OS/2 was specific to a low-level system "free" not pANSI "free". A pANSI compliant compiler cannot allow this to happen with the reserved library routine "free". SUMMARY: If the calling convention allows trapping pointer registers (on load of bad value) to be saved across calls, it is up to the compiler run-time library to allow the pointer passed to the pANSI routine "free" to be LOADable in an address register after the "free" PERIOD. Though POPable would be the minimum requirement in theory. 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
bruner@uicsrd.csrd.uiuc.edu (John Bruner) (10/12/89)
It is possible for a machine to push a register containing a bad pointer onto the stack as part of a procedure call and STILL treat the use of a bad pointer in user-written code as an error causing a fault. The former is an internal operation, while the latter is user-specified. To cite another example, presumably the operating system will not incur a fatal error if it tries to store a bad user pointer during a context switch. A machine which performs type checking in hardware may have some instructions which move data around in an unchecked fashion, but for maximum runtime type checking the compiler should generate pointer move instructions for user-specified pointer operations rather than generic move instructions. In addition, is less likely that such a machine would have an untyped compare than that it would have a simple untyped move, load, or store. The S-1 Project at the Lawrence Livermore National Laboratory built two machines with various degrees of tagged data. Pointers were not the same as integers, and the hardware would detect mixing of data types and cause faults. One of the machines also provided hardware- implemented segmentation, so that pointers had to lie within the valid range of a segment or a trap would occur. The pANS definitions for pointers make it possible to implement C on machines that don't resemble the vanilla machines which dominate the market today. The warning that it isn't portable is just that -- a warning. The same could be said for programs that always use "int" when they should use "long". The VAX/Sun/MIPS/whatever will let you do it, and your program will run on a lot of machines, but it isn't portable if you store values which are too big or call routines which expect long arguments (assuming no prototype is in scope). -- John Bruner Center for Supercomputing R&D, University of Illinois bruner@uicsrd.csrd.uiuc.edu (217) 244-4476
dfp@cbnewsl.ATT.COM (david.f.prosser) (10/12/89)
In article <976@crdos1.crd.ge.COM> davidsen@crdos1.UUCP (bill davidsen) writes: > Consider: a program is going to read some LARGE number of values in >the range 30000-30006. It wants to count the number of occurences of >each value. Therefore: > long ShortVect[10] = {0,0,0,0,0,0,0,0,0,0}, *Ptr, Val; > > Ptr = &ShortVect[-30000]; > while ((Val = MyRead()) > 0) Ptr[Val]++; > > Note that by creating the pointer to an invalid address we now can add >the value as a subscript. The address modified is *always* valid (if the >input is in range), the vector doesn't need to be 30007 in length, and >you don't have to subtract 30000 each time. This relies on the "recovery" of the pointer value through pointer arithmetic on the invalid value. On architectures in which overflow and underflow on pointers produce quiet wrap-around, you are correct that this sort of approach will work. > I make no claim that this is the only way to do this, simple that >there are programs around which actually do use this trick (I saw it in >a net program). I submit that this is neither sloppy programming or >meaningless, and that it is "not illegal" by K&R 1st Ed. It's a trick to >save some space and CPU, something anyone on a small machine tries to do. >| I have no idea what you mean by "prevailing practice". > Prevailing practice means what it says, that before compilers were >modified to make it illegal such code would work on most machines, such >as Sun, Vax, PC, Cray, PDP-11, etc. I think that fairly represents at >least 80% of the machines and users running pre-ANSI C. I claim that most (all?) of these can be caused to produce some sort of "noise" on overflow and/or underflow, most likely raising a computational exception signal. While, by default, some (all?) silently wrap, there has been no guarantee that this behavior must occur. As such, the prevailing practice does not guarantee that this approach will behave as desired. The X3J11 committee tried its best to mean it when it gave guarantees. It is entirely due to this sort of issue that asynchronous signal handling is so restrictive. This doesn't mean that you shouldn't write code like the above, just that you write it knowing what is required for an architecture to guarantee this not-strictly-portable approach. There are portability trade-offs made in virtually all C programs, unfortunately many are simply not recognized as such by the programmer. Dave Prosser ...not an official X3J11 answer...
mcdonald@aries.uiuc.edu (Doug McDonald) (10/12/89)
There are most certainly C implementations where previously valid pointers can become invalid without the program specifically doing anything. There is grave doubt that this example can be called a "hosted" C implementation. Actually, pointers coming from casts of ordinarily declared (static or automatic) variables, and from "malloc" will not die without a call to "free". But the operating system return pointers to other things (in the heap or within the OS itself) that most certainly WILL become invalid without notice during some future execution of OS code. This is known as Microsoft Windows. The manual views using "malloc" as closely related to child molestation. It claims you are supposed to use the calls that result in dying pointers. Doug McDonald
7103_300@uwovax.uwo.ca (10/12/89)
In article <238@bbxsda.UUCP>, scott@bbxsda.UUCP (Scott Amspoker) writes: > It is customary for subroutines (such as free()) to save registers > on the stack and restore their values upon return. NOTHING IS > BEING PASSED BACK TO THE CALLER! The subroutine is merely > restoring a register to its original value. The value of the > pointer is being pushed as an argument to the subroutine. That > value ultimately is discarded. However, the register containing > the pointer may also be pushed to preserve its value. Please check > some assembly code output of practically any C compiler of a function > that uses register variables. This is an implementation issue, not a language issue. Obviously if the architecture is such that popping that value off the stack will cause a fault, then either free() will have to keep the address valid or the value should not be popped. The user shouldn't be punished if the compiler attempts to load an invalid address; but if the user explicitly codes an invalid address calculation, then all bets are off. -- Eric R. Smith ersmith@uwovax.uwo.ca ersmith@uwovax.bitnet
gwyn@smoke.BRL.MIL (Doug Gwyn) (10/12/89)
In article <240@bbxsda.UUCP> scott@bbxsda.UUCP (Scott Amspoker) writes: >Since free() is probably the only possible way for a *valid* pointer to >suddenly become *invalid* ... Not at all. In any block-structured language with pointers, it is obvious how to have a valid pointer suddenly become invalid.
john@frog.UUCP (John Woods) (10/13/89)
In article <976@crdos1.crd.ge.COM>, davidsen@crdos1.crd.ge.COM (Wm E Davidsen Jr) writes: > | The "benefit" is that faster, more natural C implementations are > | permitted on architectures where this is an issue. > Please identify the machines in question and quantify the saving. > Prevailing practice means what it says, that before compilers were > modified to make it illegal such code would work on most machines, such > as VAX, VAX, VAX, VAX, VAX, etc. I think that fairly represents every single > machine and user running pre-ANSI C. Well, I've adjusted those last 2 sentences a teeny bit, but the meaning is exactly the same. To review the conclusion of the comp.lang.c.flame discussion: main() { extern void paint_memory_blue(char *p, int len); char *p = malloc(1000); /* OK. */ paint_memory_blue(p, 1000); /* it better accept NULL... */ free(p); /* OK */ if (p == NULL) /* Right here, a capability machine may fault. * And it is the programmer's fault for writing this. * If you don't know what a capability machine is, * STOP ARGUING WHAT MACHINES WILL OR WON'T DO!!! * In the words of Dennis M. Ritchie, * "This is non-negotiable." */ ; } Everybody clear now? -- John Woods, Charles River Data Systems, Framingham MA 508-626-1101 ...!decvax!frog!john, john@frog.UUCP, ...!mit-eddie!jfw, jfw@eddie.mit.edu
richard@aiai.ed.ac.uk (Richard Tobin) (10/13/89)
In article <11265@smoke.BRL.MIL> gwyn@brl.arpa (Doug Gwyn) writes: >Not at all. In any block-structured language with pointers, >it is obvious how to have a valid pointer suddenly become invalid. Doug is quite right as far as C goes - just take the address of an automatic variable. But it's not true for *any* block-structured language with pointers, because the language can prevent it. Algol 68 is an example of such a language. (REF != pointer flames >/dev/null) -- Richard -- Richard Tobin, JANET: R.Tobin@uk.ac.ed AI Applications Institute, ARPA: R.Tobin%uk.ac.ed@nsfnet-relay.ac.uk Edinburgh University. UUCP: ...!ukc!ed.ac.uk!R.Tobin
meissner@dg-rtp.dg.com (Michael Meissner) (10/14/89)
In article <240@bbxsda.UUCP> scott@bbxsda.UUCP (Scott Amspoker) writes: > Since free() is probably the only possible way for a *valid* pointer to > suddenly become *invalid* there are some interesting possibilities. Another possible way for a *valid* pointer to suddenly become *invalid* is if the pointer points to automatic storage and you exit the function creating the storage with either a return or a longjmp. -- Michael Meissner, Data General. If compiles where much Uucp: ...!mcnc!rti!xyzzy!meissner faster, when would we Internet: meissner@dg-rtp.DG.COM have time for netnews?
scott@bbxsda.UUCP (Scott Amspoker) (10/16/89)
In article <MEISSNER.89Oct14124500@tiktok.rtp.dg.com> meissner@dg-rtp.dg.com (Michael Meissner) writes: >In article <240@bbxsda.UUCP> scott@bbxsda.UUCP (Scott Amspoker) writes: > >> Since free() is probably the only possible way for a *valid* pointer to >> suddenly become *invalid* there are some interesting possibilities. > >Another possible way for a *valid* pointer to suddenly become >*invalid* is if the pointer points to automatic storage and you exit >the function creating the storage with either a return or a longjmp. I've read several of these comments since I've made that posting. I decided it really wasn't worth debating but it has happened too many times. I was refering to the possibility that free() could be returning the freed memory back to the system with said block of memory being removed from the process's memory map. Attempting to dereference a pointer to this block of memory would result in an actual trap and code dump. While I fully agree that a pointer to "expired" stack data is to be considered "invalid" I am not aware of any implementation that actually removes stack memory from the memory map upon exit from a procedure. That would probably slow things down considerably. Dereferencing such a pointer would be meaningless but would probably not cause a trap (although such a program is certainly headed for a trap :-) -- Scott Amspoker Basis International, Albuquerque, NM (505) 345-5232 unmvax.cs.unm.edu!bbx!bbxsda!scott
gwyn@smoke.BRL.MIL (Doug Gwyn) (10/17/89)
In article <252@bbxsda.UUCP> scott@bbxsda.UUCP (Scott Amspoker) writes: >While I fully agree that a pointer to "expired" stack data is to >be considered "invalid" I am not aware of any implementation that >actually removes stack memory from the memory map upon exit from >a procedure. That would probably slow things down considerably. >Dereferencing such a pointer would be meaningless but would probably >not cause a trap (although such a program is certainly headed for a >trap :-) Again, the question is NOT about dereferencing the pointer value, but rather about accessing the now-invalid pointer value at all. On the same general class of architectures for which it would be a problem for free()d pointers, it could easily be a problem for expired activation records (what you would probably call a "stack frame", but in a true segmented architecture an activation record is a separate system-controlled object in its own right, normally reclaimed by the system immediately upon expiration of its dynamic scope). I don't know of any C compilers for the B[56]x00 family of computers, but of there are any I would expect them to exhibit this behavior.
barmar@kulla (Barry Margolin) (10/17/89)
In article <252@bbxsda.UUCP> scott@bbxsda.UUCP (Scott Amspoker) writes: >While I fully agree that a pointer to "expired" stack data is to >be considered "invalid" I am not aware of any implementation that >actually removes stack memory from the memory map upon exit from >a procedure. Multics truncates the stack segment to the page containing the current frame whenever the process goes blocked. It's a system call so you're in the kernel already, and blocks usually wait for a long time (most blocking is for keyboard input), so the expense is not very significant. The benefit is that the extra pages needn't be swapped out, so the next page fault(s) will be faster, and less swap space is used. Barry Margolin, Thinking Machines Corp. barmar@think.com {uunet,harvard}!think!barmar