correct code for pointer subtraction (LONG reply)

Carl Paukstis carlp at iscuva.ISCS.COM
Tue Dec 20 16:38:19 AEST 1988


(I apologize in advance for the length of this and for omitting several
comments by other folks.  I wanted to get the whole of the main context
since I am crossposting to comp.lang.c and redirecting followups there.)

Eric Gisin at Mortice Kern Systems writes:
>
>How come I can't find a compiler that generates correct
>code for pointer subtraction in C on 8086s?
>Neither Turbo, Microsoft, or Watcom do it right.
>Here's an example:
>
>struct six {
>	int i[3];		/* six bytes, at least for MSC (comment by C.P.) */
>};
>
>int diff(struct six far* p, struct six far* q) {
>  	return p - q;
>}
>
>main(void) {
>	struct six s[1];
>	printf("%d\n", diff(s+10000, s));	/* 10000 */
>	printf("%d\n", diff(s, s+100));		/* -100 */
>}
>
>All of the compilers I tried computed a 16 bit difference,
>then sign extended it before dividing.
>This does not work if the pointers differ by more than 32K.

(NOTE CRITICAL POINT FOR ERIC'S COMPLAINT:  the difference between s and
s+10000 is 60,000 bytes - easily less that the 64K segment limit)

Then I (Carl Paukstis) pick a nit and respond:

>Of course, the code you posted is NOT legal, since the two pointers in the
>example *do not* point inside the same object.  You have verified that the
>incorrect code is generated when you IN FACT declare "struct six s[10000]"?
>If so, it's a bona-fide bug.  But if it won't work with your example, the
>worst conclusion you can directly draw is that your example is "not 
>conformant".

And Eric (thoroughly frustrated by Intel architecture by now) responds:

>Summary: Oh my god
>
>No, I DO NOT have to verify that it still generates incorrect code
>when I declare "s" as s[10000]. "diff" is a global function,
>and could be called from another module with a legal object.
>A compiler with a dumb linker cannot generate 
>code for diff depending on how it is called in than module.

OK, I admit, I was picking a nit.  I stand by my original comment, but please
note that I wasn't claiming that it DID work, only that Eric's posted
code didn't PROVE it didn't work.

(In fact, of course, it does NOT work, even if one defines s[10000])

Anyway, I got interested and did some actual research (who, me? find facts 
before I post? nontraditional for me, I admit) with Microsoft C 5.1.  I even
went so far as to read the manual.  In chapter 6 (Working With Memory
Models) of the _Microsoft C Optimizing Compiler User's Guide_, I find
table 6.1 (Addressing of Code and Data Declared with near, far, and
huge).  The row for "far", column for "Pointer Arithmetic", says "Uses
16 bits".  Hmmm. This is consistent with Eric's results, if a tad
ambiguous - they only use 16 bits for ALL the arithmetic, including the
(required) signedness of the address difference.

I also find in section 6.3.5 (Creating Huge-Model Programs), the
following paragraph:

"Similarly, the C language defines the result of subtracting two
pointers as an _int_ value.  When subtracting two huge pointers,
however, the result may be a _long int_ value.  The Microsoft C
Optimizing Compiler ggives the correct result when a type cast like the
following is used:
    (long)(huge_ptr1 - huge_ptr2)"

So, I altered the "diff()" function as follows:

long diff(struct six huge* p, struct six huge* q) {
  	return (long)(p - q);
}

(and changed the printf() format specs to "%ld")

and left the rest of the program exactly as given in the original post.
No great surprise, it works fine.  Compile with any memory model
{small|medium|compact|large|huge} and it still works.  Split the code so
that diff() is defined in a different source file (but put a 
prototype declaration in the file with main()) and it still works.  The
prototype gets arguments promoted to a type in which MSC is capable of
doing correct pointer subtraction.  The cast of the return type is
apparently necessary for subtracting "huge" pointers - even in "huge"
model.  If one removes the model designators (far or huge) from the
prototype for diff() and compiles in "huge" model, it is still necessary
to cast and return a long.  My code presented above seems a complete and
fairly non-intrusive solution; it works for any compilation memory model.

Eric: does this prototype declaration and return type satisfy your needs
to avoid grepping through thousands of lines of code and changing same?

Gentlepersons all: is this about the best job Microsoft could have done,
given the wonderfulness of Intel segmented address space?

What's the moral of the story? (nb: not necessarily intended for Eric,
who I'm sure is aware of all this):

1)  Examine the manuals for odd requirements of your target environment
    with utmost care.  Experiment in this environment.
2)  Use prototypes whenever you have a compiler that supports them.
    They can be a BIG help in odd situations like this.
3)  Avoid huge (in the abstract, not Intel/Microsoft, sense) data
    objects whenever possible.
4)  Avoid Intel-based systems whenever possible :-)
5)  all of the above
6)  1) and 2)
7)  Isn't this article too damned long already?  Shaddup!
-- 
Carl Paukstis    +1 509 927 5600 x5321  |"The right to be heard does not
                                        | automatically include the right
UUCP:     carlp at iscuvc.ISCS.COM         | to be taken seriously."
          ...uunet!iscuva!carlp         |                  - H. H. Humphrey



More information about the Comp.lang.c mailing list