Opaque C structures: how to declare them?

I saw the following two styles declaring opaque types in the C API. Is there a clear advantage in using one style over another?

Option 1

// foo.h typedef struct foo * fooRef; void doStuff(fooRef f); // foo.c struct foo { int x; int y; }; 

Option 2

 // foo.h typedef struct _foo foo; void doStuff(foo *f); // foo.c struct _foo { int x; int y; }; 
+40
c coding-style struct typedef opaque-pointers
Oct 19 '10 at 4:13
source share
3 answers

My vote for the third option, which mouviciel sent, then deleted:

I saw the third way:

 // foo.h struct foo; void doStuff(struct foo *f); // foo.c struct foo { int x; int y; }; 

If you really can't type the struct keyword, then typedef struct foo foo; (note: getting rid of useless and problematic underlining) is acceptable. But no matter what you do, never use typedef to specify pointer type names. It hides the extremely important information that variables of this type refer to an object that can be changed whenever you pass them to functions, and it handles different qualifying (e.g. const qualified) versions of a pointer, which is a big pain.

+65
Oct 19 '10 at 4:23
source share

bar(const fooRef) declares an immutable address as an argument. bar(const foo *) declares the address of the immutable argument foo as.

For this reason, I prefer option 2. Ie, the presented interface type is one where cv-ness can be specified at each level of indirection. Of course, you can bypass the library of the 1st option library and just use foo , revealing yourself to all kinds of horrors when the library editor changes the implementation. (That is, the writer of the 1st version library only perceives that fooRef is part of the invariant interface and that foo can come, go, be changed, whatever. The 2nd version library writer perceives that foo is part of the invariant interface.)

I am more surprised that no one suggested combined typedef / struct constructs.
typedef struct { ... } foo;

+1
Oct 19 '10 at 4:34 on
source share

Option 1.5

I’m used to using Option 1 , unless you call your link with _h to indicate that it is the "handle" of the "C style object of this" class "C. Then you guarantee that your function prototypes use const everywhere. where the contents of the β€œdescriptor” of this object is only input, and cannot be changed, and do not use const wherever the contents can be changed.

Here is a complete example:

 //====================================================================================================================== // my_module.h //====================================================================================================================== // An opaque pointer (handle) to a C-style "object" of "class" type "my_module" (struct my_module_s *, or my_module_h): typedef struct my_module_s *my_module_h; // Create a new "object" of "class" "my_module": // A function that takes a *pointer to* an "object" handle, 'malloc memory for a new copy of the opaque // 'struct my_module_s', then points the user input handle (via its passed-in pointer) to this newly-created // "object" of "class" "my_module". void my_module_open(my_module_h * my_module_h_p); // A function that takes this "object" (via its handle) as an input only and cannot modify it void my_module_do_stuff1(const my_module_h my_module); // A function that can modify the private content of this "object" (via its handle) (but still cannot modify the // handle itself) void my_module_do_stuff2(my_module_h my_module); // Destroy the passed-in "object" of "class" type "my_module": // A function that can close this object by stopping all operations, as required, and 'free'ing its memory. // 'struct my_module_s', then points the user input handle (via its passed-in pointer) to this newly-created "object". void my_module_close(my_module_h my_module); //====================================================================================================================== // my_module.c //====================================================================================================================== // Definition of the opaque struct "object" of C-style "class" "my_module". // - NB: Since this is an opaque struct (declared in the header but not defined until the source file), it has the // following 2 important properties: // 1) It permits data hiding, wherein you end up with the equivalent of a C++ "class" with only *private* member // variables. // 2) Objects of this "class" can only be dynamically allocated. No static allocation is possible since any module // including the header file does not know the contents of *nor the size of* (this is the critical part) this "class" // (ie: C struct). struct my_module_s { int my_private_int1; int my_private_int2; float my_private_float; // etc. etc--add more "private" member variables as you see fit } void my_module_open(my_module_h * my_module_h_p) { // Ensure the passed-in pointer is not NULL (since it is a core dump/segmentation fault to try to dereference // a NULL pointer) if (!my_module_h_p) { // Print some error or store some error code here, and return it at the end of the function instead of // returning void. goto done; } // Now allocate the actual memory for a new my_module C object from the heap, thereby dynamically creating this // C-style "object". my_module_h my_module; // Create a local object handle (pointer to a struct) my_module = malloc(sizeof(*my_module)); // Dynamically allocate memory for the full contents of the struct "object" if (!my_module) { // Malloc failed due to out-of-memory. Print some error or store some error code here, and return it // at the end of the function instead of returning void. goto done; } // Initialize all memory to zero (OR just use 'calloc()' instead of 'malloc()' above!) memset(my_module, 0, sizeof(*my_module)); // Now pass out this object to the user, and exit. *my_module_h_p = my_module; done: } void my_module_do_stuff1(const my_module_h my_module) { // Ensure my_module is not a NULL pointer. if (!my_module) { goto done; } // Do stuff where you use my_module private "member" variables. // Ex: use 'my_module->my_private_int1' here, or 'my_module->my_private_float', etc. done: } void my_module_do_stuff2(my_module_h my_module) { // Ensure my_module is not a NULL pointer. if (!my_module) { goto done; } // Do stuff where you use AND UPDATE my_module private "member" variables. // Ex: my_module->my_private_int1 = 7; my_module->my_private_float = 3.14159; // Etc. done: } void my_module_close(my_module_h my_module) { // Ensure my_module is not a NULL pointer. if (!my_module) { goto done; } free(my_module); done: } 

The only improvements after this would be:

  1. Implement full error handling and return an error instead of void .
  2. Add the configuration structure my_module_config_t to the .h file and pass it to the open function to update the internal variables when creating a new object. Example:

     //-------------------- // my_module.h //-------------------- // my_module configuration struct typedef struct my_module_config_s { int my_config_param_int; int my_config_param_float; } my_module_config_t; void my_module_open(my_module_h * my_module_h_p, const my_module_config_t *config); //-------------------- // my_module.c //-------------------- void my_module_open(my_module_h * my_module_h_p, const my_module_config_t *config) { // Ensure the passed-in pointer is not NULL (since it is a core dump/segmentation fault to try to dereference // a NULL pointer) if (!my_module_h_p) { // Print some error or store some error code here, and return it at the end of the function instead of // returning void. goto done; } // Now allocate the actual memory for a new my_module C object from the heap, thereby dynamically creating this // C-style "object". my_module_h my_module; // Create a local object handle (pointer to a struct) my_module = malloc(sizeof(*my_module)); // Dynamically allocate memory for the full contents of the struct "object" if (!my_module) { // Malloc failed due to out-of-memory. Print some error or store some error code here, and return it // at the end of the function instead of returning void. goto done; } // Initialize all memory to zero (OR just use 'calloc()' instead of 'malloc()' above!) memset(my_module, 0, sizeof(*my_module)); // Now initialize the object with values per the config struct passed in. my_module->my_private_int1 = config->my_config_param_int; my_module->my_private_int2 = config->my_config_param_int*3/2; my_module->my_private_float = config->my_config_param_float; // etc etc // Now pass out this object to the user, and exit. *my_module_h_p = my_module; done: } 
0
Feb 01 '19 at 23:07 on
source share



All Articles