[comp.lang.c] An Ubiquitous C bug

diamond@jit345.swstokyo.dec.com (Norman Diamond) (01/21/91)

In article <s64421.664471332@zeus> s64421@zeus.usq.EDU.AU (house ron) writes:

>Here's a bug which exists in every single DOS C compiler I can find,
>and may also exist on others:
>In the small memory model, it is possible for a function to have the
>address NULL.  E.G.:
>void x() {}
>main() {
>   void (*y)() = x;
>   if (y==NULL) printf ("AARRGGHH!!\n");
>}
>This sort of program CAN print the message if x() happens to be
>loaded by the linker at the start of the code segment (address 0).

According to section 3.2.2.3:  "An integral constant expression with the
value 0, or such an expression cast to type void *, is called a null pointer
constant.  If a null pointer constant is assigned to or compared for equality
to a pointer, the constant is converted to a pointer of that type.  Such a
pointer, called a null pointer, is guaranteed to compare unequal to a pointer
to any object type or function."
Every single DOS compiler that you can find is non standard-conforming.

>Perhaps on some machines, NULL should _not_ be 0?

Of course.  On many many architectures, a bit pattern of all 1's would be
suitable and very very useful.
--
Norman Diamond       diamond@tkov50.enet.dec.com
If this were the company's opinion, I wouldn't be allowed to post it.

s64421@zeus.usq.EDU.AU (house ron) (01/21/91)

Here's a bug which exists in every single DOS C compiler I can find,
and may also exist on others:

In the small memory model, it is possible for a function to have the
address NULL.  E.G.:

void x()
{
...
}

main()
{
   void (*y)();
   y = x;
   if (y==NULL) printf ("AARRGGHH!!\n");
}

This sort of program CAN print the message if x() happens to be
loaded by the linker at the start of the code segment (address 0).
The compiler seems unable to prevent the linker from loading
functions at that address.  Perhaps on some machines, NULL should
_not_ be 0?  After all, it would be easy to prevent functions being
loaded at address 1 (for example) on a DOS machine.

--
Regards,

Ron House.   (s64421@zeus.usq.edu.au)
(By post: Info Tech, U.C.S.Q. Toowoomba. Australia. 4350)

hpa@casbah.acns.nwu.edu (Peter Anvin) (01/22/91)

  [Program comparing a function pointer to NULL deleted]
>>This sort of program CAN print the message if x() happens to be
>>loaded by the linker at the start of the code segment (address 0).
>
>According to section 3.2.2.3:  "An integral constant expression with the
>value 0, or such an expression cast to type void *, is called a null pointer
>constant.  If a null pointer constant is assigned to or compared for equality
>to a pointer, the constant is converted to a pointer of that type.  Such a
>pointer, called a null pointer, is guaranteed to compare unequal to a pointer
>to any object type or function."
>Every single DOS compiler that you can find is non standard-conforming.

All DOS compilers I know of use, in small-code models, the beginning of the
code segment to store their startup code.  Thus, functions cannot be
allocated to CS:0000h, and thus no functions correspond to NULL.  The
startup cod, which is written in assembler, sets up the stack, local heap,
floating point emulation and the arguments to main(), as appropriate.
main() just acts like a subroutine to the startup code.

Should NULL be all ones?  Performance issues aside, such a machine would
only need to subtract one when converting an int to a pointer, and add one
the other way.  In constant expressions, such as when using the macro NULL,
that can of course be done at compile time.



-- 
H. Peter Anvin +++ A Strange Stranger +++ N9ITP/SM4TKN +++
INTERNET:  hpa@casbah.acns.nwu.edu   FIDONET:  1:115/989.4
BITNET:    HPA@NUACC                 RBBSNET:  8:970/101.4

dswartz@bigbootay.sw.stratus.com (Dan Swartzendruber) (01/22/91)

In article <2831@casbah.acns.nwu.edu> hpa@casbah.acns.nwu.edu (Peter Anvin) writes:
:
:  [Program comparing a function pointer to NULL deleted]
:::This sort of program CAN print the message if x() happens to be
:::loaded by the linker at the start of the code segment (address 0).
::
::According to section 3.2.2.3:  "An integral constant expression with the
::value 0, or such an expression cast to type void *, is called a null pointer
::constant.  If a null pointer constant is assigned to or compared for equality
::to a pointer, the constant is converted to a pointer of that type.  Such a
::pointer, called a null pointer, is guaranteed to compare unequal to a pointer
::to any object type or function."
::Every single DOS compiler that you can find is non standard-conforming.
:
:All DOS compilers I know of use, in small-code models, the beginning of the
:code segment to store their startup code.  Thus, functions cannot be
:allocated to CS:0000h, and thus no functions correspond to NULL.  The
:startup cod, which is written in assembler, sets up the stack, local heap,
:floating point emulation and the arguments to main(), as appropriate.
:main() just acts like a subroutine to the startup code.
:
:Should NULL be all ones?  Performance issues aside, such a machine would
:only need to subtract one when converting an int to a pointer, and add one
:the other way.  In constant expressions, such as when using the macro NULL,
:that can of course be done at compile time.
:

Gee, this sounds familiar!  I remember when the first PDP 11 with separate
I/D space came out and no one had fixed 'ld', so it blithely put the first
initialized data at data offset 0.  Want to guess what happened when some
poor sucker's program took the address of that variable and passed it to
someone who checked for a NULL pointer?  You'd be right.  Kind of strange
that this problem is STILL cropping up 13 years later.

:
:
:-- 
:H. Peter Anvin +++ A Strange Stranger +++ N9ITP/SM4TKN +++
:INTERNET:  hpa@casbah.acns.nwu.edu   FIDONET:  1:115/989.4
:BITNET:    HPA@NUACC                 RBBSNET:  8:970/101.4


--

Dan S.

gwyn@smoke.brl.mil (Doug Gwyn) (01/22/91)

In article <s64421.664471332@zeus> s64421@zeus.usq.EDU.AU (house ron) writes:
>In the small memory model, it is possible for a function to have the
>address NULL.

[Meaning that a pointer to a legitimate function can compare equal to a
null pointer constant.]  This would be a non-conforming implementation,
and one would hope that an example similar to the one you gave is part
of any C validation suite.

>Perhaps on some machines, NULL should _not_ be 0?

No, in the SOURCE CODE, "0" MUST be an allowed way of writing a null
pointer constant.  This does NOT mean that the compiler has to produce
an all 0-bits representation for a null pointer constant, however; it
could for example use an all 1-bits representation, or the address of
a reserved library object.

>After all, it would be easy to prevent functions being
>loaded at address 1 (for example) on a DOS machine.

That's another solution.

diamond@jit345.swstokyo.dec.com (Norman Diamond) (01/22/91)

In article <2831@casbah.acns.nwu.edu> hpa@casbah.acns.nwu.edu (Peter Anvin) writes:

>Should NULL be all ones?  Performance issues aside, such a machine would
>only need to subtract one when converting an int to a pointer, and add one
>the other way.  In constant expressions, such as when using the macro NULL,
>that can of course be done at compile time.

Why would it have to subtract one and add one?  At compile time, an integer
constant 0 (which has to be evaluated at compile time anyway) in a pointer
context (which has to be determined at compile time anyway) would yield a
bit-string of all 1's.  The library's implementation of free() would either
have to be compiled using this compiler, or else be sure that it compares
against this compiler's representation of NULL.

Casts between pointers and integers (other than compile-time null pointers)
are not required to do anything fancy.  If I were coding a compiler for a
machine where pointers and ints had the same size, I'd just copy the bits.
--
Norman Diamond       diamond@tkov50.enet.dec.com
If this were the company's opinion, I wouldn't be allowed to post it.

zvs@bby.oz.au (Zev Sero) (01/22/91)

Peter = hpa@casbah.acns.nwu.edu (Peter Anvin) 

Peter> Should NULL be all ones?  Performance issues aside, such a
Peter> machine would only need to subtract one when converting an int
Peter> to a pointer, and add one the other way.  In constant
Peter> expressions, such as when using the macro NULL, that can of
Peter> course be done at compile time. 

It would have to be done only at compile time.  Fragments such as:
  int x = 0;
  char *p = (char *)x;
*should* point at location zero if that is a valid address on this
machine, and dereferencing it should give you whatever is at that
address.  *Only* constant expressions evaluating to zero (such as the
macro NULL) represent the null pointer, and these are determined
at compile time.
--
	                                Zev Sero  -  zvs@bby.oz.au
This I say unto you, be not sexist pigs.
		-  The prophetess, Morgori Oestrydingh  (S.Tepper)

david@csource.oz.au (david nugent) (01/22/91)

In <2831@casbah.acns.nwu.edu> hpa@casbah.acns.nwu.edu (Peter Anvin) writes:

> >>This sort of program CAN print the message if x() happens to be
> >>loaded by the linker at the start of the code segment (address 0).

> All DOS compilers I know of use, in small-code models, the beginning of the
> code segment to store their startup code.

This is link order dependant; nothing whatever to do with the compiler.


> Thus, functions cannot be allocated to CS:0000h, and thus no 
> functions correspond to NULL.

Yes they can, and often are.


> The startup cod, which is written in assembler, sets up the stack, 
> local heap, floating point emulation and the arguments to main(), 
> as appropriate. main() just acts like a subroutine to the startup code.

... and can be wherever you like in the executable.


> Should NULL be all ones?

It, or an equivant, could be.

# define NULLFUNC	(int (*)()-1)


    david

asylvain@felix.UUCP (Alvin "the Chipmunk" Sylvain) (01/24/91)

In article <s64421.664471332@zeus> s64421@zeus.usq.EDU.AU (house ron) writes:
> Here's a bug which exists in every single DOS C compiler I can find,
> and may also exist on others:
> 
> In the small memory model, it is possible for a function to have the
> address NULL.  E.G.:
[... example nuked ...]
> This sort of program CAN print the message if x() happens to be
> loaded by the linker at the start of the code segment (address 0).
> The compiler seems unable to prevent the linker from loading
> functions at that address.  Perhaps on some machines, NULL should
> _not_ be 0?  After all, it would be easy to prevent functions being
> loaded at address 1 (for example) on a DOS machine.

I think it would be more correct and portable if the DOS linker avoided
loading the function at address 0.  Maybe there should be a way the C
compiler can inform the linker to avoid this practice.  There are too
many implementations that joyfully interchange NULL with 0 with no thot
to this problem.  (Altho, the compiler is supposed to be smart enuff to
recognize a 0 in a pointer context and convert it to a "null pointer",
whatever it happens to be.)

In fact, unless I'm mistaken, I think NULL is *defined* to be 0.  NULL
and 0 are *defined* to be an invalid address.  Therefore, if the func-
tion (or anything else) happens to actually have a valid address of 0,
then _something_ is _broken_.

(Altho, again, this doesn't really impact the internal representation.
The compiler should do the conversion.  But I don't trust a compiler to
recognize {ptr1 = 0; ptr2 = ptr1;} and know somehow that at run-time the
0 must be changed to an internal value for NULL.  I've seen compilers
that would recognize {y = 5/0;} and flag it: but {x = 0; y = 5/x;} will
work every time.)

Put another way: "valid address == 0" is a contradiction in terms.
--
asylvain@felix.UUCP (Alvin "the Chipmunk" Sylvain)
=========== Opinions are Mine, Typos belong to /usr/ucb/vi ===========
"We're sorry, but the reality you have dialed is no longer in service.
Please check the value of pi, or see your SysOp for assistance."
=============== Factual Errors belong to /usr/local/rn ===============
UUCP: uunet!{hplabs,fiuggi,dhw68k,pyramid}!felix!asylvain
ARPA: {same choices}!felix!asylvain@uunet.uu.net

gwyn@smoke.brl.mil (Doug Gwyn) (01/24/91)

In article <s64421.664559119@zeus> s64421@zeus.usq.EDU.AU (house ron) writes:
>I tried Quick C, TopSpeed C and Watcom C (a real loser).

It occurs to me that you might be linking the object modules without
preceding them by a run-time startup module (that sets up the environment,
then invoked main() with appropriate arguments, and after main() returns
invokes exit() etc.).  If the compiler documentation specifies a certain
way of linking, then to assure standard conformance you must follow the
vendor's instructions exactly.

s64421@zeus.usq.EDU.AU (house ron) (01/24/91)

david@csource.oz.au (david nugent) writes:

>It, or an equivant, could be.

># define NULLFUNC	(int (*)()-1)

I tried something like this using TopSpeed C, but I had to write a
separate one for each type of function pointer, whereas NULL, if only
it worked, works for all function types.  I tried using variations
on void *, but it took that as meaning the function returned a
void result, instead of meaning "any type of function".

--
Regards,

Ron House.   (s64421@zeus.usq.edu.au)
(By post: Info Tech, U.C.S.Q. Toowoomba. Australia. 4350)

s64421@zeus.usq.EDU.AU (house ron) (01/24/91)

risto@tuura.UUCP (Risto Lankinen) writes:

>Hi!

>While I don't see it too dramatic a problem (to have NULL pointers to valid
>function objects in C), 

It is a _VERY_ dramatic problem if you write a utility function which
takes pointers to other functions as arguments, and needs to test
which ones point to genuine functions so that it knows which ones
it has to call.

EVERY DEVIATION FROM A STANDARD IS DRAMATIC SOMEWHERE!

--
Regards,

Ron House.   (s64421@zeus.usq.edu.au)
(By post: Info Tech, U.C.S.Q. Toowoomba. Australia. 4350)

kdq@demott.com (Kevin D. Quitt) (01/25/91)

In article <s64421.664559119@zeus> s64421@zeus.usq.EDU.AU (house ron) writes:
>kdq@demott.com (Kevin D. Quitt) writes:
>
>>In article <s64421.664471332@zeus> s64421@zeus.usq.EDU.AU (house ron) writes:
>>>Here's a bug which exists in every single DOS C compiler I can find,
>...
>>    Apparently you haven't tried Microsoft C 6.0?  Which ones have you tried?
>
>I tried Quick C, TopSpeed C and Watcom C (a real loser).  Remember
>that any particular program might work, without guaranteeing that the
>bug is absent.  To be sure, look at the value of NULL pointers, and, if
>it is zero, look at a program map to verify that a non-function is
>necessarily present at the start of the default code segment.  Is this
>the case with MSC6?

    I find start-up code at zero, and with a few simple trials was not
able to get one of my (or the library's) functions loaded at zero.  Maybe
if I were more persistent...


-- 
 _
Kevin D. Quitt         demott!kdq   kdq@demott.com
DeMott Electronics Co. 14707 Keswick St.   Van Nuys, CA 91405-1266
VOICE (818) 988-4975   FAX (818) 997-1190  MODEM (818) 997-4496 PEP last

gwyn@smoke.brl.mil (Doug Gwyn) (01/26/91)

In article <156097@felix.UUCP> asylvain@felix.UUCP (Alvin "the Chipmunk" Sylvain) writes:
>I think it would be more correct and portable if the DOS linker avoided
>loading the function at address 0.

I disagree.  This is a combined language+language_implementation constraint,
and is no business of the linker.  In fact, I think the linker was being
misused since a trivial way to satisfy the constraint would be to link a
startup object module first, and that is probably what the compiler manuals
say to do.

mcdaniel@adi.com (Tim McDaniel) (01/26/91)

Please see the long-form FAQ, Frequently-Asked Questions list, which
will be reposted in six days.  E-mail me for a copy.  Null pointers
are thoroughly covered in questions 1 thru 14. 

asylvain@felix.UUCP (Alvin "the Chipmunk" Sylvain) is somewhat
confused.

--
Tim McDaniel                 Applied Dynamics Int'l.; Ann Arbor, Michigan, USA
Work phone: +1 313 973 1300                        Home phone: +1 313 677 4386
Internet: mcdaniel@adi.com                UUCP: {uunet,sharkey}!amara!mcdaniel

raymond@math.berkeley.edu (Raymond Chen) (01/26/91)

In article <14975@smoke.brl.mil>, gwyn@smoke (Doug Gwyn) writes:
>a trivial way to satisfy the constraint would be to link a
>startup object module first, and that is probably what the compiler manuals
>say to do.

Pardon me for injecting facts into the discussion, but here's a quote
from the Turbo C manual; namely, the section titled, `Using TLINK with
Turbo C Modules':

    The general format for linking Turbo C programs with TLINK is

      tlink C0x <myobjs>, <exe>, [map], <mylibs> [emu|fp87 mathx] Cx

    where:
      C0x = initialization module for memory model t, s, c, n, l or h.

    The initialization module <<must appear as the first object
    file in the list>>.  [emphasis mine]

Consequently, anybody who doesn't list C0x first is not following
the instructions and therefore deserves whatever he gets.

We now return you to the petty bickering, already in progress.