How do I hide some fields of struct in C?
A struct cannot have multiple conflicting definitions. As such, you can't create a struct that hides some of the fields.
What you can do however it declare that the struct exists in the header without defining it. Then the caller is restricted to using only a pointer to the struct and using functions in your implementation to modify it.
For example, you could define your header as follows:
typedef struct _person Person;
Person *init(const char *name, int id, float wage, int groupid);
const char *getName (const Person *p);
int getId (const Person *p);
float getWage (const Person *p);
int getGroupid (const Person *p);
And your implementation would contain:
#include "person.h"
struct _person
{
int id;
float wage;
int groupid;
char name[NAME_MAX_LEN];
};
Person *init(const char *name, int id, float wage, int groupid)
{
Person *p = malloc(sizeof *p);
strcpy(p->name, name);
p->id = id;
p->wage= wage;
p->groupid= groupid;
return p;
}
...
Addendum to John Bollinger's answer:
Although, IMHO, opaque pointer types with accessor functions (init/get/set/destroy) are the most secure approach, there's another option that allows users to place objects on the stack.
It's possible to allocate a single "typeless" chunk of memory as part of the struct
and use that memory explicitly (bit by bit / byte by byte) instead of using additional types.
i.e.:
// public
typedef struct {
float wage;
int groupid;
/* explanation: 1 for ID and NAME_MAX_LEN + 1 bytes for name... */
unsigned long private__[1 + ((NAME_MAX_LEN + 1 + (sizeof(long) - 1)) / sizeof(long))];
} person_s;
// in .c file (private)
#define PERSON_ID(p) ((p)->private__[0])
#define PERSON_NAME(p) ((char*)((p)->private__ + 1))
This is a very strong indicator that access to the data in the private__
member should be avoided. Developers that don't have access to the implementation file won't even know what's in there.
Having said that, the best approach is an opaque type, as you may have encountered when using the pthread_t
API (POSIX).
typedef struct person_s person_s;
person_s * person_new(const char * name, size_t len);
const char * person_name(const person_s * person);
float person_wage_get(const person_s * person);
void person_wage_set(person_s * person, float wage);
// ...
void person_free(person_s * person);
Notes:
avoid
typedef
with a pointer. It only confuses developers.It's better to keep pointers explicit, so all developers can know that the type they're using is dynamically allocated.
EDIT: Also, by avoiding "typedefing" a pointer type, the API promises that future / alternative implementations will also use a pointer in it's API, allowing developers to trust and rely on this behavior (see comments).
When using an opaque type, the
NAME_MAX_LEN
could be avoided, allowing names of arbitrary length (assuming renaming requires a new object). This is an extra incentive to prefer the opaque pointer approach.avoid placing the
_
at the beginning of an identifier when possible (i.e.,_name
). Names starting with_
are assumed to have a special meaning and some are reserved. The same goes for types ending with_t
(reserved by POSIX).Notice how I use the
_s
to mark the type as a struct, I don't use_t
(which is reserved).C is more often snake_case (at least historically). The best known APIs and most of the C standard is snake_case (except where things were imported from C++).
Also, being consistent is better. Using CamelCase (or smallCamelCase) in some cases while using snake_case for other things could be confusing when developers try to memorize your API.
C has no mechanism for hiding individual members of a structure type. However, by operating only in terms of pointers to such a type, and not providing a definition, you can make the whole type opaque. Users would then have to use the functions you provide to manipulate instances in any way. This is a thing that is sometimes done.
To some extent, you may be able to achieve something like what you describe with a hidden context. For example, consider this:
header.h
typedef struct _person {
float wage;
int groupid;
} Person;
implementation.c
struct _person_real {
Person person; // must be first, and is a structure, not a pointer.
int id;
char name[NAME_MAX_LEN];
};
Now you can do this:
Person *create_person(char name[]) {
struct _person_real *pr = malloc(sizeof(*pr));
if (pr) {
pr->person.wage = DEFAULT_WAGE;
pr->person.groupid = DEFAULT_GROUPID;
pr->id = generate_id();
strncpy(pr->name, name, sizeof(pr->name));
pr->name[sizeof(pr->name) - 1] = '\0';
return &pr->person; // <-- NOTE WELL
} else {
return NULL;
}
}
A pointer to the first member of a structure always points also to the whole structure, too, so if the client passes a pointer obtained from that function back to you, you can
struct _person_real *pr = (struct _person_real *) Person_pointer;
and work on the members from the larger context.
Be well aware, however, that such a scheme is risky. Nothing prevents a user from creating a Person
without the larger context, and passing a pointer to it to a function that expects the context object to be present. There are other issues.
Overall, C APIs generally either take the opaque structure approach or just carefully document what clients are permitted to do with the data they have access to, or even just document how everything works, so that users can make their own choices. These, especially the latter, are well aligned with overall C approaches and idioms -- C does not hold your hand, or protect you from doing harm. It trusts you to know what you're doing, and to do only what you intend to do.
You can use a mixin style; e.g. write in the header:
struct person {
float wage;
int groupid;
};
struct person *person_new(void);
char const *getName (struct person const *p);
int getId (struct person const *p);
and in the source
struct person_impl {
struct person p;
char name[NAME_MAX_LEN];
int id;
}
struct person *person_new(void)
{
struct person_impl *p;
p = malloc(sizeof *p);
...
return &p->p;
}
chra const *getName(struct person const *p_)
{
struct person_impl *p =
container_of(p_, struct person_impl, p);
return p->name;
}
See e.g. https://en.wikipedia.org/wiki/Offsetof for details of container_of()
.