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?
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.