Appropriate use of `void **` for garbage-collected structs in C?

69 views Asked by At

I need some help with void ** pointers.

In implementing a garbage collector in C (automatic reference counting), I've found I need a special assignment operator (similar to what C++ does with std::shared_ptr when assigning to one when it already holds a pointer). In its larger context, it fits into a small implementation of the DOM and so far I've gotten away with using generic void * pointers to hold strong/weak references.

The problem I am currently facing is how to abstract away re-assignment of a strong/weak reference, e.g.:

#define DOMStrongRef(T) T*

DOMStrongRef(struct dom_node) node = dom_strong_add_ref(existing_node_ref);
DOMStrongRef(struct dom_node) tmp = dom_strong_add_ref(node->parent);
dom_strong_release(node);
node = tmp;

This is both ugly and error-prone, as even this simplified example, it is not directly clear which pointer is the "live" one, which has already caused me dumb reference counting errors before.

I am looking for a simple, short way to write this, something like.

DOMStrongRef(struct dom_node) node = dom_strong_add_ref(existing_node_ref);
dom_strong_assign(&node, other_node) // release old ref, assign new

Also, to clarify, here's what the GC looks like:

typedef struct DOMHeader_s {
  struct vtable * vtable;
  int_least32_t strong_refcnt;
  int_least32_t weak_refcnt;
} DOMHeader;

typedef void DOMAny;

static inline DOMStrongRef(DOMAny)
dom_strong_add_ref(DOMAny *any)
{
  if (any != NULL)
    ((DOMHeader *) any)->header.strong_refcnt++;

  return any;
}

static inline void
dom_strong_release(DOMStrongRef(DOMAny) any)
{
  DOMHeader *hdr = any;

  if (hdr != NULL && --obj->header.strong_refcnt <= 0)
    dom__destroy(obj); /* call dtors, free() *
}

/* idem for weak refs, except no dom__destroy */

/*

PS: Thread safety is not required, but it would be nice if someone could also briefly hint at how it would be done in that case, as references also reside within structs, like struct dom_node's parent, for example.

Here's the relevant code I have tried:

/* dom/dom.h */

typedef void DOMAny;

#define DOMStrongRef(T) T* 
#define DOMWeakRef(T) T*

static inline void
dom_strong_assign(DOMStrongRef(DOMAny) *dest, DOMAny *src)
{
  if (*dest != NULL)
    dom_strong_release(*dest);

 *dest = dom_strong_add_ref(src);
}

Where dom_strong_add_ref and dom_strong_release handle int32_t reference counts of *src, potentially deallocating the struct during the latter.

However, when using this function, I get warnings of the following kind:

gcc -c -o build/dom/core/node.o -MMD -std=c11 -Wall -pedantic -O2 -ggdb3 -I. dom/core/node.c
In file included from dom/core/node.c:1:
./dom/core/node.h: In function ‘dom_get_object_root’:
./dom/core/node.h:50:23: warning: passing argument 1 of ‘dom_strong_assign’ from incompatible pointer type [-Wincompatible-pointer-types]
   50 |     dom_strong_assign(&a, a->parent);
      |                       ^~
      |                       |
      |                       struct dom_node **

I have looked up this specific implicit conversion online with no success, and I wouldn't consider putting a forced (DOMAny **) cast a solution. Thanks in advance for any help!

1

There are 1 answers

0
rshadr On

C23 macro which does exactly this (thanks to comments):

#define dom_strong_assign(dest, src) \
  dom_strong_assign_((dest), (src), typeof((dest)), typeof((src)))

/* assert() can be evaluated at compile time */
#define dom_strong_assign_(dest, src, tdest, tsrc) \
  do { \
    assert(!strcmp(#tdest, #tsrc)); \
    if (*dest != NULL) \
      dom_strong_release(*dest); \
    *dest = dom_strong_add_ref(src); \
  } while (0)