C question: const structs

A place to discuss the implementation and style of computer programs.

Moderators: phlip, Moderators General, Prelates

User avatar
Qaanol
The Cheshirest Catamount
Posts: 3039
Joined: Sat May 09, 2009 11:55 pm UTC

C question: const structs

Postby Qaanol » Sun Mar 15, 2015 6:32 am UTC

Quick (or maybe not-so-quick) question: in a C structure that must not be modified, is it advisable to…

1. Define the member fields of the struct as const.
2. Leave the member fields as non-const and create a const struct.
3. Leave the member fields and the struct as non-const, and present only an opaque pointer.

…or something else?

Each such struct (or rather, the data within it) will be passed to several different functions, which must not be able to modify it. For a simplified example, suppose the struct consists of an int and a pointer to double, and client code provide a function (call it calcFunc) that uses those members. Here is what it might look like with everything modifiable, and the struct presented as an opaque type:

Spoiler:

Code: Select all

// Header
struct aStruct;

struct aStruct* createStruct(int count, double *values);

double calculateFromStruct(struct aStruct *theStruct, double (*calcFunc)(const int, const double*));

void freeStruct(struct aStruct *oldStruct);

Code: Select all

// Source
struct aStruct
{
  int count;
  double *values;
}

struct aStruct* createStruct(int count, double *values)
{
  struct aStruct *newStruct = malloc(sizeof(struct aStruct));
  if (NULL != newStruct) *newStruct = (struct aStruct) {.count = count, .values = values};
  return newStruct;
}

double calculateFromStruct(struct aStruct *theStruct, double (*calcFunc)(const int, const double*))
{
  if ((NULL == theStruct) || (NULL == calcFunc)) return 0.0; // Worst error handling ever.
  return calcFunc((*theStruct).count, (*theStruct).values);
}

void freeStruct(struct aStruct *oldStruct)
{
  free(oldStruct);
}
In that example, the only thing stopping whatever gets passed in as calcFunc from modifying the struct is that calcFunc takes const parameters.

In my application speed is important, but only within calcFunc which thus requires direct access to the values array, but calcFunc must not be able to modify those values (there will be other threads calling calculateFromStruct with other calcFuncs on the structure at the same time). So…what is the best way to do this?

The way I see it, having an opaque type passed by reference means the client code doesn’t need to know about the internal makeup of the struct, which is good. However it also means that memory must be allocated, pointers checked for NULL, and later freed. On the other hand, having either the struct or the members const means all that memory management is avoided, and in fact the calculateFromStruct function can be eliminated entirely, but then client code would have to pay attention to the internal makeup of the struct, which is not good. There are probably other considerations I haven’t thought of, so that’s why I’m asking. Also I think I read somewhere that const members of a non-const struct might actually still be mutable, but I don’t know if that’s true.

Anyway, as I said above I don’t care about speed outside of calcFunc, just about simplicity, proper design, and correctness. However calcFunc does require direct access to data arrays for speed reasons.
wee free kings

Tub
Posts: 319
Joined: Wed Jul 27, 2011 3:13 pm UTC

Re: C question: const structs

Postby Tub » Sun Mar 15, 2015 9:32 pm UTC

A few general considerations first:

  • If you want the struct members to be truly const, define the members as const. That way, no code can modify them after initialization - which is a problem when using malloc, because you can initialize local variables, but not a malloc'ed block. (at least not without hacks; see below)
  • If you want to define an API where some parts of the program may write to the struct, while others may only read, pass the struct as const to the restricted areas of your code, but keep it non-const otherwise.
  • If you're trying to define a public API for a library, and you may need to extend or change your struct later, use an opaque pointer (take a look at e.g. the zlib API). Not as much for the constness, but because the layout of your struct may change. This will prevent read-only access, too.
Of course, all three of these solution protect only against accidental manipulation, not against malicious ones. In either case, a pointer is available, and it is trivial to cast that pointer to a non-const pointer to anything. So with enough malice, the values can be changed. If you need to be absolutely certain, allocate a page directly, initialize the struct there, then ask your operating system to turn the whole page read-only. Probably overkill, lacks portability, but it is possible.

If you just want to prevent data races, the second method should fit you well. You can play around with the struct as you like during initialization, then pass a pointer to const struct to your function.

With one important caveat:
If your struct contains a pointer to X, the const struct will contain a const pointer to non-const X!
i.e. the function will not be able to change the pointer, but it can still change whatever the pointer is pointing at: in this case X.

So you'll either need to change all pointers to pointers to const (don't have to be const pointers to const, just pointers to const), or get rid of the pointers altogether.

Using a C++ std::vector instead of (int count, pointer to first element) would remove the pointers, if you can use it. The vector API nicely propagates the const to its data, without introducing execution overhead - the constness is solved at compile time.

Though having pointers to const is not as much of a problem as the first solution (with all members being const), because you can do this:

Code: Select all

struct aStruct {
  int count;
  const double *values;
}

double *myarray = malloc(...)
myarray[0] = 42; // initialization is ok, it's not const yet
aStruct *struct = malloc(...)
struct->count = 42; // ok, it's not const yet
struct->values = (const double *) myarray; // no manipulation of the doubles shall be possible via the struct
const aStruct *publicPointer = (const aStruct *) struct; // no manipulation of the struct members shall be possible via this pointer
// You can now safely pass the publicPointer whereever - all members and array elements are read-only when accessed via publicPointer
}

User avatar
Qaanol
The Cheshirest Catamount
Posts: 3039
Joined: Sat May 09, 2009 11:55 pm UTC

Re: C question: const structs

Postby Qaanol » Mon Mar 16, 2015 2:48 pm UTC

Tub wrote:
  • If you want the struct members to be truly const, define the members as const. That way, no code can modify them after initialization - which is a problem when using malloc, because you can initialize local variables, but not a malloc'ed block. (at least not without hacks; see below)
  • If you want to define an API where some parts of the program may write to the struct, while others may only read, pass the struct as const to the restricted areas of your code, but keep it non-const otherwise.
  • If you're trying to define a public API for a library, and you may need to extend or change your struct later, use an opaque pointer (take a look at e.g. the zlib API). Not as much for the constness, but because the layout of your struct may change. This will prevent read-only access, too.
Of course, all three of these solution protect only against accidental manipulation, not against malicious ones. In either case, a pointer is available, and it is trivial to cast that pointer to a non-const pointer to anything. So with enough malice, the values can be changed. If you need to be absolutely certain, allocate a page directly, initialize the struct there, then ask your operating system to turn the whole page read-only. Probably overkill, lacks portability, but it is possible.


Thanks Tub, I appreciate the insights. The structs are truly constant—any client code interacting with a struct will not be able to modify any of its members nor what the pointer members point to. The only time any member of the struct gets written to, is when the struct is initialized.

The data being pointed to from the struct is allocated and managed elsewhere, and may sometimes change “out from under” the struct, but it won’t disappear. The code to read things via the struct takes care of making sure the data is ready. Client code does not need to see the members of the struct for any reason.

As you say, malloc is somewhat bothersome here, but I think I’ve figured out what I want to do.
wee free kings


Return to “Coding”

Who is online

Users browsing this forum: Majestic-12 [Bot] and 6 guests