Rob
Rob

Reputation: 947

C compiler checking of a typedef'ed void *

We have an anonymous type, typedefed to void *, which is the handle for an API (all code in C11). It is deliberately void * as what it is pointing to changes depending on the platform we are compiled for and we also don't want the application to try dereferencing it. Internally we know what it should be pointing to and we cast it appropriately. This is fine, the code is public, we've been using it for years, it cannot be changed.

The problem is that we now need to introduce another one of these, and we don't want the user to get the two confused, we want the compiler to throw an error if the wrong handle is passed to one of our functions. However, all of the versions of all of the C compilers I have tried so far (GCC, Clang, MSVC) don't care; they know that the underlying type is void * and so anything goes (this is with -Wall and -Werror). Putting it another way, our typedef has not achieved anything, we might as well have just used void *. I have also tried Lint and CodeChecker, who also don't seem to care (though you could probably question my configurations for these). Note that I am not able to use -Wpedantic as we include third party code where that wouldn't fly.

I have tried making the new thing a specific typedefed pointer rather than a void * but that doesn't entirely fix things as the compiler is still happy for the caller to pass that new specific typedefed pointer into the existing functions that are expecting the existing handle typedef.

Is there (a) a way to construct a new anonymous handle such that the compiler will not allow it to be passed to the existing functions or (b) a checker that we can apply to pick the problem up, at least in our own use of these APIs?

Here is some code to illustrate the problem:

#include <stdlib.h>

typedef struct {
    int contents;
} existingThing_t;
typedef void *anonExistingHandle_t;


typedef struct {
    char contents[10];
} newThing_t;
typedef void *anonNewHandle_t;
typedef newThing_t *newHandle_t;


static void functionExisting(anonExistingHandle_t handle)
{
    existingThing_t *pThing = (existingThing_t *) handle;
    
    // Perform the function
    (void) pThing;
}

static void functionNew(anonNewHandle_t handle)
{
    newThing_t *pThing = (newThing_t *) handle;
    
    // Perform a new function
    (void) pThing;
}

int main() {
    anonExistingHandle_t existingHandle = NULL;
    anonNewHandle_t newHandleA = NULL;
    newHandle_t newHandleB = NULL;

    functionExisting(existingHandle);
    functionNew(newHandleA);

    // These should result in a compilation error
    functionExisting(newHandleA);
    functionNew(existingHandle);
    functionExisting(newHandleB);

    return 0;
}

Upvotes: 2

Views: 173

Answers (1)

KamilCuk
KamilCuk

Reputation: 140970

Is there (a) a way to construct a new anonymous handle such that the compiler will not allow it to be passed to the existing functions

Yes, use a type that can't be implicitly converted to void *. Use a structure.

typedef struct {
    struct newThing_s *p;
} anonNewHandle_t;

Anyway, your design is just flawed and disables all static compiler checks. Do not use void *, instead use structures or structures with void * inside, to enable compile checks. Research how the very, very standard FILE * works. FILE is not void.

Do not use typedef pointers. They are very confusing. https://wiki.sei.cmu.edu/confluence/display/c/DCL05-C.+Use+typedefs+of+non-pointer+types+only

I suggest rewriting your library so that you do not use void * and do not use typedef pointers.

The design, may look like the following:

// handle.h
struct handle_s;
typedef struct {
  struct handle_s *p;
} handle_t;
handle_t handle_init(void);
void handle_deinit(handle_t t);
void handle_do_something(handle_t t);

// handle.c
struct handle_s {
   int the_stuff_you_need;
};
handle_t handle_init(void) {
   return (handle_t){
        .p = calloc(1, sizeof(struct handle_s))
   };
}
void handle_do_something(handle_t h) {
     struct hadnle_s *t = h->p;
     // etc.
}

// anotherhandle.h
   // similar to above
typedef struct {
   struct anotherhandle_s *p;
} anotherhandle_t;
void anotherhandle_do_something(anotherhandle_t h);

// main
int main() {
   handle_t h = handle_new();
   handle_do_something(h);
   handle_free(h);

   anotherhandle_do_something(h); // compiler error
}

Upvotes: 2

Related Questions