Programing

C 구조체에서 멤버 숨기기

lottogame 2020. 12. 7. 07:44
반응형

C 구조체에서 멤버 숨기기


저는 C에서 OOP에 대해 읽었지만 C ++에서와 같이 개인 데이터 멤버를 가질 수없는 방법을 결코 좋아하지 않았습니다. 하지만 두 개의 구조물을 만들 수 있다는 생각이 들었습니다. 하나는 헤더 파일에 정의되고 다른 하나는 소스 파일에 정의됩니다.

// =========================================
// 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;
}

여기에서 한 구조를 다른 구조로 캐스팅 할 수 있습니다. 이것은 나쁜 습관으로 간주됩니까? 아니면 자주하나요?


개인적으로 더 좋아합니다.

typedef struct {
  int _public_member;
  /*I know you wont listen, but don't ever touch this member.*/
  int _private_member;
} SomeStructSource;

결국 C입니다. 만약 사람들이 망치고 싶다면 다음을 제외하고는 물건을 숨길 필요가 없습니다.

필요한 것이 ABI / API 호환성을 유지하는 것이라면 제가 본 것보다 더 일반적인 두 가지 접근 방식이 있습니다.

  • 클라이언트에게 구조체에 대한 액세스 권한을 부여하지 말고, 불투명 한 핸들 (예쁜 이름의 void *)을 제공하고, 모든 것에 대해 init / destroy 및 접근 자 함수를 제공하십시오. 이렇게하면 라이브러리를 작성하는 경우 클라이언트를 다시 컴파일하지 않고도 구조를 변경할 수 있습니다.

  • 구조체의 일부로 불투명 핸들을 제공하여 원하는대로 할당 할 수 있습니다. 이 접근 방식은 ABI 호환성을 제공하기 위해 C ++에서도 사용됩니다.

예 :

 struct SomeStruct {
  int member;
  void* internals; //allocate this to your private struct
 };

sizeof(SomeStruct) != sizeof(SomeStructSource). 이것은 것입니다 누군가가 당신을 찾아 언젠가 당신을 살해하도록합니다.


거의 가지고 있지만 충분히 멀리 가지 않았습니다.

헤더에서 :

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의 OOP에 대한 올바른 디자인 패턴은 모든 데이터에 액세스하는 기능을 제공하고 데이터에 대한 공개 액세스를 허용하지 않는 것입니다. 클래스 데이터는 개인이 될하기 위해, 소스에서 선언되어야하고, 앞으로 방식으로 참조 할 곳 CreateDestroy할당 및 데이터의 자유를한다. 그런 식으로 공공 / 사적 딜레마는 더 이상 존재하지 않을 것입니다.

/*********** 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 */
}

그렇게하지 마십시오. API가 SomeStruct를 매개 변수로 사용하는 것을 지원하는 경우 (예상대로) 스택에 하나를 할당하고 전달할 수 있습니다. 컴파일러가 개인 멤버에 액세스하려고하면 중대한 오류가 발생합니다. 클라이언트 클래스에 대한 할당에는 공간이 포함되어 있지 않습니다.

구조체에서 멤버를 숨기는 고전적인 방법은 void *로 만드는 것입니다. 기본적으로 구현 파일 만 알고있는 핸들 / 쿠키입니다. 거의 모든 C 라이브러리가 개인 데이터를 위해이 작업을 수행합니다.


당신이 제안한 방법과 비슷한 것이 실제로 가끔 사용되지만 (예 : 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;
}

숨겨진 구조를 작성하고 공용 구조의 포인터를 사용하여 참조합니다. 예를 들어, .h는 다음을 가질 수 있습니다.

typedef struct {
    int a, b;
    void *private;
} public_t;

그리고 .c :

typedef struct {
    int c, d;
} private_t;

분명히 포인터 산술로부터 보호하지 않으며 할당 / 할당 해제에 약간의 오버 헤드를 추가하지만 질문의 범위를 벗어난 것 같습니다.


다음 해결 방법을 사용하십시오.

#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

void *퍼블릭 구조체에서 프라이빗 구조체에 대한 포인터를 사용하는 것과 같은 더 나은 방법이 있습니다. 당신이하는 방식은 컴파일러를 속이고 있습니다.


This approach is valid, useful, standard C.

A slightly different approach, used by sockets API, which was defined by BSD Unix, is the style used for struct sockaddr.


Not very private, given that the calling code can cast back to a (SomeStructSource *). Also, what happens when you want to add another public member? You'll have to break binary compatibility.

EDIT: I missed that it was in a .c file, but there really is nothing stopping a client from copying it out, or possibly even #includeing the .c file directly.


Related, though not exactly hiding.

Is to conditionally deprecate members.

Note that this works for GCC/Clang, but MSVC and other compilers can deprecate too, so its possible to come up with a more portable version.

If you build with fairly strict warnings, or warnings as errors, this at least avoids accidental use.

// =========================================
// 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;
}

My solution would be to provide only the prototype of the internal struct and then declare the definition in the .c file. Very useful to show C interface and use C++ behind.

.h :

struct internal;

struct foo {
   int public_field;
   struct internal *_internal;
};

.c :

struct internal {
    int private_field; // could be a C++ class
};

Note: In that case, the variable have to be a pointer because the compiler is unable to know the size of the internal struct.

참고URL : https://stackoverflow.com/questions/2672015/hiding-members-in-a-c-struct

반응형