> It's a common problem in embedded projects. You want to hide private
> data of modules, but you don't want to use dynamic memory allocation.
>
> In the .h file you write the typedef only and in .c file you write the
> full definition of the struct.
> In this way you can't statically allocate the struct, because the
> compiler doesn't know the inner definition. If you want to use static
> allocation, you need to show the inner definition in the .h file, i.e.
> define it publicly.
>
> FreeRTOS project uses another strategy. The real private definition is
> in the .c file, so hiding data. But the .h file has a dummy definition.
> ...
> Do you have better strategies?
C provides at the language level data hiding only at a meta level. You
can define in your documentation what information the user is allowed to
assume is true, and what they shouldn't. You can perhaps use nameing
conventions to make it more obvious when that principle is being
violated (things like leading underscores in the name to mark things
that shouldn't be assumed to be promised). This goes in line with the
principle that C trusts the programmer, and is only trying to help fight
off Murphy and not Machiavelli.
The FreeRTOS method of dual structure definitions is one way to make
that naming much more obvious (using names like dummy1, but comes at the
cost of needing to maintain two concurrent versions of the structures,
and there HAVE been cases where the two have gotten out of sync (I don't
know if they have added a sizeof check to the C file to at least catch
the problem when it occurs).
It also comes at the cost that the program technically has undefined
behavior due to a violation of the One Definition Rule (The type of the
handle, and thus that type of that parameter to the functions, changes
depending on whether you are in implementation code or user code).
C++ improves this slightly, by allowing you to assign visibility
(public/protected/private) to members, and will generate an error if
someone accesses a member they aren't supposed to.
Other languages can hide the information more completely, put at the
cost that either you can't statically allocate the object (because you
need to know its size to allocate it) or the compiler needs a back door
that allows IT to peek at the full definition to get its size. The user
always has the option to peek at the source module if it is available,
or decode the compilers output to get information about the structure.
Often, the method provided by C is good enough, and you can always say
to the programmer that has their program break because the used
information they weren't supposed to use that its a problem of their own
making. (This presumes it isn't a security issue, but trusting in hiding
to provide security starts on a losing basis).
Reply by David Brown●March 3, 20212021-03-03
On 03/03/2021 13:16, pozz wrote:
> It's a common problem in embedded projects. You want to hide private
> data of modules, but you don't want to use dynamic memory allocation.
>
> In the .h file you write the typedef only and in .c file you write the
> full definition of the struct.
> In this way you can't statically allocate the struct, because the
> compiler doesn't know the inner definition. If you want to use static
> allocation, you need to show the inner definition in the .h file, i.e.
> define it publicly.
>
> FreeRTOS project uses another strategy. The real private definition is
> in the .c file, so hiding data. But the .h file has a dummy definition.
>
> --- start of module.h ---
> typedef struct myclass_s myclass;
>
> typedef struct {
> � uint8_t dummy1;
> � uint16_y dummy2;
> � char dummy3[24];
> } static_myclass;
>
> myclass *myclass_create_static(static_myclass *buf, uint8_t age,
> uint16_t sn, consr char *name);
> --- end of module.h ---
>
> --- start of module.c ---
> struct myclass_s {
> � uint8_t age;
> � uint16_y serial_number;
> � char name[24];
> };
>
> myclass *myclass_create_static(static_myclass *buf, uint8_t age,
> uint16_t sn, consr char *name) {
> � myclass *obj = (myclass *)buf;
> � obj->age = age;
> � obj->serial_number = sn;
> � memcpy(obj->name, name, sizeof(obj->name));
> � return obj;
> }
> --- end of module.c ---
>
>
> Do you have better strategies?
Neither C nor C++ provide good features for data hiding like this -
FreeRTOS' method is perhaps the least bad way to get something that is
valid according to the rules for compatible types while also avoiding
the overhead of pointers and non-static data. But it has the
disadvantage that the type needs to be defined twice, leading to
maintenance inconvenience.
With C++, you can do this:
// myclass.h
#include "myclass_imp.h"
struct myclass : public myclass_imp {
// Stuff you want to be documented and easily
// seen by users of the class
}
// myclass_imp.h
// Don't rely on the details in this file!!
struct myclass_imp {
// Hidden stuff
}
People can still see the details in the class if they want to, but at
least it is a bit separate and out of the way.
(I don't bother with a second header for the implementation class - I
just put it in the second half of the main header.)
Reply by pozz●March 3, 20212021-03-03
It's a common problem in embedded projects. You want to hide private
data of modules, but you don't want to use dynamic memory allocation.
In the .h file you write the typedef only and in .c file you write the
full definition of the struct.
In this way you can't statically allocate the struct, because the
compiler doesn't know the inner definition. If you want to use static
allocation, you need to show the inner definition in the .h file, i.e.
define it publicly.
FreeRTOS project uses another strategy. The real private definition is
in the .c file, so hiding data. But the .h file has a dummy definition.
--- start of module.h ---
typedef struct myclass_s myclass;
typedef struct {
uint8_t dummy1;
uint16_y dummy2;
char dummy3[24];
} static_myclass;
myclass *myclass_create_static(static_myclass *buf, uint8_t age,
uint16_t sn, consr char *name);
--- end of module.h ---
--- start of module.c ---
struct myclass_s {
uint8_t age;
uint16_y serial_number;
char name[24];
};
myclass *myclass_create_static(static_myclass *buf, uint8_t age,
uint16_t sn, consr char *name) {
myclass *obj = (myclass *)buf;
obj->age = age;
obj->serial_number = sn;
memcpy(obj->name, name, sizeof(obj->name));
return obj;
}
--- end of module.c ---
Do you have better strategies?