C-container design with `const` elements?

If the container interface declares pointers to contained elements as const ?

Task: create a container in C (Note: this is clearly about simple C, not about C ++, nor C #). The container must be provided with pointers to and return pointers to elements.

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

 #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, a container should not use pointers to modify stored data. I would like to make this understandable in the interface, change a little:

 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 another change, which is also logical:

 const void *get(container_t *c) ^new 

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

  • Create a container without const ? Sometimes I see this library documentation, for example, Glib [ https://developer.gnome.org/glib/2.42/glib-Double-ended-Queues.html#g-queue-push-tail] . But I would really like the "security" const .

  • what he might need the return value get const throw away? That is, call

     y = (int *)get(&c); 
  • I would prefer not to discard the const inside the get function, as in

     return (void *)c->data; 

    because I don’t know if the calling item should const at all. underlined text

+6
source share
4 answers

Your interface enters into a contract, that is, the object that is passed into it will never be modified through this access. Thus, in essence, you will need two types of container or, more generally, interfaces that assume that the object is modified ( void* version), and one that assumes that it can be changed ( void const* version.

This is a problem that occurs in many places, even in the C library. For example, memchr is an interface that silently performs the conversion of a const object. This direct cheating can be circumvented in modern C with _Generic .

It is impossible to avoid the problem of saving the value, and then reproduce it in a completely unrelated place in the code. C and POSIX standards have this problem for tss_set/tss_get and pthread_setspecific/pthread_getspecific and solve the problem in an incompatible way. Variant C has void* for both variants POSIX has void const* for setting and void* for receiving.

+2
source

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

If you still need a pointer to a constant, then your second approach works best. But if the user can discard const, then the container code as such, the promise not to modify the data is shallow.

Also note that your container stores a pointer to void. This in itself is a convincing sign for the user that the container code cannot do much, because he does not know about the type of data that the pointer points to. Yes, he can still write garbage in this memory location.

Given that the constant const does not guarantee constancy, that is, the compiler may not put constant data in read-only memory, the container code user may still try to use const. Now it is possible that the const data may actually be in read-only memory or the user can provide the container with an address from read-only memory. That is, the user has ensured that no code can change persistent data. In this case, you do not need to worry about defining a pointer to persistent data. If the user confirms that the data cannot be made read-only, then he / she can add data such as CRC to the data. Thus, when he / she captures data, he / she can check whether the data is reliable or not.

To summarize : if you have a pointer to void, then making it a pointer to const void is pointless. In addition, the user must protect their data. A data structure should only care about its purpose.

+2
source

const * means that you can receive and modify data at this address, but you cannot change the address that you specify.

This means that the user wants to change their content content, will be able to do it. But the address will remain the same.

However, everyone can point their pointer to some address. You cannot prevent this.

See the link (The C Book - Const and Volatile) in chapter 8.4.1 for more information.

+1
source

If you need pointer constant y, the correct path is: int * const y = get(&c);

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

+1
source

Source: https://habr.com/ru/post/979936/


All Articles