Hiding members in a C struct

41,239

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.

Share:
41,239
Marlon
Author by

Marlon

Updated on February 17, 2022

Comments

  • Marlon
    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
    Marlon almost 14 years
    This is why SomeStructSource is defined in the source file.
  • Heath Hunnicutt
    Heath Hunnicutt almost 14 years
    Only 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
    visual_learner almost 14 years
    Some consider using typedef to hide pointers to be a bad idea, particularly because it is more obvious that SomeStruct * needs to be freed somehow than SomeThing, which looks like an ordinary stack variable. Indeed, you can still declare struct SomeStruct; and, as long as you don't define it, people will be forced to use SomeStruct * pointers without being able to dereference their members, thus having the same effect while not hiding the pointer.
  • gnud
    gnud almost 14 years
    And any jury would let them go afterwards.
  • Dietrich Epp
    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
    supercat about 11 years
    One 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
    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
    supercat about 11 years
    That'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 a module_t *Module_CreateIn(module_store_t *p). Code could create an automatic variable of type module_store_t and then use Module_CreateIn to derive from that that a pointer a newly-initialized module whose lifetime would match that of the auto-variable.
  • Adi
    Adi almost 11 years
    The 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
    Felipe Lavratti almost 11 years
    Adi, 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
    Felipe Lavratti almost 11 years
    I 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
    Adi almost 11 years
    As 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
    Adi almost 11 years
    which 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
    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
    Felipe Lavratti almost 11 years
    Agreed 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
    Luis over 9 years
    IMHO the second example given here should be the answer, just remember to provide a destructor function.
  • Engineer
    Engineer over 8 years
    In 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
    mkonvisar almost 7 years
    interesting... this approach hides declaration of "private" struct, but allows you access private part if you get instance of T struct.
  • davidA
    davidA over 6 years
    Hiding 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
    Abhishek Sagar over 6 years
    This will go in my udemy tutorial. Awesome
  • Purple Ice
    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
    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
    mercury0114 over 3 years
    Your 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 type S.x for that field.