programing

C 구조체의 부재 숨기기

goodsources 2022. 8. 11. 22:24
반응형

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

반응형