From: pat@frumious.uucp (Patrick Smith)
Subject: Pointer comparisons
Date: Thu, 17 Dec 1992 01:56:56 GMT
Reply-To: uunet.ca!frumious!pat
Organization: None

(I hope nobody minds my changing the subject line to something
a little more succinct.)

While responding to a previous posting, I looked up what the
draft C++ standard (the September 17 version) had to say about
pointer comparisons.  Here's an extract from section 5.9:

   Pointers to objects or functions of the same type (after pointer
   conversions) may be compared; the result depends on the relative
   positions of the pointed-to objects or functions in the address
   space.

   Two pointers to the same object compare equal.  If two pointers
   point to nonstatic data members of the same object, the pointer
   to the later declared member compares higher provided the two
   members [are] not separated by an access-specifier label (11.1)
   and provided their class is not a union.  If two pointers
   point to nonstatic members of the same object separated by an
   access-specifier label (11.1) the result is unspecified.
   If two pointers point to data members of the same union, they
   compare equal (after conversion to void*, if necessary).
   If two pointers point to elements of the same array or one beyond
   the end of the array, the pointer to the object with the higher
   subscript compares higher.  Other pointer comparisons are
   implementation dependent.

Unfortunately, my copy of the C standard isn't at all handy, so
I can't compare with what it says.

Some of this is quite unclear to me.


"Pointers ... may be compared".  Does this mean any pair of pointers
may be compared, or some pairs may be compared?  It certainly suggests
"any pair" to me, but some might interpret it as "some pairs".


"the result depends on the relative positions ... in the address
space."  What does this mean?  Must one convert the pointers into
actual addresses and return the result of comparing those addresses?
Probably not, given that "[o]ther pointer comparisons are
implementation dependent."  Maybe one is to convert the pointers
into addresses and apply some (arbitrary) function to those addresses?
If so, why would one want such a condition?  What happens if there's
more than one address space?  This clause seems reasonable to me if
interpreted as "the general intent of pointer comparison is to compare
addresses", but perhaps it's not suitable as part of the specification
of the language.


"If two pointers point to elements of the same array ...".
What if they point to members of elements?

   struct X { int a; int b; };
   X x[2];

Must it be true that &x[0].a < &x[1].a?  What about &x[0].b < &x[1].a?


"If two pointers point to data members of the same union, they compare
equal (after conversion to void*, if necessary)."  This one seems
clear enough, but may have a trap for the unwary.

   class A {};
   class B {};
   class C : public A, public B {};
   union D {
      B b;
      C c;
   } d;

Here the comparison &d.c == &d.b is legal, but might return 0
(it compares pointers to d.b and the B part of d.c).  To compare
pointers to d.c and d.b themselves, one should explicitly cast
to void*.  Ouch.


Let me try to be constructive for a change.  Would something
similar to the following be reasonable?  (It would clearly need
much more careful wording and attention to details.)

1) Any two pointers to the same type may be compared, with any of
   the relational operators.

2) In general, the results of the comparisons need not have anything
   to do with the objects to which the pointers point.  For example,
   it is possible that p and q point to the same object, but p != q.
   (But see below.)

3) The relational operators define a total ordering on the set of
   pointers of each type.  (All the normal rules, including
   transitivity.)

4) If two pointers p and q (of the same type) are obtained through
   sequences of "normal" operations, then

      p == q   <==>   p and q point to the same object

      If p and q point to elements of the same array, or to
      subobjects of _different_ elements of the same array,
      then they compare the same way as the indexes of the
      elements in the array.

      As above when p and q point to members of the same (non-union)
      object.

      If p and q point to members of the same union object, then
      p == q if either

         - the members have the same type (ignoring const and volatile)

      or

         - p and q are void* pointers and were obtained by converting
           directly from pointers to the types of the members, with
           no intermediate conversions (again ignore const and
           volatile everywhere).

      If neither of these conditions is met, p == q is
      implementation-defined.


For rule (4), "normal" operations would include things such as
the built-in & operator, adding integers to pointers to array
elements, normal casts (eg. derived class pointer to base class
pointer), and calls to standard library functions.  It would
specifically exclude casts whose meaning is not defined by the
standard (eg. casting an integer to a pointer) and calling an
extern "C" function.  (Actually, one could develop a notion of a
C function which only does "normal" operations and permit
calling such a function.  (Is there already a concept of strictly
conforming C function, as opposed to program?))


On systems where a pointer can be treated as an integer address,
this should pose no problems.

On systems where a pointer is composed of a segment identifier
and an offset, the compiler might arrange that the "normal"
operations would never change the segment identifier.
Comparisons could then be done by just comparing the
segment identifier and offset.  If two pointers to the same object
have different segment identifiers, then the programmer must have
applied some un"normal" operation, so it's OK if the pointers
compare unequal.

One might add a library function sameObject(p,q) which would
indicate whether p and q point to the same object, no matter
how p and q were obtained.


If we ignore the question of how much existing code this would
break, is this a reasonable suggestion?

And how much existing code would this break (that isn't already
broken)?  Note that it doesn't break any code that restricts
itself to "normal" operations.

-- 
Patrick Smith
uunet.ca!frumious!pat
pat%frumious.uucp@uunet.ca
Newsgroups: comp.std.c++