Ark said:
I meant the silly question a bit wider though: If something is
implementation-defined, how reliable is (or, rather, can be) any
"auto-probing"?
That would depend upon what is being probed.
E.g., identically qualified pointers to the same location: are they
represented by the same bit pattern?
It is certain that identically qualified pointers to the same
location do NOT have to all have the same bit pattern
If not, is their difference always
a 0 anyway (I guess it should)? Is p1==p2 the same as p1-p2==0?
Yes, this behaviour is not implementation-defined. The C standard
requires that identically qualified pointers to the same location
compare equal no matter what their internal representation,
and requires the pointer difference in such case to be 0. Also, in
C99, if you were to write out both pointers using %p format, and
were to read that back in with %p during the same execution (and
the object referenced had continued to exist) then C99 promises that
the two pointers read in will compare equal -- but it doesn't promise
that the text representation of the two will be the same.
In the cases you have indicated, the implementation-defined behaviours
are fairly constrained. There are other examples that are much less
constrained.
An example of an implementation defined behaviour that is much less
constrained is the behaviour of right-shifting negative (signed) values.
An implementation is fully permitted to use an instruction that
fills the top bit with whatever happens to be in the status register
"carry" bit (seriously, several systems have such an instruction).
Thus, the bit filled in might depend upon whether the last
signed integral operation happened to generate a carry or not.
That last signed integral operation might have been somewhere near
the bottom of a loop with the >> happening to be near the top, so the
result of >> might end up depending upon code that has no obvious
connection and which is visually rather some distance away.
The conversion of integer to pointer is another example of
fairly unconstrained implementation defined behaviour. Implementations
are not required to provide such a conversion in -any- meaningful way.
Implementations could literally choose to load a random value into the
pointer (except in the case that the source integer is a constant 0:
that's defined as creating a NULL pointer, but the internal
representation of NULL pointers is not promised to be all-bits-zero.)
Implementations could also, in converting from an integral type to
a pointer, interpret the bits in non-obvious ways. For example, the
last byte (least signficant byte) might be treated as a reference
to an address register, and some permutation of the other bytes
might be treated as the offset relative to that register.
(int *)0x1234 is not necessarily even -close- in virtual memory to
(int *)0x1235 .
Also, the conversion algorithm could depend upon the pointer type being
converted to -- (char *)0x12345 might refer to virtual address 0x12345,
but (int *)0x12345 might refer to virtual address 0x48d14 (e.g.,
0x12345 * sizeof(int) )
In some cases, "implementation-defined" means that the implementation
has to choose one particular meaningful and consistant behaviour
(such as the time represention used for clock() )
but in other cases, "implementation-defined" can include
"behave badly". "Behave badly" is -uncommon- for most
implementation-defined behaviour; on the other hand, it is
not uncommon for factors beyond your effective control to be
at work in implementation-defined behaviour for some arithmetic
operations, such as signed right-shift or behaviour upon overflow.