web-dev-qa-db-ja.com

Cの構造体のフィールドをconst修飾することは良い考えですか?

次のプログラムを検討してください。

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

私はこの種の設計で特に悪いことは何も認識していませんが、コンパイラがコードを大幅に最適化しているときに影響があるかもしれません。

constCを書き込みアクセス制御のメカニズムとしてこのように使用することは良い考えですか?

6
Michael Pankov

いいえ、そのような方法でconstを使用することはnotをお勧めします。

構造体フィールドをconstとして宣言することにより、それらのフィールドが値を変更しないことを宣言します。結局、値を変更すると、人間のコードの読者とコンパイラの両方を誤解させることになります。 1つ目はコードを理解しにくくし、2つ目はプログラムの微妙なバグの原因になる可能性があります。

構造体のメンバーへの直接アクセスを避けたい場合は、不透明型として使用できます。

//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(sizeof(S_t), 1);
}

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

C 2011ドラフト

6.7.3型修飾子
...
6非const修飾型の左辺値を使用して、const修飾型で定義されたオブジェクトを変更しようとした場合、 動作は未定義です。非揮発性修飾型の左辺値を使用して、揮発性修飾型で定義されたオブジェクトを参照しようとした場合、動作は未定義です。133)

133)これは、プログラムで実際にオブジェクトとして定義されていない場合でも(メモリマップされた入出力アドレスのオブジェクトなど)、たとえ修飾された型で定義されているかのように動作するオブジェクトに適用されます。

鉱山を強調します。

いいえ、これは良い考えではありません。構造体型のコンテンツを直接だまされたくない場合は、構造体型を不透明にし、それを操作するためのAPIを提供します。

8
John Bode

私の個人的な例はLinuxカーネルから来ています:

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

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

構造体定義内でのconstの有効な使用について読者に思い出させてください。この場合、dataフィールドは一部のデータの読み取り専用ハンドルとして機能します。

0
bazz