Often by convention, a private member has an extra underscore in its name, or something like _pri
appended. Or possibly a comment. This technique doesn't do compiler-enforced checking to make sure no one inappropriately accesses those fields, but serves as a warning to anyone reading a struct
declaration that the contents are implementation details and they shouldn't peek or poke at them.
Another common technique is to expose your structure as an incomplete type. For example, in your header file, you might have:
struct my_struct;
void some_function(struct my_struct *);
And in the implementation, or some internal header which is not accessible to consumers of the library, you have:
struct my_struct
{
/* Members of that struct */
};
You can also do similar tricks with void pointers, that get cast to the right place in the "private" portion of the code. This approach loses some flexibility (you can't have a stack-allocated instance of an undefined type, for example), but that may be acceptable.
If you want to have a mixture of private and public members, you can do the same thing as above, but store the private struct pointer as a member of the public one, and leave it incomplete in public consumers of the library.
Although, this introduces some indirection, which may hurt performance. There are some (generally non-portable, but will work on reasonable compilers) type-punning tricks you can use too:
struct public_struct
{
int public_member;
int public_member2;
/* etc.. */
};
struct private_struct
{
struct public_struct base_members;
int private_member1;
int private_member2;
};
void some_function(struct public_struct *obj)
{
/* Hack alert! */
struct private_struct *private = (struct private_struct*)obj;
}
This also assumes that you can't store these objects on the stack or in static storage, or get the size at compile time.