Toby
Toby

Reputation: 10144

How is this struct definition made private?

In some vendor code I see structs used as objects with pointers to them as handles in a header file as follows

hdr.h

typedef struct _FOO_Obj_ {
    uint16_t x;
} FOO_Obj;

typedef struct FOO_Obj * FOO_Handle;

src.c

#include <hdr.h>
void main (void) {
    FOO_Handle bar = FOO_init(); // No error
    bar->x = 5;    // Error: pointer to incomplete class type
}

This allows the file that includes such header files to use the type FOO_Handle but somehow prevents access to the members of the FOO_Obj, e.g. at the marked line in src.c... what is happening here??

For comparison, I would previously have used the following:

struct FOO_Obj {
    uint16_t x;
};
typedef struct FOO_Obj * FOO_Handle;

With this method, I could also do:

FOO_Obj y;
FOO_Handle bar (void) {  // unintentional bicycle pun ftw
    return &y;
}

But with the first method, this same function causes an error that the return type doesn't match???

Upvotes: 1

Views: 83

Answers (3)

user2404501
user2404501

Reputation:

Generally, any time you mention a struct type that doesn't exist yet, you create it. If you specify the contents of the struct at the same time, it's created as a complete type, otherwise it's incomplete. Complete struct types allow the use of the . and -> operators. Incomplete struct types don't.

A struct type can be created with a declaration that does nothing else:

struct foo; /* Creates an incomplete type called "struct foo") */
struct bar { int x,y; }; /* Creates a complete type called "struct bar" */

Or it can be created with a declaration that also declares some variables.

struct foo *p; /* Creates an incomplete type called "struct foo"
                  and a variable of type "struct foo *" called "p" */
struct bar { int x,y; } v, *p; /* Creates a complete type called "struct foo"
                                  and a variable of type "struct foo" called "v"
                                  and a variable of type "struct foo *" called "p" */

Note that you can't just say struct foo v; unless struct foo already exists as a complete type. Incomplete types have unknown sizes, so the compiler wouldn't know how much space to allocate for p. But pointers to structs have a known size even if the struct itself doesn't, so struct foo *p; works fine with an incomplete type.

Or you can create a struct at the same time you create a typedef:

typedef struct foo f, *fptr; /* Creates an incomplete type with 2 names:
                                "struct foo" and "f"; also makes "fptr" an alias
                                for the type "struct foo *" */
typedef struct bar { int x,y; } b, *bptr; /* Creates a complete type with 2 names:
                                             "struct bar" and "b"; also makes "bptr"
                                             an alias for the type "struct bar *" */

The version where you declare a struct type and a variable of that type at the same time even works inside the parameter list of a function declaration:

int dosomething(struct foo *p) { ... }
int dosomethingelse(struct foo *p) { ... }

If struct foo doesn't already exist, the above code creates a type struct foo for the first function, and another type struct foo for the second function. Each one has a separate scope. They're not the same type. This is not something you'll ever do on purpose.

Upvotes: 1

Jens Gustedt
Jens Gustedt

Reputation: 78943

I still don't get it how they intend to use this, but here is a general rule that applies to such stuff if you want to go beyond the usual "opaque pointer" method which just uses a pointer to struct without defining the struct.

You may have different struct definitions in different compilation units (here the user code and the library) that are passed around as parameters to functions between the units, but

  • their structure must be exactly the same, same types same order
  • if the struct has a tagname in one unit it also must have one in the other
  • if in both units there is a struct tag, this tag must be exactly the same

Otherwise (one struct with tag and one without, or different tag names) the behavior is undefined. Think e.g of link time optimization to see why this might be important.

There is another rule that applies in connection with C++ that I recently learnt the hard way. There, the C++ name for name mangling is either the struct tag if it exists, or the typename if it doesn't. So if you have struct without tag, you'd better have the typedef names agree, too.

Another, minor, point in the code that you found is that names starting with underscore are reserved in file scope, they should not be used.

Upvotes: 1

Yu Hao
Yu Hao

Reputation: 122453

The type of the structure is struct _FOO_Obj_, after the typedef, it has an alias FOO_Obj. However, there's no such type as struct FOO_Obj.

So this other typedef line

typedef struct FOO_Obj * FOO_Handle;

should be either

typedef FOO_Obj * FOO_Handle;

or

typedef struct _FOO_Obj_ * FOO_Handle;

Upvotes: 2

Related Questions