Architectural considerations and approaches to opaque structs and data hiding in C
Addressing the code in your question:
sizeof(InputBuffer)
You can't take the size of a hidden struct (frequently called an "opaque struct")! testBuffer.c has no idea what size the struct is because it's hidden! It doesn't have access to the implementation.
Also I have no idea what you're trying to do here:
#include "InputBuffer.h"
void testBuffer() {
InputBuffer b = sizeof(InputBuffer); // <=== What is this?
}
You can't arbitrarily assign a number to a struct.
Additional notes:
Your typedef is awkward.
In InputBuffer.c, do:
typedef struct InputBuffer_s {
char* buffer;
size_t buffer_length;
ssize_t input_length;
} InputBuffer_t;
Then, in InputBuffer.h and testBuffer.c, do one of the following options:
Option 1: make a forward declaration typedef to your opaque (hidden) struct
In InputBuffer.h, do:
#ifndef INPUTBUFFER_H
#define INPUTBUFFER_H
// Forward declaration of the struct defined in InputBuffer.c, since this
// header does not have access to that definition. You can therefore call this
// an "opaque struct". It is a type of data hiding since this header now knows
// that `InputBuffer_t` **exists**, but doesn't know what is in it.
typedef struct InputBuffer_s InputBuffer_t;
#endif
And in testBuffer.c:
#include "InputBuffer.h"
void testBuffer(InputBuffer_t *inputBuffer) {
}
Option 2: Make a forward declaration typdef to a pointer to your opaque (hidden) struct. This typedef is now a "handle" in the form of a pointer to a struct
This option is NOT recommended by some people, although I have used it professionally in some high-quality, safety-critical, real-time C code-bases before.
@Lundin, for example, strongly recommends against using this technique in their comment below this answer, which states:
I strongly disagree about the advi[c]e to hide pointers behind typedef, even when they are opaque. Hiding pointers behind a typedef is very bad in general, but we also know from experience that hiding opaque pointers behind a typedef leads to bad API. Specifically the Windows API with its HANDLE, HWND and other weird types like that which lead the programmer to pass those in turn by reference HANDLE*
, creating multiple levels of indirection needlessly and therefore slower and less readable code overall.
This is a good point. Therefore, I recommend you only consider typedef
ing a pointer to a struct into a "handle" as I do below if:
- The ONLY types of
_h
-named "handles" in your entire code base are pointers, so that ALL _h
-named handles are clearly known as pointers.
- You make sure developers know that ALL
_h
-named "handles" in your code base are pointers, so they don't unnecessarily make references (in C++) or pointers (in C or C++) to them.
With the above 2 consideration in mind, I have used this "handle" technique and am fine with it, although I can understand the arguments against it. You can see me use it in my answer here: Opaque C structs: various ways to declare them
In InputBuffer.h, do:
#ifndef INPUTBUFFER_H
#define INPUTBUFFER_H
// InputBuffer_h is a "handle", or pointer to an opaque struct;
// AKA: InputBuffer_h is an "opaque pointer", meaning it is a pointer
// to a struct whose implementation is hidden. This is true data-hiding
// in C.
typedef struct InputBuffer_s *InputBuffer_h;
#endif
And in testBuffer.c:
#include "InputBuffer.h"
void testBuffer(InputBuffer_h inputBuffer) {
}
But, regardless of which option you choose above, you can't do anything with that inputBuffer
param really, as you can't dereference it nor access any of its members in "testBuffer.c" because its implementation is hidden and defined in a different source file (InputBuffer.c) which you have not included!
Good Approach 1 [this is really a better approach than above]: put your struct definition in the same source file which needs its full definition
Therefore, I wonder if there is a way to hide the implementation of a struct type and avoid the incomplete type error at the same time.
So, you should declare your function prototypes which need access to the implementation in InputBuffer.h, and then write the function definitions in InputBuffer.c, so they have access to the opaque struct's implementation details, since the struct is defined in InputBuffer.c.
That would look like this, for example:
In InputBuffer.h, do:
#ifndef INPUTBUFFER_H
#define INPUTBUFFER_H
// Forward declaration of the struct defined in InputBuffer.c, since this
// header does not have access to that definition. You can therefore call this
// an "opaque struct". It is a type of data hiding since this header now knows
// that `InputBuffer_t` **exists**, but doesn't know what is in it.
typedef struct InputBuffer_s InputBuffer_t;
// put any public function prototypes (declarations) you may need here
#endif
And in InputBuffer.c:
#include "InputBuffer.h"
// Full struct definition; no need to typedef this here since it's already
// typedef'ed in InputBuffer.h, which is included above.
struct InputBuffer_s {
char* buffer;
size_t buffer_length;
ssize_t input_length;
};
void testBuffer(InputBuffer_t *inputBuffer) {
// Now you have full access to the size of the `InputBuffer_t`, and its
// members, since the full definition of this struct is above.
}
vvvvvvvvv
Here is a more-thorough answer I wrote on how I like to use and write "Object-based" C architecture using opaque pointers/structs: Opaque C structs: various ways to declare them
^^^^^^^^^
Good Approach 2 [alternative approach to the one just above]: put your struct definition in a _private.h
header file which you will include only in other source files which need the struct's full definition
Note that an alternative approach to using a single-source-file opaque pointer/struct architecture (which opaque pointers/opaque struct architectures frequently require using dynamic memory allocation with malloc()
, as I show in my other answer linked-to above), is to simply include "hidden" implementations which are defined in headers suffixed with _private.h
, such as myheader_private.h
. The implication is that these "private" headers should only be included in source files which need to see the "hidden" struct's full definition, but should never be included by the user of the API directly. This is a slightly-less-strong form of data hiding, but has the advantage of letting you give full access to the struct definition to multiple source files.
Example:
In InputBuffer_private.h (the "private" header file), do:
// THIS "PRIVATE" HEADER SHOULD ONLY BE INCLUDED BY SOURCE FILES WHICH NEED FULL
// ACCESS TO THE STRUCT DEFINITION BELOW. It should NOT generally be include by
// regular users of your API, since your architectural goal is probably to have
// some level of data hiding to hide the contents of this struct from your
// regular API users.
#ifndef INPUTBUFFER_PRIVATE_H
#define INPUTBUFFER_PRIVATE_H
// Full struct definition. No need to typedef it here since it will be
// typedefed in the "public" header file below.
struct InputBuffer_s {
char* buffer;
size_t buffer_length;
ssize_t input_length;
};
#endif
In InputBuffer.h (the "public" header file), do:
#ifndef INPUTBUFFER_H
#define INPUTBUFFER_H
// Do your choice of Option 1 or 2 above, to expose the **existence** of the
// opaque struct to the user of the API:
typedef struct InputBuffer_s InputBuffer_t; // Option 1
// OR:
typedef struct InputBuffer_s *InputBuffer_h; // Option 2
#endif
And in InputBuffer.c:
#include "InputBuffer.h"
#include "InputBuffer_private.h" // <==== NOTICE THIS ADDITION!
void testBuffer(InputBuffer_t *inputBuffer) {
// Now you have full access to the size of the `InputBuffer_t`, and its
// members, since the full definition of this struct is **INCLUDED** above.
}
You can also give the full struct definition to other source files as needed:
Ex: in SomeOtherSource.c:
#include "SomeOtherSource.h"
#include "InputBuffer_private.h" // to expose the details of the opaque struct
// Now you can have full access to the size of the `InputBuffer_t`, and access
// to all of its members, as needed, in any function below.
// Your functions here
Final notes: if you include any _private.h
header file in another "public" header file, you just lost data hiding!
If you don't want true data hiding, including any _private.h
header file in another "public" (intended to be included by users of the API) header file will expose the full struct definition to the user of the API, and all true data hiding is lost!
This is a valid architectural approach you can choose to take, however, if you want. The advantage will be that you can now use static memory allocation for all of your structs instead of requiring dynamic memory allocation as opaque pointers (aka: opaque structs) would otherwise require.
Now, you have 2 options:
- Leave the
_private.h
part of the header name. This is a "soft data hiding" approach which tells users of your public API that that header is intended to be private, and they shouldn't access stuff in it directly even though they technically can. This is a perfectly valid approach, and again, both this and the option just below now allow you to use full static memory allocation for all these structs, which is great.
- This is essentially how Python works: you simply prefix
_
to any function names you want to be "private", even though Python does NOT support true data hiding, and anyone importing a module can access all "private" members if they really want to.
- REMOVE the
_private.h
part of the header name if you no longer want any data hiding. The struct definition is now both fully exposed and intended to be fully exposed. Anyone can now include this header anywhere, and this is fine. The struct definition is fully available to anyone who includes the header, and you expect users of your public API to be able to do this as well. This is also fine, depending on the architectural approach you want to take. The choice is yours.
DON'T leave the _private.h
suffix on the end of your header file with the struct definition in it AND allow users of your public API to include your _private.h
headers directly. That violates the design intent of your API. Instead, either remove the _private.h
suffix and allow users of your public API to include it directly, OR leave that suffix and only include _private.h
files according to one of the approaches previously described above (in private source files for true data hiding, or in public header files for pseudo-data-hiding, like exists in Python).
See also
- Again, see also my other answer here for a full example of one "handle" (typdefed pointer to struct) style technique. This technique also shows the full way to use dynamic memory allocation (via
malloc()
) for opaque struct creation, as needed: Opaque C structs: various ways to declare them