Prevent incomplete type declaration when defining opaque pointer?

Viewed 270

There's a feature in the C standard that was hiding a bug in my code, and I'd like to know if there's some way of preventing it, or at least issuing a warning.

In C, this code implicitly declares struct foo as an incomplete type:

struct foo *ptr; /* I didn't even tell the compiler I wish to use struct foo */

However, I'd prefer to be required to declare the incomplete type instead, like this:

struct foo; /* foo is incomplete, but I'm telling the compiler to allow references to foo */
struct foo *ptr; /* that's fine, pointer to an incomplete type which I said I wish to use */

The bug I was talking about is that I made a typo in a pointer definition, and so it was pointing to an incomplete type that was created "on the fly" by the compiler with no warning. Had the compiler warned me with something like "pointer to undeclared struct", I would have corrected the typo. Can I enable such a warning in some way?

3 Answers

The pointer itself is OK provided you do not dereference it or use it in a way which requires the complete type.

If you try you will get an error.

Examples:

struct foo; /* foo is incomplete, but I'm telling the compiler to allow references to foo */

void foo(void *vptr)
{
    struct foo *ptr = ptr;
    ptr -> x = 0; //error
}
#include <stdlib.h>
struct foo; /* foo is incomplete, but I'm telling the compiler to allow references to foo */

void foo(void)
{
    struct foo *ptr = malloc(sizeof(*ptr)); // error - incomplete type 
}

https://godbolt.org/z/bnKEGr

There is no need for additional warning messages as the pointer to incomplete type is valid.

You cannot allocate an object of incomplete type unless the definition is present in the same translation unit as the variable. That's the whole point of using opaque pointers - the type is completed by the struct definition in the corresponding .c file which will not be visible to the caller.

There's two ways to implement them, either as a typedef in a header without pointers:

typedef struct foo foo;

or as a pointer:

typedef struct foo* foo;

The former style has the advantage that you don't hide pointers behind typedef, which is often confusing. Your API would then be for example void foo_init (foo* f); and the caller must declare all instances as pointers.

The latter style has the advantage that you can pretend that the opaque type is a common variable. This allows the caller to seemingly declare objects, while they are actually declaring pointers without realizing. The API then becomes void foo_init (foo f); where everything appears to be passed by value.

The problem with your error is that you can make the typo anywhere in the code, and the compiler cannot know if you will finally complete the type or not (it is valid not to complete the type and there are some uses of that ---e.g. opaque types)

Opaque types are normally managed by reference, you define an incomplete structure that you export in a public header file, and you complete in a private header file. The implementation module includes both files (includes the private only, but as the private will include the public, you have both) and the library user includes only the public, wich has the incomplete. This way you can manage a library of objects for which you don't know the internal structure, but the implementation has full access to them. And as pointer to different structures are different types of pointers you have a weak type checking.

But all of this requires that the compiler shuts up when it reaches the end of a compilation unit and some of the types have not been completed.

Related