C 구조체의 부재 숨기기
C에서 OOP에 대해 읽은 적이 있지만, C++에서처럼 개인 데이터 구성원을 가질 수 없다는 점이 마음에 들지 않습니다.그런데 갑자기 2개의 구조물을 만들 수 있다는 생각이 들었어요.하나는 헤더 파일에 정의되어 있고 다른 하나는 소스 파일에 정의되어 있습니다.
// =========================================
// in somestruct.h
typedef struct {
int _public_member;
} SomeStruct;
// =========================================
// in somestruct.c
#include "somestruct.h"
typedef struct {
int _public_member;
int _private_member;
} SomeStructSource;
SomeStruct *SomeStruct_Create()
{
SomeStructSource *p = (SomeStructSource *)malloc(sizeof(SomeStructSource));
p->_private_member = 42;
return (SomeStruct *)p;
}
여기서부터는 한 구조물을 다른 구조물로 주조할 수 있습니다.이것이 나쁜 관행으로 간주됩니까?아니면 자주 하나요?
sizeof(SomeStruct) != sizeof(SomeStructSource)
언젠가 누군가 당신을 찾아내서 살해할 겁니다
개인적으로 저는 이렇게 하고 싶어요.
typedef struct {
int _public_member;
/*I know you wont listen, but don't ever touch this member.*/
int _private_member;
} SomeStructSource;
결국 C입니다. 만약 사람들이 실수를 하고 싶다면, 그들은 무언가를 숨길 필요가 없습니다. 단, 다음을 제외하곤 말이죠.
ABI/API의 호환성을 유지하는 것이 필요한 경우, 제가 본 바로는 두 가지 접근방식이 있습니다.
고객에게 구조물에 대한 액세스 권한을 부여하지 말고 불투명한 핸들(예쁜 이름의 보이드*)을 부여하고 모든 것에 대해 초기화/파괴 및 액세스 기능을 제공합니다.이렇게 하면 라이브러리를 작성할 때 클라이언트를 다시 컴파일하지 않고도 구조를 변경할 수 있습니다.
구조의 일부로 불투명 핸들을 제공하여 원하는 대로 할당할 수 있습니다.이 접근방식은 ABI 호환성을 제공하기 위해 C++에서도 사용됩니다.
예
struct SomeStruct {
int member;
void* internals; //allocate this to your private struct
};
나는 공공 구조 패턴을 사용하는 것을 추천하지 않는다.C의 OOP에 대한 올바른 설계 패턴은 모든 데이터에 액세스할 수 있는 기능을 제공하고 데이터에 대한 공개 액세스를 허용하지 않는 것입니다.소스에서 서 클래스 데이터는 으로 참조됩니다.Create
★★★★★★★★★★★★★★★★★」Destroy
는 할당하여 데이터를 해방합니다.이런 식으로 공공/사적 딜레마는 더 이상 존재하지 않을 것이다.
/*********** header.h ***********/
typedef struct sModuleData module_t'
module_t *Module_Create();
void Module_Destroy(module_t *);
/* Only getters and Setters to access data */
void Module_SetSomething(module_t *);
void Module_GetSomething(module_t *);
/*********** source.c ***********/
struct sModuleData {
/* private data */
};
module_t *Module_Create()
{
module_t *inst = (module_t *)malloc(sizeof(struct sModuleData));
/* ... */
return inst;
}
void Module_Destroy(module_t *inst)
{
/* ... */
free(inst);
}
/* Other functions implementation */
반대로 Malloc/Free(상황에 따라서는 불필요한 오버헤드일 수 있음)를 사용하지 않는 경우에는 개인 파일에서 구조를 숨길 것을 권장합니다.개인 회원은 액세스 할 수 있지만, 유저에게 달려 있습니다.
/*********** privateTypes.h ***********/
/* All private, non forward, datatypes goes here */
struct sModuleData {
/* private data */
};
/*********** header.h ***********/
#include "privateTypes.h"
typedef struct sModuleData module_t;
void Module_Init(module_t *);
void Module_Deinit(module_t *);
/* Only getters and Setters to access data */
void Module_SetSomething(module_t *);
void Module_GetSomething(module_t *);
/*********** source.c ***********/
void Module_Init(module_t *inst)
{
/* perform initialization on the instance */
}
void Module_Deinit(module_t *inst)
{
/* perform deinitialization on the instance */
}
/*********** main.c ***********/
int main()
{
module_t mod_instance;
module_Init(&mod_instance);
/* and so on */
}
거의 다 됐지만 아직 멀지는 않았어요.
머리글:
struct SomeStruct;
typedef struct SomeStruct *SomeThing;
SomeThing create_some_thing();
destroy_some_thing(SomeThing thing);
int get_public_member_some_thing(SomeThing thing);
void set_public_member_some_thing(SomeThing thing, int value);
.c에서:
struct SomeStruct {
int public_member;
int private_member;
};
SomeThing create_some_thing()
{
SomeThing thing = malloc(sizeof(*thing));
thing->public_member = 0;
thing->private_member = 0;
return thing;
}
... etc ...
요점은 이제 소비자는 SomeStruct의 내부 정보를 전혀 모르고 있으며, 사용자가 재컴파일할 필요가 없어도 자유롭게 멤버를 추가하거나 삭제할 수 있다는 것입니다.또한 멤버를 직접 "우발적으로" 뭉치거나 SomeStruct를 스택에 할당할 수도 없습니다.물론 이것도 단점으로 볼 수 있다.
저의 해결책은 내부 구조의 프로토타입만 제공하고 .c 파일에 정의를 선언하는 것입니다.C 인터페이스를 표시하고 뒤에서 C++ 를 사용하는 경우에 매우 편리합니다.
.h:
struct internal;
struct foo {
int public_field;
struct internal *_internal;
};
.c:
struct internal {
int private_field; // could be a C++ class
};
주의: 이 경우 컴파일러는 내부 구조의 크기를 알 수 없기 때문에 변수는 포인터여야 합니다.
.bit-field
정말 숨기고 싶은 게 있다면 좋은 해결책이 될 수도 있어요.
struct person {
unsigned long :64;
char *name;
int age;
};
struct wallet {
char *currency;
double balance;
};
구조체의 첫 번째 구성원은 이름 없는 비트엘드이다.에 사용되다64-bit pointer
이 경우는,완전히 숨겨져 구조 변수 이름으로 액세스할 수 없습니다.
이 구조체의 첫 번째 64비트는 사용되지 않기 때문에 개인 포인터로 사용할 수 있습니다.이 멤버는 변수 이름 대신 메모리 주소로 액세스할 수 있습니다.
void init_person(struct person* p, struct wallet* w) {
*(unsigned long *)p = (unsigned long)w;
// now the first 64-bit of person is a pointer of wallet
}
struct wallet* get_wallet(struct person* p) {
return (struct wallet*)*(unsigned long *)p;
}
인텔(R) Mac에서 테스트한 작은 작업 예:
//
// Created by Rieon Ke on 2020/7/6.
//
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#if __x86_64__ || __LP64__
#define PRIVATE_SET(obj, val) *(unsigned long *) obj = (unsigned long) val;
#define PRIVATE_GET(obj, type) (type)*(unsigned long *) obj;
#define PRIVATE_POINTER unsigned long:64
#else
#define PRIVATE_SET(obj, val) *(unsigned int *) obj = (unsigned int) val;
#define PRIVATE_GET(obj, type) (type)*(unsigned int *) obj;
#define PRIVATE_POINTER unsigned int:32
#endif
struct person {
PRIVATE_POINTER;
char *name;
int age;
};
struct wallet {
char *currency;
double balance;
};
int main() {
struct wallet w;
w.currency = strdup("$$");
w.balance = 99.9;
struct person p;
PRIVATE_SET(&p, &w) //set private member
p.name = strdup("JOHN");
p.age = 18;
struct wallet *pw = PRIVATE_GET(&p, struct wallet*) //get private member
assert(strcmp(pw->currency, "$$") == 0);
assert(pw->balance == 99.9);
free(w.currency);
free(p.name);
return 0;
}
다음의 회피책을 사용합니다.
#include <stdio.h>
#define C_PRIVATE(T) struct T##private {
#define C_PRIVATE_END } private;
#define C_PRIV(x) ((x).private)
#define C_PRIV_REF(x) (&(x)->private)
struct T {
int a;
C_PRIVATE(T)
int x;
C_PRIVATE_END
};
int main()
{
struct T t;
struct T *tref = &t;
t.a = 1;
C_PRIV(t).x = 2;
printf("t.a = %d\nt.x = %d\n", t.a, C_PRIV(t).x);
tref->a = 3;
C_PRIV_REF(tref)->x = 4;
printf("tref->a = %d\ntref->x = %d\n", tref->a, C_PRIV_REF(tref)->x);
return 0;
}
결과는 다음과 같습니다.
t.a = 1
t.x = 2
tref->a = 3
tref->x = 4
것이 사용되기도 다양한 의 방법을 하십시오).struct sockaddr*
BSD 소켓 API)에 포함되지만 C99의 엄격한 에일리어스 규칙을 위반하지 않고는 사용이 거의 불가능합니다.
단, 다음과 같이 안전하게 수행할 수 있습니다.
somestruct.h
:
struct SomeStructPrivate; /* Opaque type */
typedef struct {
int _public_member;
struct SomeStructPrivate *private;
} SomeStruct;
somestruct.c
:
#include "somestruct.h"
struct SomeStructPrivate {
int _member;
};
SomeStruct *SomeStruct_Create()
{
SomeStruct *p = malloc(sizeof *p);
p->private = malloc(sizeof *p->private);
p->private->_member = 0xWHATEVER;
return p;
}
절대 그러지 마세요.만약 당신의 API가 SomeStruct를 파라미터로 사용하는 것을 지원한다면(내 예상대로), 스택에 할당하여 전달할 수 있습니다.컴파일러가 클라이언트 클래스에 할당하는 개인 멤버에 대한 공간이 없기 때문에 개인 멤버에 액세스하려고 하면 큰 오류가 발생합니다.
구조체의 부재를 숨기는 전형적인 방법은 부재를 보이드로 만드는 것입니다*.기본적으로는 구현 파일만 아는 핸들/쿠키입니다.거의 모든 C 라이브러리가 개인 데이터에 대해 이 기능을 수행합니다.
숨겨진 구조물을 써서 공공 구조물에 포인터를 사용해서 참조할 거예요.예를 들어, .h에는 다음과 같은 것이 있습니다.
typedef struct {
int a, b;
void *private;
} public_t;
그리고 당신의 .c:
typedef struct {
int c, d;
} private_t;
이것은 분명히 포인터 산술로부터 보호되지 않고, 할당/해제를 위해 약간의 오버헤드를 더하지만, 질문의 범위를 벗어난다고 생각합니다.
더 요. 예를 '아까보다'를 하는 방법이 요. ★★★★★★★★★★★★★★★★★,void *
공용 구조체의 개인 구조물에 대한 포인터입니다.당신이 하는 방식은 컴파일러를 속이는 것입니다.
이 접근방식은 유효하고 유용한 표준 C입니다.
BSD Unix에 의해 정의된 소켓 API에 의해 사용되는 약간 다른 접근 방식은 다음과 같습니다.struct sockaddr
.
발신자 코드가 다른 사람에게 캐스트 될 수 있기 때문에 매우 사적인 것은 아닙니다.(SomeStructSource *)
또, 다른 공개 멤버를 추가하고 싶은 경우는 어떻게 됩니까?바이너리 호환성을 깨야 합니다.
편집: .c 파일로 되어 있는 것을 잊어버렸습니다만, 클라이언트에 의한 카피를 막을 수 있는 것은 아무것도 없습니다.또, 그 경우에도 마찬가지입니다.#include
.c 파일을 직접 입력한다.
관련이 있긴 하지만 정확히 숨긴 건 아니야
조건부로 멤버를 비하하는 것입니다.
이것은 GCC/Clang에서는 동작하지만, MSVC나 다른 컴파일러에서도 권장하지 않기 때문에, 보다 포터블한 버전을 작성할 수 있습니다.
상당히 엄격한 경고 또는 경고를 오류로 빌드하면 최소한 실수로 인한 사용을 방지할 수 있습니다.
// =========================================
// in somestruct.h
#ifdef _IS_SOMESTRUCT_C
# if defined(__GNUC__)
# define HIDE_MEMBER __attribute__((deprecated))
# else
# define HIDE_MEMBER /* no hiding! */
# endif
#else
# define HIDE_MEMBER
#endif
typedef struct {
int _public_member;
int _private_member HIDE_MEMBER;
} SomeStruct;
#undef HIDE_MEMBER
// =========================================
// in somestruct.c
#define _IS_SOMESTRUCT_C
#include "somestruct.h"
SomeStruct *SomeStruct_Create()
{
SomeStructSource *p = (SomeStructSource *)malloc(sizeof(SomeStructSource));
p->_private_member = 42;
return (SomeStruct *)p;
}
익명의 구조물은 여기서 유용하게 쓰일 수 있다.
#ifndef MYSTRUCT_H
#define MYSTRUCT_H
typedef struct {
int i;
struct {
int j;
} MYSTRUCT_PRIVATE;
// NOTE: Avoid putting public members after private
int k;
} MyStruct;
void test_mystruct();
#endif
개인 구성원에 대한 액세스 권한이 있는 모든 파일에서MYSTRUCT_PRIVATE
이 헤더를 포함하기 전에 빈 토큰으로 지정합니다.이러한 파일에서 개인 구성원은 익명 구조 내에 있으며 다음을 사용하여 액세스할 수 있습니다.m.j
그 외의 장소에서는, 다음의 방법으로 밖에 액세스 할 수 없습니다.m.MYSTRUCT_PRIVATE.j
.
#define MYSTRUCT_PRIVATE
#include "mystruct.h"
void test_mystruct() {
// Can access .j without MYSTRUCT_PRIVATE in both
// initializer and dot operator.
MyStruct m = { .i = 10, .j = 20, .k = 30 };
m.j = 20;
}
#include <stdio.h>
#include "mystruct.h"
int main() {
// You can declare structs and, if you jump through
// a small hoop, access private members
MyStruct m = { .i = 10, .k = 30 };
m.MYSTRUCT_PRIVATE.j = 20;
// This will not work
//MyStruct m2 = { .i = 10, .j = 20, .k = 30 };
// But this WILL work, be careful
MyStruct m3 = { 10, 20, 30 };
test_mystruct();
return 0;
}
나는 민간회원 뒤에 공공회원을 두는 것을 추천하지 않는다.부재 지정자 없이 구조물 초기화(예:{ 10, 20, 30 }
는 프라이빗 멤버를 초기화할 수 있습니다.개인 구성원 수가 변경되면 구성원 지정자가 없는 모든 이니셜라이저도 자동으로 중단됩니다.이를 피하기 위해 항상 회원 지정자를 사용하는 것이 좋습니다.
C++와 같이 자동 생성자가 없으므로 구조체, 특히 개인 구성원을 0으로 초기화하도록 설계해야 합니다.멤버는 0으로 초기화되어 있는 한 초기화 기능이 없어도 무효 상태가 되지 않습니다.구성원 지정자 초기화 금지, 단순 초기화{ 0 }
안전하도록 설계되어야 합니다.
제가 발견한 유일한 단점은 디버거나 코드 완성 같은 것들이 엉망이 된다는 것입니다.그들은 보통 어떤 타입의 멤버가 한 파일에 있고 다른 타입의 멤버가 다른 파일에 있는 것을 좋아하지 않습니다.
언급URL : https://stackoverflow.com/questions/2672015/hiding-members-in-a-c-struct
'programing' 카테고리의 다른 글
VueJS 커스텀 디렉티브 + import 이벤트 (0) | 2022.08.11 |
---|---|
Vue에서 대화 상자가 완료될 때까지 선택 양식을 변경하지 않도록 하는 방법 (0) | 2022.08.11 |
O(1)의 reducx/vuex 저장소의 요소를 업데이트하려면 어떻게 해야 합니까? (0) | 2022.08.11 |
C에서 열거형(enum)을 정의하려면 어떻게 해야 합니까? (0) | 2022.08.11 |
컴포넌트가 정의되어 있지만 사용되지 않는 vars는 사용되지 않습니다. (0) | 2022.08.11 |