2

In the link provided- https://embeddedgurus.com/barr-code/2010/11/what-belongs-in-a-c-h-header-file/

Michael Barr states the following:

DON’T expose the internal format of any module-specific data structure passed to or returned from one or more of the module’s interface functions. That is to say there should be no “struct { … } foo;” code in any header file. If you do have a type you need to pass in and out of your module, so client modules can create instances of it, you can simply “typedef struct foo moduleb_type” in the header file. Client modules should never know, and this way cannot know, the internal format of the struct.

What I understand is that if there is a module say "led" wants to be used by a client module, say "main", main module should not know the inner workings of module "led". Here is what I did following the advice but it just seems impossible to implement it:

led.c:

#include "led.h"

typedef enum
{
    RED = 0,
    GREEN

} e_LedColor_t;

typedef enum
{
    FAST = 0,
    SLOW,
    DIRECT,
    OFF,
    HEARTBEAT,
    DOUBLE_BLINK,
    IDENTIFICATION

} e_LedMode_t;

struct Led
{
    e_LedColor_t color;
    e_LedMode_t mode;

};

led.h:

#ifndef LED_H
#define LED_H

typedef struct Led led_t;

#endif

main.c

#include "led.h"

int main() {

    led_t led;

    return 1;
}

I receive the error on line led_t led; in main saying:
error: field has incomplete type 'ledt_t'(aka 'struct led')

Just because the main module cannot recognize a definition for the Led struct, it throws an error. But if I do a definition, then the whole idea of encapsulation is lost. There must be something that I misunderstand but I do not know what it is. Can anyone help me?

Arda30
  • 107
  • 5
  • 1
    This only works for pointers. If you want to place the actual struct instance inside a function, then this compilation unit must know how big it. The same holds for passing them to functions; since function parameters are passed by value in C, compiler has to know how large your struct is in order to copy it. But if you are passing a pointer around and not trying to access the members of the dereferenced struct, then it only needs to pass the address around. – vgru Nov 16 '19 at 11:58
  • 3
    Note that there are many answers which deal with this on SO: [c opaque pointers](https://stackoverflow.com/search?q=c+opaque+pointer) or [c opaque structs](https://stackoverflow.com/search?q=c+opaque+struct). – vgru Nov 16 '19 at 12:02
  • oh wow I got it now. Would it be okay if I alloc a Led variable by specifying the size of it then? – Arda30 Nov 16 '19 at 12:03
  • Don't `#include "led.h"` in `main.c`. The functions in `led.c` should return a pointer to an opaque type `led_t` and main should pass the pointer to the functions of `led.c`. `led.c` should do all the operations on the thing. – Paul Ogilvie Nov 16 '19 at 12:04
  • 1
    Your public header should only expose the opaque struct and the function prototypes. One of these function protoypes is usually something like `struct led_t * led_create(void)`, which does the allocation. This way you control the access to the struct fields by essentially making them read-only. – vgru Nov 16 '19 at 12:06
  • May be that content can help you. Please check answer on https://stackoverflow.com/questions/20120833/how-do-i-refer-to-a-typedef-in-a-header-file. – nurisezgin Nov 16 '19 at 12:07
  • 1
    An example of an opaque struct is `FILE` in `stdio.h`. You create it using [FILE * fopen(...)](http://www.cplusplus.com/reference/cstdio/fopen/), but you never access its internals. – vgru Nov 16 '19 at 12:10
  • @PaulOgilvie what do you mean "Don't #include "led.h" in main.c". Then how am I going to call and use the functions in led.c? – Arda30 Nov 16 '19 at 13:13
  • Thanks a lot guys for the replies! – Arda30 Nov 16 '19 at 13:53
  • @PaulOgilvie Of course you're gonna need to include led.h in main, but there will only be an incomplete struct declaration there + function prototypes. – Lundin Nov 16 '19 at 15:26
  • @Lundin, sorry, I meant "Don't `#include "led.h"` in `led.c`. – Paul Ogilvie Nov 16 '19 at 15:39
  • @PaulOgilvie You need to do that too, because the struct definition needs to see the forward declaration. – Lundin Nov 16 '19 at 16:53
  • by the way should I be doing the same for typedef enum variables? I think I should though – Arda30 Nov 16 '19 at 18:27
  • @Arda30 Enums are to be regarded as numeric constants so it isn't necessary to use private encapsulation for them. If only the implementation needs to see them, then simply put the enum in the .c file. Otherwise if the caller needs it too, put it in the .h file. – Lundin Nov 17 '19 at 18:41

1 Answers1

2

It probably bares more explanation than Micheal has given, but I think his intent was to set out the "rules" for experienced practitioners, rather then teach about opaque types - he as assumed that level of understanding amongst his audience - perhaps you have to buy one of his books to get the full skinny ;-)

Later in the comments, Micheal replies to a question about that "rule" and defines:

typedef struct window* window_handle ;
                     ^

note the type is a pointer typedef, not an instance type.

The type foo in the article cannot be instantiated, you can only create a pointer, so the interface will take parameters of type foo*, rather then foo. This for example how stdio's FILE type is defined and used - you cannot create a FILE instance, only a FILE*

You might either define:

typedef struct Led* led_t;

or keep your typedef and instantiate an led_t*:

led_t* led;

The type is opaque in both cases.

The first hides the fact that the "handle" let_t is a pointer, which may be useful because in some cases it may not be a pointer - it could be an integer index into a resource array of finite length for example.

The point is you can only instantiate pointers to incomplete types, not concrete instances.

Clifford
  • 88,407
  • 13
  • 85
  • 165
  • Thank you very much Clifford for your kind response. Now I have state, led and main modules. I have implemented the led, state and main module as explained but led module needs to read the state of the system so it can change the LEDs. Since typedef for system state is only defined in state.h and included in main.c , led functions are not able to take state pointer as their parameters. You may say, include state.h in led.c instead of main.c but I will also need to read the state of the system in various other modules, like button.c. How to overcome this situation? – Arda30 Nov 16 '19 at 13:59
  • 1
    _" but I will also need to read the state of the system in various other modules, like button.c."_ That may warrant a different question, it sounds like a design issue and not really relevant to your original question which is coding-standard related. Things like LEDs and Buttons sound like device layer code, and should not need to be aware of system state - that is an application layer issue - sounds like you need to be aware of _design rules_ regarding _coupling_ and _cohesion_ rather then just coding rules - great code of a poor design is not much help. – Clifford Nov 16 '19 at 14:13
  • 1
    Hiding pointers behind a typedef is bad practice even for opaque types. Better to let the caller declare pointer types. Because an API like `void set_stuff(type t)` is going to be very confusing for the caller, whereas `void set_stuff(type* t);` is clear. Similarly, `int get_stuff(const type*t);` is far clearer than `int get_stuff (const type t);` – Lundin Nov 16 '19 at 15:22
  • 1
    @Lundin I am not sure I agree in all cases for the reasons stated - a handle is a handle and need not be a pointer; the implementation details can be hidden, and different implementations can have a common interface. I choose to present the alternatives and let the reader figure it out, and you are more emphatic. In practice the reader is likely to see both in other people's code and examples, so it serves a purpose to present both. The reader would do well to consider your opinion I am sure. – Clifford Nov 16 '19 at 16:01
  • @Clifford By now we have very long experience of the HANDLE and HWND opaque types in Windows, that are really just glorified pointers. There's tons of code passing these around as `HANDLE*` needlessly, creating useless de-referencing layers. We need not hide what a type _is_ for the caller, only what it _contains_. – Lundin Nov 17 '19 at 18:43