-1

Define InputBuffer in InputBuffer.c

typedef struct InputBuffer_t {
  char* buffer;
  size_t buffer_length;
  ssize_t input_length;
} InputBuffer;

Hide the InputBuffer's implementation in InputBuffer.h

#ifndef INPUTBUFFER_H
#define INPUTBUFFER_H

typedef struct InputBuffer_t InputBuffer;

#endif

Then use InputBuffer in testBuffer.c

#include "InputBuffer.h"

void testBuffer() {
   InputBuffer b = sizeof(InputBuffer);
}

However, compiling testBuffer will result in "variable has incomplete type 'struct InputBuffer'" because the complete InputBuffer implementation is not in InputBuffer.h.

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.

Tony
  • 111
  • 5
  • This code should not cause a compiler warning. You don't dereference the pointer and you don't access any of the members. – Cheatah Dec 02 '21 at 05:57
  • @Tony That code doesn't generate any errors or warnings. If you are seeing errors, you need to post the actual code that you are attempting to compile. See [mcve]. – user3386109 Dec 02 '21 at 06:38
  • @Cheatah question modifed – Tony Dec 02 '21 at 06:46
  • 2
    The line you added would be invalid even without hiding the structure definition. – user3386109 Dec 02 '21 at 06:51
  • @user3386109 Adding header guard is for an easy copy and paste the code for a test, not for hiding the structure definition. – Tony Dec 02 '21 at 07:05
  • Use a pointer? If it's opaque, then you would have no use for it's size; use construtors. This has nothing to do with `typedef`. – Neil Dec 02 '21 at 07:06
  • @Tony The header guard was the version 3 edit. The nonsensical line was added in the latest edit (version 4). – user3386109 Dec 02 '21 at 07:07
  • @user3386109, no need to have deleted your comments under my answer. I think they were useful and provided context. – Gabriel Staples Dec 02 '21 at 07:21
  • @GabrielStaples IMO, comments under answers should always be temporary. – user3386109 Dec 02 '21 at 07:23
  • 1
    @GabrielStaples When comments are used for getting clarification **and** the clarification is obtained, it's a good idea to delete the comments. Once clarified they are just noice... – Support Ukraine Dec 02 '21 at 07:29
  • May be it's not a duplicate of '[How to include opaque type in multiple .c files?](https://stackoverflow.com/questions/55708456/how-to-include-opaque-type-in-multiple-c-files)', but answers given there apply here. – CiaPan Dec 02 '21 at 16:32

2 Answers2

1

The down side of private encapsulation through forward declaration is that the caller gets an incomplete type, that's just how it is. The caller has to use pointer types.

If you for some reason need to expose the size of the struct outside the encapsulation, you'll have to design a getter function for that purpose. Example:

InputBuffer* b = InputBufferCreate();
size_t size = InputBufferGetSize(b);

More info here: How to do private encapsulation in C?

Lundin
  • 195,001
  • 40
  • 254
  • 396
0

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:

  1. 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) {
    
    }
    
  2. 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 typedefing a pointer to a struct into a "handle" as I do below if:

    1. 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.
    2. 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:

  1. 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.
    1. 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.
  2. 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

  1. 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
Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
  • @user3386109, yeah, you're right. I updated my wording from "wrong" to "awkward". I suspect the OP isn't revealing their full code and somewhere they are trying to access the implementation details of a hidden (opaque) struct, causing the error. – Gabriel Staples Dec 02 '21 at 06:46
  • @user3386109....and there it is! They just updated their question with what is wrong with their code. – Gabriel Staples Dec 02 '21 at 06:47
  • @user3386109, agreed. I agree with you that when you said there was nothing wrong with their code, you were right, and that when you say it is now invalid, you are right. I agree in both cases. They added a line which _now_ makes it invalid. Originally, their code was awkward, but fine, and would have compiled as they originally wrote it in the original question. – Gabriel Staples Dec 02 '21 at 06:52
  • I strongly disagree about the advise 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. – Lundin Dec 02 '21 at 07:52
  • @Lundin, point taken. I massively updated my answer to include your quoted comment and some bullets to consider when typedefing a pointer to a struct as a "handle". – Gabriel Staples Dec 02 '21 at 19:19