ccastingexpressionimplicit-conversiontype-safety

How to trigger implicit pointer conversion inline in a C macro?


Context

I have some functions to support using a custom memory allocator in my library:

void *(allocate)(struct allocator *allocator, size_t size, size_t alignment);
void (deallocate)(struct allocator *allocator, void *pointer, size_t size, size_t alignment);

There's two things that I would still like to improve upon with this interface:

  1. There is a need to write sizeof(type) and alignof(type) for every allocation and deallocation.
  2. The type void * allows mistakenly using a pointer to a type with invalid size or alignment.

Both of these can be fixed with some simple macros:

#define allocate(allocator, type, count) \
    (type *)(allocate)(allocator, sizeof(type) * (count), alignof(type))

#define deallocate(allocator, pointer, type, count) \
    do { \
        type *typed_pointer = pointer; \
        (deallocate)(allocator, typed_pointer, sizeof(type) * (count), alignof(type)); \
    } while (0)

The macro takes care of calling sizeof(type) and alignof(type), and also performs a cast of the pointer to the given type so the compiler issues a warning when it is used as a different type.

Problem

deallocate does not have a return value, so we can implement it as a block with a variable to perform the implicit pointer cast. But for a function that does return a value, maybe reallocate, we cannot type check the pointer that is passed to the macro in the same way.

Is there any way to perform this "type checking" implicit cast inline inside of an expression, such that the macro can have a result?

EDIT: I would like to stick to standard C without any GNU or other extensions, if possible.


Solution

  • You can use a compound literal, (type *) { pointer } as the argument to reallocate, and the compiler will do its usual checks for assignment to a pointer type. Per C 2018 6.5.2.5 2 and 6, the constraints and semantic rules for an initializer list apply to a compound literal, and per 6.7.9 11, the constraints and semantic rules for assignment apply to an initializer for a scalar, except that the type is taken to be unqualified.

    Not all types can be handled this way, as appending *to a type does not necessarily yield a pointer to that type. For example, int [] would produce int [] *, which will not work. You already have that problem for the (type *) cast on allocate. The user could use a typedef for any type, though, and then pass the typedef name.