Hiding members in a C struct
Solution 1
Personally, I'd more like this:
typedef struct {
int _public_member;
/*I know you wont listen, but don't ever touch this member.*/
int _private_member;
} SomeStructSource;
It's C after all, if people want to screw up, they should be allowed to - no need to hide stuff, except:
If what you need is to keep the ABI/API compatible, there's 2 approaches that's more common from what I've seen.
Don't give your clients access to the struct, give them an opaque handle (a void* with a pretty name), provide init/destroy and accessor functions for everything. This makes sure you can change the structure without even recompiling the clients if you're writing a library.
provide an opaque handle as part of your struct, which you can allocate however you like. This approach is even used in C++ to provide ABI compatibility.
e.g
struct SomeStruct {
int member;
void* internals; //allocate this to your private struct
};
Solution 2
sizeof(SomeStruct) != sizeof(SomeStructSource)
. This will cause someone to find you and murder you someday.
Solution 3
You almost have it, but haven't gone far enough.
In the header:
struct SomeStruct;
typedef struct SomeStruct *SomeThing;
SomeThing create_some_thing();
destroy_some_thing(SomeThing thing);
int get_public_member_some_thing(SomeThing thing);
void set_public_member_some_thing(SomeThing thing, int value);
In the .c:
struct SomeStruct {
int public_member;
int private_member;
};
SomeThing create_some_thing()
{
SomeThing thing = malloc(sizeof(*thing));
thing->public_member = 0;
thing->private_member = 0;
return thing;
}
... etc ...
The point is, here now consumers have no knowledge of the internals of SomeStruct, and you can change it with impunity, adding and removing members at will, even without consumers needing to recompile. They also can't "accidentally" munge members directly, or allocate SomeStruct on the stack. This of course can also be viewed as a disadvantage.
Solution 4
I do not recommend using the public struct pattern. The correct design pattern, for OOP in C, is to provide functions to access every data, never allowing public access to data. The class data should be declared at the source, in order to be private, and be referenced in a forward manner, where Create
and Destroy
does allocation and free of the data. In a such way the public/private dilemma won't exist any more.
/*********** header.h ***********/
typedef struct sModuleData module_t'
module_t *Module_Create();
void Module_Destroy(module_t *);
/* Only getters and Setters to access data */
void Module_SetSomething(module_t *);
void Module_GetSomething(module_t *);
/*********** source.c ***********/
struct sModuleData {
/* private data */
};
module_t *Module_Create()
{
module_t *inst = (module_t *)malloc(sizeof(struct sModuleData));
/* ... */
return inst;
}
void Module_Destroy(module_t *inst)
{
/* ... */
free(inst);
}
/* Other functions implementation */
In the other side, if you do not want to use Malloc/Free (which can be unnecessary overhead for some situations) I suggest you hide the struct in a private file. Private members will be accessible, but that on user's stake.
/*********** privateTypes.h ***********/
/* All private, non forward, datatypes goes here */
struct sModuleData {
/* private data */
};
/*********** header.h ***********/
#include "privateTypes.h"
typedef struct sModuleData module_t;
void Module_Init(module_t *);
void Module_Deinit(module_t *);
/* Only getters and Setters to access data */
void Module_SetSomething(module_t *);
void Module_GetSomething(module_t *);
/*********** source.c ***********/
void Module_Init(module_t *inst)
{
/* perform initialization on the instance */
}
void Module_Deinit(module_t *inst)
{
/* perform deinitialization on the instance */
}
/*********** main.c ***********/
int main()
{
module_t mod_instance;
module_Init(&mod_instance);
/* and so on */
}
Solution 5
Never do that. If your API supports anything that takes SomeStruct as a parameter (which I'm expecting it does) then they could allocate one on a stack and pass it in. You'd get major errors trying to access the private member since the one the compiler allocates for the client class doesn't contain space for it.
The classic way to hide members in a struct is to make it a void*. It's basically a handle/cookie that only your implementation files know about. Pretty much every C library does this for private data.
Marlon
Updated on February 17, 2022Comments
-
Marlon about 2 years
I've been reading about OOP in C but I never liked how you can't have private data members like you can in C++. But then it came to my mind that you could create 2 structures. One is defined in the header file and the other is defined in the source file.
// ========================================= // in somestruct.h typedef struct { int _public_member; } SomeStruct; // ========================================= // in somestruct.c #include "somestruct.h" typedef struct { int _public_member; int _private_member; } SomeStructSource; SomeStruct *SomeStruct_Create() { SomeStructSource *p = (SomeStructSource *)malloc(sizeof(SomeStructSource)); p->_private_member = 42; return (SomeStruct *)p; }
From here you can just cast one structure to the other. Is this considered bad practice? Or is it done often?
-
Marlon almost 14 yearsThis is why SomeStructSource is defined in the source file.
-
Heath Hunnicutt almost 14 yearsOnly so if you publish SomeStructSource. A C++ object pointer is similar, one could use offsetof() and pointer maths to get to the private members.
-
visual_learner almost 14 yearsSome consider using
typedef
to hide pointers to be a bad idea, particularly because it is more obvious thatSomeStruct *
needs to be freed somehow thanSomeThing
, which looks like an ordinary stack variable. Indeed, you can still declarestruct SomeStruct;
and, as long as you don't define it, people will be forced to useSomeStruct *
pointers without being able to dereference their members, thus having the same effect while not hiding the pointer. -
gnud almost 14 yearsAnd any jury would let them go afterwards.
-
Dietrich Epp over 12 years"Always code as if the person who ends up maintaining your code is a violent psychopath who knows where you live." (attributed to Rick Osborne)
-
supercat about 11 yearsOne difficulty with that approach is that it requires the use of malloc/free even in situations where it should be possible for the struct to simply be created as a stack variable and then disappear when the method exits. Use of malloc/free for things which should have stacking semantics can lead to memory fragmentation if between the creation/destruction other code needs to create persistent objects. Such problem may be mitigated if one provides a method to use a passed-in block of storage to hold the object, and typedefs an int[] of suitable size for such purpose.
-
Felipe Lavratti about 11 years@supercat True, if you do no want to use malloc/free in your embedded system make the struct private to the programmer, not to the code. I have edited my answer to deal with it.
-
supercat about 11 yearsThat's a reasonable approach. An approach which would enforce the proper usage even more strongly would be to define a
typedef int module_store_t[20];
and then have amodule_t *Module_CreateIn(module_store_t *p)
. Code could create an automatic variable of typemodule_store_t
and then useModule_CreateIn
to derive from that that a pointer a newly-initialized module whose lifetime would match that of the auto-variable. -
Adi almost 11 yearsThe second approach does not help in struct encapsulation. Unfortunately, it still allows private struct members to be directly referenced! Please try it in your code.
-
Felipe Lavratti almost 11 yearsAdi, you are right. Unfortunately there is no design pattern able to force the compiler to generate errors if user tries to access data members of the struct when you want to allocate the struct data in the stack, not the heap. This is a short blanket problem and we must address it with common sense. The struct has been defined inside a header named private, so, from now on, user must not access names declared inside these files. It may lead to encapsulation violations, that's why I always stick with the malloc choice (example 1 of the answer).
-
Felipe Lavratti almost 11 yearsI find a really bad design allowing the client to access any member of a struct. The whole struct should be private. Access to it's members should be done through getters and setters.
-
Adi almost 11 yearsAs much as I like C for its simplicity, it is so annoying when it comes to applying design patterns. C and design patterns simply are not compatible. And it is really frustrating that, after 40 years of C existence, there is no single technique which would allow you to utilize the best practice coding rules in C. If we ignore the stack allocation issue, ADT technique really could be usable but only if there would be an appropriate malloc implementation which would not cause any fragmentation problems. I am really surprised that there are no Standard C Library implementations <to be continued>
-
Adi almost 11 yearswhich are tailored around this issue. I am primarily talking in the context of embedded systems where C is nowadays mostly used and where memory fragmentation represents a big problem. Just to say, I am not satisfied with solutions which use the statically allocated objects instead of malloc. Yeah, one could say that I'm really annoyed with such C 'features' :/
-
nos almost 11 years@fanl doing so in C has a lot of implications, e.g. to hide a struct in that way, it becomes quite hard to allocate it on the stack, or inline as a member of another struct. The easy way out of that is to dynamically allocate the struct and only expose a void* or handle, and while doing so may be ok in some cases, there's many cases where the implications are too big and it will inhibit you from taking advantage of what C provides you.
-
Felipe Lavratti almost 11 yearsAgreed Adi. However statically allocated objects might be of good use, since the maximum number of instances of a given struct existing at the same time can be determined with considerable precision. What I usually do in embedded system is to use malloc, if I get allocation fault at some point of the product development, I'll have to overwrite the allocation for each module with the static choice. There will be no implications propagated into the rest of the firmware.
-
Luis over 9 yearsIMHO the second example given here should be the answer, just remember to provide a destructor function.
-
Engineer over 8 yearsIn performance critical situations, avoid the jump that
void *
implies and just allocate the private data inline - privacy be damned (in this case prefixing an underscore is all you can do). -
mkonvisar almost 7 yearsinteresting... this approach hides declaration of "private" struct, but allows you access private part if you get instance of T struct.
-
davidA over 6 yearsHiding members of a struct can work well with most applications as a form of encapsulation and to break compilation dependencies, however in many constrained (embedded) systems, the use of
malloc
is impractical, and therefore structs will need to be allocated statically or on the stack. Fully hiding the struct makes this difficult (impossible?) so you end up exposing it anyway. And then you have to document it, and then it might as well be explicit. -
Abhishek Sagar over 6 yearsThis will go in my udemy tutorial. Awesome
-
Purple Ice over 5 years@meowsqueak Just because code isn't seen by anyone, doesn't mean that it doesn't need to be documented, otherwise proprietary software development would be one of the best jobs you could ever get in your programming career. Best in a sense that you never have to write documentation... Except until 2 months later when everything is on fire. So, you need to document everything anyway, but doesn't mean you have to explain to others who shouldn't touching it what it is.
-
davidA over 5 years@PurpleIce I agree entirely - what I meant (from memory) is that you have to document it as part of the API.
-
mercury0114 over 3 yearsYour first suggestion is actually a great one. What we do in our company is
struct S { int x; // PRIVATE_FIELD };
and then we have our own C code analyser that checks the comments and if it sees a comment "PRIVATE_FIELD", it prevents users to typeS.x
for that field.