Hiding members in a C struct

CStructPrivate Members

C Problem Overview


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?

C Solutions


Solution 1 - C

sizeof(SomeStruct) != sizeof(SomeStructSource). This will cause someone to find you and murder you someday.

Solution 2 - C

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 3 - C

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 - C

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 - C

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.

Solution 6 - C

Something similar to the method you've proposed is indeed used sometimes (eg. see the different varities of struct sockaddr* in the BSD sockets API), but it's almost impossible to use without violating C99's strict aliasing rules.

You can, however, do it safely:

somestruct.h:

struct SomeStructPrivate; /* Opaque type */

typedef struct {
  int _public_member;
  struct SomeStructPrivate *private;
} SomeStruct;

somestruct.c:

#include "somestruct.h"

struct SomeStructPrivate {
    int _member;
};

SomeStruct *SomeStruct_Create()
{
    SomeStruct *p = malloc(sizeof *p);
    p->private = malloc(sizeof *p->private);
    p->private->_member = 0xWHATEVER;
    return p;
}

Solution 7 - C

I'd write a hidden structure, and reference it using a pointer in the public structure. For example, your .h could have:

typedef struct {
    int a, b;
    void *private;
} public_t;

And your .c:

typedef struct {
    int c, d;
} private_t;

It obviously doesn't protect against pointer arithmetic, and adds a bit of overhead for allocation/deallocation, but I guess it's beyond the scope of the question.

Solution 8 - C

Use the following workaround:

#include <stdio.h>

#define C_PRIVATE(T)        struct T##private {
#define C_PRIVATE_END       } private;

#define C_PRIV(x)           ((x).private)
#define C_PRIV_REF(x)       (&(x)->private)

struct T {
    int a;

C_PRIVATE(T)
    int x;
C_PRIVATE_END
};

int main()
{
    struct T  t;
    struct T *tref = &t;

    t.a = 1;
    C_PRIV(t).x = 2;

    printf("t.a = %d\nt.x = %d\n", t.a, C_PRIV(t).x);

    tref->a = 3;
    C_PRIV_REF(tref)->x = 4;

    printf("tref->a = %d\ntref->x = %d\n", tref->a, C_PRIV_REF(tref)->x);

    return 0;
}

Result is:

t.a = 1
t.x = 2
tref->a = 3
tref->x = 4

Solution 9 - C

There are better ways to do this, like using a void * pointer to a private structure in the public struct. The way you are doing it you're fooling the compiler.

Solution 10 - C

This approach is valid, useful, standard C.

A slightly different approach, used by sockets API, which was defined by BSD Unix, is the style used for struct sockaddr.

Solution 11 - C

My solution would be to provide only the prototype of the internal struct and then declare the definition in the .c file. Very useful to show C interface and use C++ behind.

.h :

struct internal;

struct foo {
   int public_field;
   struct internal *_internal;
};

.c :

struct internal {
    int private_field; // could be a C++ class
};

Note: In that case, the variable have to be a pointer because the compiler is unable to know the size of the internal struct.

Solution 12 - C

I found that bit-field might be a good solution if you really want to hide something.

struct person {
    unsigned long :64;
    char          *name;
    int           age;
};

struct wallet {
    char *currency;
    double balance;
};

The first member of struct person is an unnamed bit-field. used for a 64-bit pointer in this case. It's completely hidden and cannot be accessed by struct variable name.

Because of the first 64-bit in this struct is unused, so we can use it as a private pointer. We can access this member by its memory address instead of variable name.

void init_person(struct person* p, struct wallet* w) {
    *(unsigned long *)p = (unsigned long)w;
    // now the first 64-bit of person is a pointer of wallet
}

struct wallet* get_wallet(struct person* p) {
    return (struct wallet*)*(unsigned long *)p;
}

A small working example, tested on my intel mac:

//
// Created by Rieon Ke on 2020/7/6.
//

#include <stdlib.h>
#include <string.h>
#include <assert.h>


#if __x86_64__ || __LP64__
#define PRIVATE_SET(obj, val) *(unsigned long *) obj = (unsigned long) val;
#define PRIVATE_GET(obj, type) (type)*(unsigned long *) obj;
#define PRIVATE_POINTER unsigned long:64
#else
#define PRIVATE_SET(obj, val) *(unsigned int *) obj = (unsigned int) val;
#define PRIVATE_GET(obj, type) (type)*(unsigned int *) obj;
#define PRIVATE_POINTER unsigned int:32
#endif

struct person {
    PRIVATE_POINTER;
    char *name;
    int age;
};

struct wallet {
    char *currency;
    double balance;
};

int main() {

    struct wallet w;
    w.currency = strdup("$$");
    w.balance = 99.9;

    struct person p;
    PRIVATE_SET(&p, &w) //set private member

    p.name = strdup("JOHN");
    p.age = 18;

    struct wallet *pw = PRIVATE_GET(&p, struct wallet*) //get private member

    assert(strcmp(pw->currency, "$$") == 0);
    assert(pw->balance == 99.9);

    free(w.currency);
    free(p.name);

    return 0;
}

Solution 13 - C

Not very private, given that the calling code can cast back to a (SomeStructSource *). Also, what happens when you want to add another public member? You'll have to break binary compatibility.

EDIT: I missed that it was in a .c file, but there really is nothing stopping a client from copying it out, or possibly even #includeing the .c file directly.

Solution 14 - C

Related, though not exactly hiding.

Is to conditionally deprecate members.

Note that this works for GCC/Clang, but MSVC and other compilers can deprecate too, so its possible to come up with a more portable version.

If you build with fairly strict warnings, or warnings as errors, this at least avoids accidental use.

// =========================================
// in somestruct.h

#ifdef _IS_SOMESTRUCT_C
#  if defined(__GNUC__)
#    define HIDE_MEMBER __attribute__((deprecated))
#  else
#    define HIDE_MEMBER  /* no hiding! */
#  endif
#else
#  define HIDE_MEMBER
#endif

typedef struct {
  int _public_member;
  int _private_member  HIDE_MEMBER;
} SomeStruct;

#undef HIDE_MEMBER


// =========================================
// in somestruct.c
#define _IS_SOMESTRUCT_C
#include "somestruct.h"

SomeStruct *SomeStruct_Create()
{
  SomeStructSource *p = (SomeStructSource *)malloc(sizeof(SomeStructSource));
  p->_private_member = 42;
  return (SomeStruct *)p;
}

Solution 15 - C

An anonymous struct can be of use here.

#ifndef MYSTRUCT_H
#define MYSTRUCT_H

typedef struct {
  int i;
  struct {
    int j;
  } MYSTRUCT_PRIVATE;

  // NOTE: Avoid putting public members after private
  int k;
} MyStruct;

void test_mystruct();

#endif

In any file that should have access to the private members, define MYSTRUCT_PRIVATE as an empty token before including this header. In those files, the private members are in an anonymous struct and can be accessed using m.j, but in all other places they can only be accessed using m.MYSTRUCT_PRIVATE.j.

#define MYSTRUCT_PRIVATE
#include "mystruct.h"

void test_mystruct() {
  // Can access .j without MYSTRUCT_PRIVATE in both
  // initializer and dot operator.
  MyStruct m = { .i = 10, .j = 20, .k = 30 };
  m.j = 20;
}
#include <stdio.h>
#include "mystruct.h"

int main() {
  // You can declare structs and, if you jump through
  // a small hoop, access private members
  MyStruct m = { .i = 10, .k = 30 };
  m.MYSTRUCT_PRIVATE.j = 20;

  // This will not work
  //MyStruct m2 = { .i = 10, .j = 20, .k = 30 };

  // But this WILL work, be careful
  MyStruct m3 = { 10, 20, 30 };

  test_mystruct();

  return 0;
}

I do not recommend putting public members after private members. Initializing a struct without member designators, such as with { 10, 20, 30 } can still initialize private members. If the number of private members changes, this will also silently break all initializers without member designators. It's probably best to always use member designators to avoid this.

You must design your structs, and especially the private members, to be zero initialized since there are no automatic constructors as in C++. As long as the members are initialized to 0 then they won't be left in an invalid state even without an initialization function. Barring a member designator initialization, initializing to simply { 0 } should be designed to be safe.

The only downside I've found is that this does mess with things like debuggers and code completion, they typically don't like it when one type has one set of members in one file, and a different set in another file.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMarlonView Question on Stackoverflow
Solution 1 - ChobbsView Answer on Stackoverflow
Solution 2 - CnosView Answer on Stackoverflow
Solution 3 - CLogan CapaldoView Answer on Stackoverflow
Solution 4 - CFelipe LavrattiView Answer on Stackoverflow
Solution 5 - CNG.View Answer on Stackoverflow
Solution 6 - CcafView Answer on Stackoverflow
Solution 7 - CjweyrichView Answer on Stackoverflow
Solution 8 - CIlya MatveychikovView Answer on Stackoverflow
Solution 9 - Cuser181548View Answer on Stackoverflow
Solution 10 - CHeath HunnicuttView Answer on Stackoverflow
Solution 11 - CadcView Answer on Stackoverflow
Solution 12 - CRieon KeView Answer on Stackoverflow
Solution 13 - CMatthew FlaschenView Answer on Stackoverflow
Solution 14 - Cideasman42View Answer on Stackoverflow
Solution 15 - CmmorinView Answer on Stackoverflow