9

Consider the following program:

#include <stdlib.h>
#include <stdio.h>

typedef struct S_s {
    const int _a;
} S_t;

S_t *
create_S(void) {
    return calloc(sizeof(S_t), 1);
}

void
destroy_S(S_t *s) {
    free(s);
}

const int
get_S_a(const S_t *s) {
    return s->_a;
}

void
set_S_a(S_t *s, const int a) {
    int *a_p = (int *)&s->_a;
    *a_p = a;
}

int
main(void) {
    S_t s1;
    // s1._a = 5; // Error
    set_S_a(&s1, 5); // OK
    S_t *s2 = create_S();
    // s2->_a = 8; // Error
    set_S_a(s2, 8); // OK

    printf("s1.a == %d\n", get_S_a(&s1));
    printf("s2->a == %d\n", get_S_a(s2));

    destroy_S(s2);
}

I'm not aware of anything particularly bad with this kind of design, but there might be impact when compiler is heavily optimizing the code and some considerations I'm missing.

Is it a good idea to use const in C as a mechanism of write access control in such a way?

Michael Pankov
  • 568
  • 1
  • 5
  • 15

3 Answers3

13

No, using const in such a way is not a good idea.

By declaring your structure fields as const, you are declaring an intention that those fields will never change their value. If you then change the value after all, you are misleading both human readers of your code and the compiler. The first makes the code harder to understand and the second can be the cause for subtle bugs in your program.

If you want to avoid direct access to the members of your structure, then you can just use it as an opaque type:

//in s.h:
typedef struct S_s S_t;

S_t *create_S(void);
void destroy_S(const S_t *s);
int get_S_a(const S_t *s);
void set_S_a(S_t *s, const int a);

//in s.c
#include "s.h"
struct S_s {
    const int _a;
};

S_t *
create_S(void) {
    return calloc(1, sizeof(S_t));
}

void
destroy_S(const S_t *s) {
    free((S_t *)s);
}

int
get_S_a(const S_t *s) {
    return s->_a;
}

void
set_S_a(S_t *s, const int a) {
    s->_a = a;
}

// In main.c
#include "s.h"
int
main(void) {
    // const S_t s1; // Error: size of S_t unknown here
    S_t *s2 = create_S();
    // s2->_a = 8; // Error: members of S_t unknown here
    set_S_a(s2, 8); // OK

    printf("s2->a == %d\n", get_S_a(s2));

    destroy_S(s2);
}
Bart van Ingen Schenau
  • 71,712
  • 20
  • 110
  • 179
  • Then I can't create such structures on stack and have arrays of them? – Michael Pankov Dec 27 '13 at 15:46
  • @constantius: That is right. As C does not have proper access control, you have to choose between access protection and the ability to allocate on the stack or in an array. – Bart van Ingen Schenau Dec 27 '13 at 15:56
  • Note I've modified the code so the `const`ness of the structure itself is not used. Also, see https://www.securecoding.cert.org/confluence/display/seccode/EXP05-C.+Do+not+cast+away+a+const+qualification and exception **EXP05-EX3** in particular. Seems like `const` fields aren't optimized in any way when the containing structure is not `const`. Standard reference would be good here. – Michael Pankov Dec 27 '13 at 16:14
  • @constantius: Casting away constness on members also causes undefined behaviour, but is less likely to cause actual problems. A full standard reference would not fit in a comment (If you really want one, ask a new Q for it on StackOverflow.com) – Bart van Ingen Schenau Dec 27 '13 at 16:33
  • So these people writing a *secure coding standard* are promoting *insecure* code by making an exception for `const`-qualification of structure fields? Oh my. – Michael Pankov Dec 27 '13 at 16:38
  • I've actually found an existing answer with reference: http://stackoverflow.com/a/18529041/113134 – Michael Pankov Dec 27 '13 at 16:45
  • Having considered the reasons for such wording in the Standard for a while, I have to say that there's no compiler that implements a "real" `const` qualification of fields of non-`const` structures. That is, if the structure is not constant, it's never is going to be placed to read-only storage, even if all the fields are constant (think of `memset` of such a structure — you don't access the fields). Should such "accurate" implementation exist, it would be very ineffective and won't be "zero overhead", which is the main principle of `C`. – Michael Pankov Dec 29 '13 at 01:32
  • @constantius: I would guess that the main reason for not making a difference between structure fields (or other sub-objects) and 'top-level' objects in this regard is simply to not complicate the standard any more than needed. – Bart van Ingen Schenau Dec 29 '13 at 11:56
  • Do you know what's the point of stating that a non-const object containing a const object is considered const and shouldn't be assigned to? There's clearly no case a writable area of memory representing a non-const struct can be considered "partially non-writable" because of const fields. – Michael Pankov Dec 29 '13 at 16:57
  • @constantius: `const` in C does not mean 'constant' but 'read-only'. How would you explain it if you would be able to modify a complete structure that has some read-only parts in it? It does not matter if the underlying memory representation can be changed, but if the conceptual operation makes sense. – Bart van Ingen Schenau Dec 29 '13 at 17:29
  • I know about "read-only" semantics of `const`. But *you are* able to overwrite the structure with constant fields. The only thing I'm talking about is "legalizing" the way most implementations work, and not considering an assignment to `const` field of non-`const` structure a UB. – Michael Pankov Dec 29 '13 at 19:52
  • 2
    @MichaelPankov The implementation can assume that the value never changes, and decide to elide loads. Suddenly your object "has two states" depending on how it is observed – Caleth Jan 20 '20 at 10:16
9

C 2011 draft

6.7.3 Type qualifiers
...
6 If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined. If an attempt is made to refer to an object defined with a volatile-qualified type through use of an lvalue with non-volatile-qualified type, the behavior is undefined.133)

133) This applies to those objects that behave as if they were defined with qualified types, even if they are never actually defined as objects in the program (such as an object at a memory-mapped input/output address).

Emphasis mine.

So no, this isn't a good idea. If you don't want anyone directly mucking with the contents of your struct type, make it opaque and provide an API to manipulate it.

John Bode
  • 10,826
  • 1
  • 31
  • 43
0

My personal example comes from the Linux kernel:

struct firmware {
    size_t size;
    const u8 *data;
    struct page **pages;

    /* firmware loader private fields */
    void *priv;
};

I would like to remind readers of a valid use of const inside a struct definition. In this case, the data field acts as a read-only handle on some data.

bazz
  • 101
  • 2
  • 9
    `data` field is a pointer to `const u8`, but the field itself is not const and can be set to point to a different address. – vgru Feb 26 '20 at 14:53