11

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?

3 Answers 3

14

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);
}
12
  • Then I can't create such structures on stack and have arrays of them? Dec 27, 2013 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. Dec 27, 2013 at 15:56
  • Note I've modified the code so the constness of the structure itself is not used. Also, see securecoding.cert.org/confluence/display/seccode/… 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. Dec 27, 2013 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) Dec 27, 2013 at 16:33
  • 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, 2020 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.

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.

1
  • 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, 2020 at 14:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.