C Design – Is It a Good Idea to Const-Qualify Structure Fields?

access-controlcdesign

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?

Best Answer

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);
}