Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Design C-container with `const` elements?

Should a container interface declare pointers to the contained elements as const?

Task: Design a container in C (Note: this is explicitly about plain C, not about C++, nor C#). The container is to be fed with pointers to items, and should return pointers to items.

A somewhat pointless MWE, but the idea extends to useful containers as well:

#include <stdio.h>

typedef struct {
    void *data;
} container_t;

void put(container_t *c, void *data)
{
    c->data = data;
}

void *get(container_t *c)
{
    return c->data;
}

int main(void)
{
    container_t c;
    int x = 42;
    int *y;

    put(&c, &x);
    y = get(&c);

    printf("value = %d\n", *y);

    return 0;
}

So far, so good.

Well, the container should not use the pointers to modify the stored data. I'd like to make this clear in the interface, by a small change:

void put(container_t *c, const void *data)
                         ^new

Now the compiler asks me to make another change, which I really do agree with:

typedef struct { const void *data; } container_t;
                 ^new

Then the compiler asks me to make one more change, which is quite logical as well:

const void *get(container_t *c)
^new

And now, the compiler complains about y not being const int *, which makes me a bit unhappy. What is the right way to deal with this?

  • Design the container without const? I see this sometimes in library documentation, e.g., Glib [https://developer.gnome.org/glib/2.42/glib-Double-ended-Queues.html#g-queue-push-tail]. But I'd really like the "safety" const provides.

  • document that the return value from get may need it's const cast away? I.e. call as

    y = (int *)get(&c);
    
  • I would rather not cast away the const inside the get function, as in

    return (void *)c->data;
    

    because I do not know if the caller should actually consider the item const at all. emphasized text

like image 911
stefan Avatar asked Dec 19 '14 16:12

stefan


4 Answers

Your interface is making a contract, that is that the object that is passed into it will never be modified through that access. So in essence you would need two container types, or more generally to interfaces, one that supposes that the object is mutable (a void* version) and one that supposes that it can be modified (a void const* version.

This is a problem that occurs in many places, even in the C library. E.g memchr is such an interface that silently does a const conversion of the object. This direct treason could be circumvented in modern C with _Generic.

Your problem of storing the value and then reproduce it at completely unrelated place in the code can't be avoided. The C and POSIX standards have that problem for tss_set/tss_get and pthread_setspecific/pthread_getspecific, and solve the problem in an incompatible way. The C variant has void* for both, the POSIX variant has void const* for setting and void* for getting.

like image 51
Jens Gustedt Avatar answered Nov 15 '22 06:11

Jens Gustedt


A container type is just that - a container. It allows the user to add/remove/access the contained item. What the user does with it is up to the user.

If you still want a pointer to constant then your 2nd approach is what works best. But if a user can cast off const, so can the container's code, as such the promise of not modifying the data is shallow.

Also note that your container stores pointer to void. That it self is a strong indication to the user that the container's code can't do much because it has no idea about the type of data the pointer will be pointing to. Yes, it can still write garbage at that memory location.

Given that the const does not guarantee constantness i.e. the compiler might not place the const data in a read-only memory, the user of the container code can still attempt to use const. Now, it is possible that const data might actually end up in a read-only memory or the user might be providing the container the address from a read-only memory. That is, the user has ensured that no code can modify the constant data. In this case, you do not need to worry about defining a pointer to constant data. If the user has verified that the data cannot be made read-only then s/he can still add stuff like CRC to the data. That way, when s/he grabs the data s/he can verify if the data is reliable or not.

To summarize: if you have pointer to void then making it a pointer to const void is kind of pointless. Besides, it is up to the user to protect his/her data. A data structure should only worry about its purpose.

like image 2
RcnRcf Avatar answered Nov 15 '22 06:11

RcnRcf


const * means you can access and modify the data in that address but cannot modify the address you point.

That means if the user want to change your data content, will be able to do. But the address will be remaining the same.

However, everybody is free to set their pointer to some address. You cannot prevent this.

For more information, look at this link (The C Book - Const and Volatile) chapter 8.4.1.

like image 1
totten Avatar answered Nov 15 '22 04:11

totten


If you want the constness of the y pointer, the right way would be:
int * const y = get(&c);

If you want to constify what y points to, then, as you mentioned:
const int * y = get(&c);

like image 1
Andreas DM Avatar answered Nov 15 '22 04:11

Andreas DM