次のプログラムを検討してください。
#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);
}
私はこの種の設計で特に悪いことは何も認識していませんが、コンパイラがコードを大幅に最適化しているときに影響があるかもしれません。
const
でC
を書き込みアクセス制御のメカニズムとしてこのように使用することは良い考えですか?
いいえ、そのような方法で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);
}
6.7.3型修飾子
...
6非const修飾型の左辺値を使用して、const修飾型で定義されたオブジェクトを変更しようとした場合、 動作は未定義です。非揮発性修飾型の左辺値を使用して、揮発性修飾型で定義されたオブジェクトを参照しようとした場合、動作は未定義です。133)
133)これは、プログラムで実際にオブジェクトとして定義されていない場合でも(メモリマップされた入出力アドレスのオブジェクトなど)、たとえ修飾された型で定義されているかのように動作するオブジェクトに適用されます。
鉱山を強調します。
いいえ、これは良い考えではありません。構造体型のコンテンツを直接だまされたくない場合は、構造体型を不透明にし、それを操作するためのAPIを提供します。
私の個人的な例はLinuxカーネルから来ています:
struct firmware {
size_t size;
const u8 *data;
struct page **pages;
/* firmware loader private fields */
void *priv;
};
構造体定義内でのconst
の有効な使用について読者に思い出させてください。この場合、data
フィールドは一部のデータの読み取り専用ハンドルとして機能します。