views:

359

answers:

9

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 = 0xWHATEVER;
  return (SomeStruct *)p;
}

From here you can just cast one structure to the other. Is this considered bad practice? Or is it done often?

(I think this is done with a lot of the structures when using the Win32 API, but you guys are the experts let me know!)

A: 

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.

Matthew Flaschen
This is why SomeStructSource is defined in the source file.
Marlon
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.
Heath Hunnicutt
+11  A: 

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

hobbs
And any jury would let them go afterwards.
gnud
+6  A: 

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.

SB
+1  A: 

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.

Kinopiko
+1  A: 

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.

Heath Hunnicutt
+8  A: 

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
 };
nos
+6  A: 

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.

Logan Capaldo
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.
Chris Lutz
+1  A: 

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.

jweyrich
You could just do `typedef void private_t` there.
Kinopiko
@Kinopiko thanks, was definitely a typo. Removed the typedef to avoid type redefinition.
jweyrich
+3  A: 

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;
}
caf