隐藏C结构中的成员

时间:2010-04-20 01:40:21

标签: c struct private-members

我一直在阅读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;
}

从这里你可以将一个结构投射到另一个结构。 这被认为是不好的做法吗?还是经常这样做?

14 个答案:

答案 0 :(得分:46)

sizeof(SomeStruct) != sizeof(SomeStructSource)。这个导致某人找到你并有一天谋杀你。

答案 1 :(得分:31)

就个人而言,我更喜欢这个:

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

毕竟,如果人们想要搞砸了,他们应该被允许 - 不需要隐藏东西,除了:

如果您需要保持ABI / API兼容,那么有两种方法比我看到的更常见。

  • 不要让你的客户端访问struct,给他们一个不透明的句柄(一个带有漂亮名字的void *),为所有东西提供init / destroy和accessor函数。这可以确保您可以更改 如果您正在编写库,则甚至无需重新编译客户端。

  • 提供一个不透明的句柄作为你的结构的一部分,你可以随意分配。这种方法甚至在C ++中用于提供ABI兼容性。

e.g

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

答案 2 :(得分:25)

你几乎拥有它,但还远远不够。

在标题中:

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的内部有了 no 的知识,你可以随意修改它,随意添加和删除成员,即使没有消费者需要重新编译。他们也不能“意外地”直接攻击成员,或者在堆栈上分配SomeStruct。这当然也可以被视为一个缺点。

答案 3 :(得分:15)

我不建议使用公共结构模式。对于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 */
}

答案 4 :(得分:9)

永远不要那样做。如果你的API支持任何以SomeStruct作为参数的东西(我期待它),那么他们可以在堆栈上分配一个并将其传入。你会因为编译器而试图访问私有成员时遇到重大错误客户端类的分配不包含空间。

在结构中隐藏成员的经典方法是使其成为无效*。它基本上是一个只有你的实现文件知道的句柄/ cookie。几乎每个C库都会为私有数据执行此操作。

答案 5 :(得分:7)

有时会使用与您提出的方法类似的东西(例如,在BSD套接字API中查看struct sockaddr*的不同变量),但在不违反C99的严格别名规则的情况下几乎不可能使用。 / p>

但是,你可以安全地

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

答案 6 :(得分:4)

我会编写一个隐藏的结构,并使用公共结构中的指针引用它。例如,你的.h可能有:

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

你的.c:

typedef struct {
    int c, d;
} private_t;

它显然不能防止指针算法,并为分配/释放增加了一些开销,但我想这超出了问题的范围。

答案 7 :(得分:3)

使用以下解决方法:

#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

答案 8 :(得分:2)

有更好的方法可以做到这一点,例如在公共结构中使用指向私有结构的void *指针。你这样做的方式就是愚弄编译器。

答案 9 :(得分:1)

这种方法有效,有用,标准C.

由BSD Unix定义的套接字API使用的略有不同的方法是用于struct sockaddr的样式。

答案 10 :(得分:1)

我发现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;
}

一个小示例,在我的intel 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;
}

答案 11 :(得分:0)

非常私密,因为调用代码可以强制转换回(SomeStructSource *)。此外,当您想要添加其他公共成员时会发生什么?你必须打破二进制兼容性。

编辑:我错过了它在.c文件中,但实际上并没有什么能阻止客户端将其复制出来,甚至可能直接#include .c文件。

答案 12 :(得分:0)

我的解决方案是只提供内部结构的原型,然后在.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
};

注意:在这种情况下,变量必须是指针,因为编译器无法知道内部结构的大小。

答案 13 :(得分:0)

相关,虽然不完全隐藏。

有条件地弃用会员。

请注意,这适用于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;
}