I asked about this here some time back. The folks who know the
Standard well denied that this is portable. The standard gives you
now way to rely on common prefixes of fields to be laid out the same
way in memory. But you _can_ rely on casts between the first field
and an enclosing struct type to work correctly. So the fully portable
way is this:
Does anyone recall which thread this was asked about in? I'm curious as
to whether or not "the folks who know the Standard well" had offered
concerns beyond alignment.
If 'struct refcount' (above) has an alignment requirement that is not
met by where 'data' in 'increase_refcount' (above) points to, then the
behaviour appears to be undefined[6.3.2.3p7]. But as alluded to above,
my impressions is that the alignment requirement of a struct or union
must satisfy the alignment requirement(s) of its member(s), so a pointer
to the first member (or element) can be cast to a pointer to the
surrounding struct or union (or array).
typedef enum { StringTag, URLTag, ... } TAG;
typedef unsigned REF_COUNT;
typedef struct {
TAG tag;
REF_COUNT ref_count;
} HEADER, *REF_COUNTED_OBJECT_PTR;
// every kind object needs the same initial header field.
typedef struct string_s {
HEADER hdr[1];
char *text;
unsigned length;
} STRING;
REF_COUNTED_OBJECT_PTR make_string(void)
{
STRING *s = safe_malloc(sizeof(STRING));
s->hdr->tag = StringTag;
s->hdr->ref_count = 0;
s->text = NULL;
s->length = 0;
return (REF_COUNTED_OJBECT_PTR)s;
}
Which you suggested might've been better as:
return s->hdr;
(I hope you don't mind my including that from our brief, private
discussion.)
REF_COUNTED_OBJECT_PTR ref (REF_COUNTED_OBJECT_PTR p)
{
p->ref_count++;
return p;
}
void deref (REF_COUNTED_OBJECT_PTR p)
{
if (--p->ref_count == 0)
switch (p->tag) {
case StringTag:
safe_free((STRING*)p);
break;
case ...
}
return p;
}
... now for example
{
REF_COUNTED_OJBECT_PTR x, s = ref(make_string());
... use s
x = ref(s);
... use x
deref(s);
... yada yada
deref(x); // causes deallocation.
}
The only "problem" I have with the "first member" strategy
('container_of', 'CONTAINING_RECORD', etc.) is in regards to
extensibility...
So all of the structs that you define 'HEADER' as the first member for
are all effectively reference-countable using the above strategy.
That's fine.
Then perhaps a day comes when you might like to actually track some
reference-countable objects in linked lists, but maybe not others. So
maybe you do:
struct ref_countable_and_linked_list_trackable {
HEADER hdr[1];
LIST_ENTRY link[1];
};
And then you revisit the code and change, let's say, 'struct string_s' to:
typedef struct string_s {
struct ref_countable_and_linked_list_trackable hdr[1];
char *text;
unsigned length;
} STRING;
There might be several struct definitions to change, too.
Then perhaps a day comes when you might like to actually allow for some
object types to include an "extension" pointer because you'd like to use
an interface where these objects might optionally be associated with
additional data. Do you put the extension pointer inside the definition
of 'HEADER'? Could it go unused and waste space? Do you put it inside
'struct ref_countable_and_linked_list_trackable'? Could it go unused
and waste space? Does it apply to all reference-countable object types?
Does it apply to all linked-list-trackable object types? Does it
apply to object types which are not reference-countable? It's
essentially a "multiple-inheritance" challenge, if I understand it
correctly.
One strategy might be to have object type "operations:"
typedef void f_deref(void *);
typedef void f_unlink(void *);
typedef void f_free_extension(void *);
struct object_operations {
f_deref * deref;
f_unlink * unlink;
f_free_extension * free_extension;
};
then for a particular object type, you could have (at file scope):
struct object_operations string_operations = {
string_deref,
0,
0,
};
struct object_operations node_operations = {
node_deref,
node_unlink,
0,
};
etc. Then your "header" might simply be like 'ops' below:
struct foo {
struct object_operations * ops;
/* Other members */
};
and every object would need to have this header initialized to point to
the operations appropriate for its type. This strategy can also
eliminate the 'tag' requirement, since an object's supported operations
define the use of the object.
void deref(void * object) {
/* ...Maybe check for null pointer... */
/* ...Maybe yield debug messages... */
/* ...Maybe adjust debugging counters... */
((struct object_operations *) object)->deref(object);
return;
}
However, this strategy trades the size of objects for the speed required
to initialize the 'ops' member and to call the functions instead of
making the simpler casts and offset computations for the inherited
properties (like 'ref_count').
Also, there is still wasted space for any object types with unsupported
operations, in such a type's 'xxx_operations' instance. So if one had
hundreds of operations, one might further wish to squeeze space and
forfeit time by using a single "operations function" that returns the
supported operations:
enum object_operation_type {
obj_op_deref,
obj_op_unlink,
obj_op_free_extension,
obj_ops
};
union object_operations {
f_deref * deref;
f_unlink * unlink;
f_free_extension * free_extension;
};
typedef union object_operations f_object_operations(
enum object_operation_type
);
Then your header might simply be like 'ops' below:
struct foo {
f_object_operations * ops;
/* Other members */
};
And each object type would have a handler:
f_object_operations string_operations;
union object_operations string_operations(
enum object_operation_type type
) {
union object_operations result;
switch (type) {
case obj_op_deref:
result.deref = string_deref;
return result;
}
/* Problem */
}
And the general interface would call the specific one:
void deref(void * object) {
f_object_operations ** ops;
/* ...Maybe check for null pointer... */
/* ...Maybe yield debug messages... */
/* ...Maybe adjust debugging counters... */
ops = object;
(*ops)(obj_op_deref).deref(object);
return;
}
Then perhaps a day comes when you'd like to be able to support
additional operations at execution-time. One could "hook" an object's
"handler" with a new handler that optionally invokes the old handler, if
needed. That's pretty easy; just set the 'ops' member.
Then perhaps a day comes when your additional operations need some kind
of context whose storage is not accomodated by the object being hooked.
One could have a "stack" of handlers and context, but then it seems to
me that the minimal size for a meaningful "header" would be:
struct header {
f_object_operations * ops;
void * context;
};
where both of these header members could be hooked for any object, and
where the 'context' member could point to context which itself includes
the same header for the previous[ly hooked] context, etc.
What approach(es) to a "multiple inheritance" challenge do you other
folks in comp.lang.c enjoy?