| |
comp.lang.c |
In article <474569f4.1120053...@news.xs4all.nl> Going the other direction -- from thin to fat, including thin_g()'s Contrary to Chuck F's followup, it *is* possible to implement fat In general, the simplest method is to have all "fat pointers" if (p.current >= p.base && p.current <= p.limit) A pointer is valid-for-dereference if it is valid (as above) *and* Given this type of "fat pointer", the simplest thin-to-fat conversion fat_pointer make_fat_pointer(machine_pointer_type value) { result.current = value; Clearly this is somewhat undesirable, as it means all "thin-derived" When dealing with pointers to objects embedded within larger objects % cat derived.c void basefunc(struct base *, void (*)(struct base *)); /* in base.c */ void func(void) { static void subfunc(struct base *p0) { There is clearly no problem in the call to basefunc(), even if we (It is probably the case, not that I have thought about it that struct multi_inherit { static void callback(struct base2 *); void func(void) { static void callback(struct base2 *p0) { p = (struct multi_inherit *) is "iffy" at the line marked "danger", although it works in practice A more complicated way to implement "fat pointers", which also fat_pointer make_fat_pointer(machine_pointer_type value) { p = look_up_in_table(value); The compiler can cache these computed fat pointers, use them
>... you do need to link fat-pointer-compiled object files with
>fat-pointer-compiled libraries; much as you need to link 64-bit object
>files with 64-bit libraries, little-endian object files with little-
>endian libraries, and on MS-DOS used to link large memory model object
>files with large memory model libraries.
construct: if fat_f() is a function compiled with "fat" pointers,
and it calls thin_g() in a library compiled with "thin" pointers
while passing a pointer, the call need only pass through a "skim
off the fat" layer (fat_g_to_thin_g() perhaps). A compiler could
even generate such a shim "on the fly" at link-time.
return value if it returns a pointer -- is considerably more
difficult. There are several ways to deal with this, with different
tradeoffs.
pointers in C, despite cast-conversions and malloc() and different
array sizes and pointer usage and so on. Again, there are multiple
ways to deal with various issues, with different tradeoffs.
represented as a triple: <currentvalue, base, limit>. A pointer
is "valid" if its current-value is at least as big as its base and
no bigger than its limit:
/* pointer value is valid */;
else
/* pointer value is invalid */;
strictly less than its limit. This is so that we can compute
&array[N] (where N is the size of the array) but not access the
nonexistent element array[N].
is done like this:
fat_pointer result;
result.base = (machine_pointer_type) MINIMUM_MACHINE_ADDRESS;
result.limit = (machine_pointer_type) MAXIMUM_MACHINE_ADDRESS;
return result;
}
pointers lose all protection.
(such as elements of "struct"s), the simplest method is again to
widen the base-and-limit to encompass the large object. Consider,
e.g.:
#include "base.h"
struct derived {
struct base common;
int additional;
};
static void subfunc(struct base *);
struct derived var;
...
basefunc(&var.common, subfunc);
...
}
struct derived *p = (struct derived *)p0; /* line X */
... use p->common and p->additional here ...
}
"narrow" the "fat pointer" to point only to the sub-structure
&var.common. However, when basefunc() "calls back" into subfunc(),
as presumably it will, with a "fat pointer" to the "common" part
of a "struct derived", we will have to "re-widen" the pointer. We
can do that at the point of the cast (line X), or simply avoid
"narrowing" the fat pointer at the call to basefunc(), so that when
basefunc() calls subfunc(), it passes a pointer to the entire
structure "var", rather than just var.common.
much, that we can pass only the "fully widened to entire object"
pointer if we are taking the address of the *first* element of
the structure. That is, C code of the form:
struct base1 b1;
struct base2 b2;
int additional;
};
struct multi_inherit m;
...
b2func(&m.b2, callback);
...
}
struct multi_inherit *p;
((char *)p0 - offsetof(struct multi_inherit, b2)); /* DANGER */
... use p->b1, p->b2, and p->additional ...
}
on real C compilers. If the call to b2func() passes a pointer
whose base is &m.b2 and whose limit is (&m.b2 + 1), the cast in
callback() must somehow both reduce the base and increase the limit.
The C Standard says that a pointer to the first element of a
structure can be converted back to a pointer to the entire structure,
but says nothing about this kind of tricky subtraction to go from
"middle of structure" to "first element" and thence to "entire
structure". Still, several ways to handle this -- either by
forbidding it, or by recognizing such subtractions embedded within
cast expressions -- are obvious.)
provides a more useful way to go from "thin" to "fat", is to record
thin-to-fat conversions in one or more runtime tables, and do
lookups as needed:
fat_pointer *p;
if (p == NULL)
__runtime_exception("invalid pointer");
return *p;
}
internally, pass them around, or "thin" them (by simply taking
p.current) at any time as needed for compatibility with code compiled
without the fat pointers. But there is a significant runtime cost
whenever going from "thin" to "fat", typically greater than that
added by verifying p.current as needed. (Note also that malloc()
must manipulate the, or a, fat-pointer table, if pointers that come
out of malloc() are ever to be looked-up. Thus, you *can* link
against various thin functions, but never against "thin malloc".)
--
In-Real-Life: Chris Torek, Wind River Systems
Salt Lake City, UT, USA (40°39.22'N, 111°50.29'W) +1 801 277 2603
email: forget about it http://web.torek.net/torek/index.html
Reading email is like searching for food in the garbage, thanks to spammers.