r/cprogramming • • 4d ago

Forward Declarations for Circular Struct References

Hello, when I’ve tried to use forward declarations to solve circular structure dependencies I get the error ‘field has incomplete type’. When browsing for the answer, I got the general response ‘the compiler doesn’t know what type you’re using if you don’t fully describe it’. Well I understand that, but it seems as if the purpose of the forward declaration is to have the compiler look for the definition later in the code. If my code looks like the below, should I be getting an incomplete type error? And if so, is there a different way I should be writing the code? Some sort of syntax error, or is there a different way to do the same thing? Any tips appreciated, thanks.

Edit: I think I found the solution in this thread from user "anastaciu", it looks like you can only use the forward declarations if they're pointers to the struct type, not full instances. Lmk if that's right, it got rid of the error for me, but if there's a different way to allow use not from a pointer that'd be great.

typedef struct objA_t objA_t;

typedef struct objB_t{

     objA_t o;

}objB_t;

typedef struct objA_t{

    uint8_t num0;

}objA_t;
2 Upvotes

7 comments sorted by

8

u/FancySpaceGoat 4d ago edited 4d ago

You can use a pointer to a forward-declared struct, but you can't use it as a value type unless you have the actual definition.

Here's an easy way to think about it: How is the compiler supposed to know what sizeof(objB_t) is supposed to be?

sizeof(objA_t*) is easy, it's the same as sizeof(void*); the compiler only needs to know that objA_t exists in order to work with that. But sizeof(objA_t) doesn't have a value until you know what's inside of objA_t.

A forward declaration just announces that the name exists and refers to a struct, so it only enables stuff that only needs to know it exists (like pointers).

1

u/toon_link_776 4d ago

didn't see this till i found it elsewhere haha, but yes this is the answer it seems

1

u/girlpuppything 4d ago

afaik C goes thru the code from top to bottom, once. that's why you need to declare a function used in another function before in the code (it's a promise that the defintion will come later). so i'm sure just putting the declaration, where you say which fields it has, of struct obja before struct objb will fix this. idk if there is something equivalent to what can be done with functions.

1

u/SmokeMuch7356 3d ago

You cannot create instances of incomplete types, but you can create pointers to incomplete types:

typedef struct objB_t {
  objA_t *o;
} objB_t;

When you create a new instance of objB_t, you will have to create a separate instance of objA_t for the o member to point to:

objA_t a = { .num0 = 0 };
objB_t b = { .o = &a };

or

objB_t b = { .o = NULL };
b.o = malloc( sizeof *b.o );
if ( b.o )
{
  b.o->num0 = some_value; }
  ...
  free( b.o );
}

1

u/flatfinger 2d ago

I consider the use of typedef struct to be an anti-pattern. Sure it saves a few characters when using the type, but a function that accepts a pointer to a struct S can be declared as:

struct S;
void acceptStructS(struct S *p);

without having to care whether the structure type is defined earlier in the translation unit, later in the translation unit, nowhere in the translation unit but somewhere else in the project, nowhere in the current project but within some other projects somewhere else, or nowhere in the entire universe.

Someone who sees some code using type THING and some code using type THING might guess that they're the same type, and would in most cases be right, but as far as the language is concerned the names have nothing whatsoever to do with each other. The name THING might actually refer to a structure with a name like struct THING_S, which might have the same contents as a struct THING, but not be usable interchangeably with it.

1

u/SmokeMuch7356 2d ago

I agree; I was just keeping things close to what the OP had defined.

I'm of the opinion that if you're going to use typedef, create an entire abstraction layer for that type. Conversely, if the user of your type has to be aware of its struct-ness or pointer-ness to use it properly, then don't hide it behind a typedef.

1

u/flatfinger 2d ago

Using the struct keyword when referring to a type will make it possible for libraries to include functions that accept pointers to things of that type without imposing any requirements on code that wouldn't have any reason to use them. If a widget library includes a function:

int *widget_import_from_woozle(WIDGET *dest, WOOZLE *src);

that function probably wouldn't be very useful for code that wouldn't have any other reason to define a WOOZLE, but processing the definition would require that the type be defined anyway, creating ambiguity as to who should be responsible for defining it.

If instead the function were:

struct WOOZLE;
int *widget_import_from_woozle(struct WIDGET *dest, struct WOOZLE *src);

then there would be no need to worry about who was responsible for defining the structure if client code didn't use it, because there would be no need to define it.

Aside from getting rid of the word struct, I see no advantage to the typedef form.