Incomplete type in C on variable size structure

375 views Asked by At

Up to now, I've been using void * as a way to encapsulate private data in C. The idea is : the user should not bother with the internal, and just request exposed functions.

Hence for example :

typedef void* myPrivateType;

myPrivateType createMPt(int someArg);
int doSomething(myPrivateType mpt, int someOtherArg);
void freeMpt(myPrivateType mpt);

This works well at hiding the internal of myPrivateType. However, there is one last minor catch : void* is so permissive, that the compiler will silently accept any kind of pointer, and not trigger any warning in case of incorrect type. This looks like a small issue, but it just increases the likelyhood that a user improperly uses the interface and lose a lot of time trying to debug what's wrong.

As a consequence, I'm now leaning on using incomplete types instead of void*, as a stricter way to control type during compilation.

Hence the previous example becomes as follows :

typedef struct foo* myPrivateType;

myPrivateType createMPt(int someArg);
int doSomething(myPrivateType mpt, int someOtherArg);
void freeMpt(myPrivateType mpt);

As you can see, almost nothing has changed, only the typedef. It works better than previous example as now, if the user provides another pointer than `myPrivateType', the compiler will complain, and the error will be immediately caught.

This is a fairly good alternative. Except that, in the "private section" of the code (the .c file), I will have to define what struct foo is. In some cases, it's quite straightforward, when such content is clearly and statically defined.

But sometimes, the content of myPrivateType depends on some variable provided at runtime, and therefore its size may vary. This is no good for a C struct, which is supposed to have a defined size at compilation time.

As a workaround, I could for example typedef myPrivateType this way :

typedef size_t* myPrivateType;

or

typedef size_t myPrivateType[];   // equivalent

This let the possibility to decide the size later on, as a multiple of size_t. But now, myPrivateType is more permissive, as any size_t* pointer will also fit the bill.

I'm wondering if there is a way to combine both properties, with myPrivateType being very strict, hence impossible to confuse with any other type of pointer, but the underlying private data keeping the ability to select its size at runtime.

2

There are 2 answers

5
Jon Chesterfield On

How about:

struct myPrivateType { void * private; };

Use in essentially exactly the same way that you used to use the raw void pointer. You can expose the whole struct in the header, since there's nothing much the application can do with the void pointer it contains anyway.

This solves the problem of implicit conversions with void * without introducing any irritations beyond the mpt->private dereference within your library functions.

edit: with the typedef struct myPrivateType myPrivateType; construct if you prefer.

5
R Sahu On

I'm wondering if there is a way to combine both properties, with myPrivateType being very strict, hence impossible to confuse with any other type of pointer, but the underlying private data keeping the ability to select its size at runtime.

Yes, you can do that quite easily by using:

struct foo
{
   int dataType;
   void* fooData;
};

struct FooData1
{
   // ... 
};

struct FooData2
{
   // ... 
};

myPrivateType createMPt(int someArg)
{
   myPrivateType ret = malloc(sizeof(*ret));

   if ( someArg == 10 )
   {
      ret->dataType = 1;
      ret->fooData = malloc(sizeof(FooData1));
   }
   else if ( someArg == 20 )
   {
      ret->dataType = 2;
      ret->fooData = malloc(sizeof(FooData2));
   }
   else
   {
      // ...
   }
}

Update

If a public function, one that is declared in a .h file, is called many times in a tight loop, and the implementation of that function has to call different functions based of the dataType of struct foo, you can store a function pointer in struct foo instead. This is analogous to the use of virtual tables in C++ classes.

struct foo
{
   int dataType;
   void* fooData;
   void (*functionInTightLoop)(void);
};

struct FooData1
{
   // ... 
};

void FooData1Function()
{
}

struct FooData2
{
   // ... 
};

void FooData2Function()
{
}


myPrivateType createMPt(int someArg)
{
   myPrivateType ret = malloc(sizeof(*ret));

   if ( someArg == 10 )
   {
      ret->dataType = 1;
      ret->fooData = malloc(sizeof(FooData1));
      ret->functionInTightLoop = FooData1Function;
   }
   else if ( someArg == 20 )
   {
      ret->dataType = 2;
      ret->fooData = malloc(sizeof(FooData2));
      ret->functionInTightLoop = FooData2Function;
   }
   else
   {
      // ...
   }
}

void tightFunction(myPrivateType foo)
{
   foo->functionInTightLoop();
}