简体   繁体   English

在 C 结构中隐藏成员

[英]Hiding members in a C struct

I've been reading about OOP in C but I never liked how you can't have private data members like you can in C++.我一直在阅读 C 中的 OOP,但我从不喜欢你不能像在 C++ 中那样拥有私有数据成员。 But then it came to my mind that you could create 2 structures.但后来我想到你可以创建 2 个结构。 One is defined in the header file and the other is defined in the source file.一个在头文件中定义,另一个在源文件中定义。

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

From here you can just cast one structure to the other.从这里您可以将一个结构转换为另一个结构。 Is this considered bad practice?这被认为是不好的做法吗? Or is it done often?还是经常做?

sizeof(SomeStruct) != sizeof(SomeStructSource) . sizeof(SomeStruct) != sizeof(SomeStructSource) This will cause someone to find you and murder you someday.导致有人找到你并在某天谋杀你。

Personally, I'd more like this:就个人而言,我更喜欢这样:

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

It's C after all, if people want to screw up, they should be allowed to - no need to hide stuff, except:毕竟是 C,如果人们想搞砸,他们应该被允许 - 不需要隐藏东西,除了:

If what you need is to keep the ABI/API compatible, there's 2 approaches that's more common from what I've seen.如果您需要保持 ABI/API 兼容,那么在我所见的情况下,有两种更常见的方法。

  • Don't give your clients access to the struct, give them an opaque handle (a void* with a pretty name), provide init/destroy and accessor functions for everything.不要让您的客户访问该结构,给他们一个不透明的句柄(一个带有漂亮名称的 void*),为所有内容提供初始化/销毁和访问器函数。 This makes sure you can change the structure without even recompiling the clients if you're writing a library.如果您正在编写库,这确保您可以更改结构,甚至无需重新编译客户端。

  • provide an opaque handle as part of your struct, which you can allocate however you like.提供一个不透明的句柄作为结构的一部分,您可以随意分配它。 This approach is even used in C++ to provide ABI compatibility.这种方法甚至在 C++ 中用于提供 ABI 兼容性。

eg例如

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

You almost have it, but haven't gone far enough.你几乎拥有它,但还远远不够。

In the header:在标题中:

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);

In the .c:在 .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 ...

The point is, here now consumers have no knowledge of the internals of SomeStruct, and you can change it with impunity, adding and removing members at will, even without consumers needing to recompile.关键是,现在消费者知道 SomeStruct 的内部结构,你可以随意更改它,随意添加和删除成员,即使消费者不需要重新编译。 They also can't "accidentally" munge members directly, or allocate SomeStruct on the stack.他们也不能“意外地”直接处理成员,或者在堆栈上分配 SomeStruct。 This of course can also be viewed as a disadvantage.这当然也可以被视为一个缺点。

I do not recommend using the public struct pattern.我不建议使用公共结构模式。 The correct design pattern, for OOP in C, is to provide functions to access every data, never allowing public access to data.对于 C 中的 OOP,正确的设计模式是提供访问每个数据的函数,绝不允许公开访问数据。 The class data should be declared at the source, in order to be private, and be referenced in a forward manner, where Create and Destroy does allocation and free of the data.类数据应在源处声明,以便私有,并以正向方式引用,其中CreateDestroy进行分配和释放数据。 In a such way the public/private dilemma won't exist any more.这样一来,公共/私人困境将不再存在。

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

In the other side, if you do not want to use Malloc/Free (which can be unnecessary overhead for some situations) I suggest you hide the struct in a private file.另一方面,如果您不想使用 Malloc/Free(在某些情况下这可能是不必要的开销),我建议您将结构隐藏在私有文件中。 Private members will be accessible, but that on user's stake.私人成员将是可访问的,但这取决于用户的股份。

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

Never do that.永远不要那样做。 If your API supports anything that takes SomeStruct as a parameter (which I'm expecting it does) then they could allocate one on a stack and pass it in. You'd get major errors trying to access the private member since the one the compiler allocates for the client class doesn't contain space for it.如果您的 API 支持将 SomeStruct 作为参数的任何内容(我希望它会这样做),那么他们可以在堆栈上分配一个并将其传递。您会在尝试访问私有成员时遇到重大错误,因为编译器为客户端类分配不包含空间。

The classic way to hide members in a struct is to make it a void*.在结构中隐藏成员的经典方法是使其成为 void*。 It's basically a handle/cookie that only your implementation files know about.它基本上是一个只有你的实现文件知道的句柄/cookie。 Pretty much every C library does this for private data.几乎每个 C 库都为私有数据执行此操作。

Something similar to the method you've proposed is indeed used sometimes (eg. see the different varities of struct sockaddr* in the BSD sockets API), but it's almost impossible to use without violating C99's strict aliasing rules.有时确实会使用类似于您提出的方法的方法(例如,请参阅 BSD 套接字 API 中不同的struct sockaddr* ),但如果不违反 C99 的严格别名规则,几乎不可能使用它。

You can, however, do it safely :但是,您可以安全地进行操作:

somestruct.h : somestruct.h

struct SomeStructPrivate; /* Opaque type */

typedef struct {
  int _public_member;
  struct SomeStructPrivate *private;
} SomeStruct;

somestruct.c : 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;
}

I'd write a hidden structure, and reference it using a pointer in the public structure.我会编写一个隐藏结构,并使用公共结构中的指针来引用它。 For example, your .h could have:例如,您的 .h 可能具有:

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

And your .c:还有你的 .c:

typedef struct {
    int c, d;
} private_t;

It obviously doesn't protect against pointer arithmetic, and adds a bit of overhead for allocation/deallocation, but I guess it's beyond the scope of the question.它显然不能防止指针运算,并为分配/释放增加了一些开销,但我想这超出了问题的范围。

Use the following workaround:使用以下解决方法:

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

Result is:结果是:

t.a = 1
t.x = 2
tref->a = 3
tref->x = 4

There are better ways to do this, like using a void * pointer to a private structure in the public struct.有更好的方法可以做到这一点,比如在公共结构中使用指向私有结构的void *指针。 The way you are doing it you're fooling the compiler.你这样做的方式是在愚弄编译器。

My solution would be to provide only the prototype of the internal struct and then declare the definition in the .c file.我的解决方案是只提供内部结构的原型,然后在 .c 文件中声明定义。 Very useful to show C interface and use C++ behind.对于展示 C 接口和在后面使用 C++ 非常有用。

.h : 。H :

struct internal;

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

.c : 。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.注意:在这种情况下,变量必须是指针,因为编译器无法知道内部结构的大小。

This approach is valid, useful, standard C.这种方法是有效的、有用的、标准的 C.

A slightly different approach, used by sockets API, which was defined by BSD Unix, is the style used for struct sockaddr .由 BSD Unix 定义的套接字 API 使用的稍微不同的方法是用于struct sockaddr的样式。

I found that bit-field might be a good solution if you really want to hide something.我发现如果你真的想隐藏一些东西, bit-field可能是一个很好的解决方案。

struct person {
    unsigned long :64;
    char          *name;
    int           age;
};

struct wallet {
    char *currency;
    double balance;
};

The first member of struct person is an unnamed bit-field. struct person 的第一个成员是一个未命名的位域。 used for a 64-bit pointer in this case.在这种情况下用于64-bit pointer It's completely hidden and cannot be accessed by struct variable name .它是完全隐藏的,不能通过 struct variable name 访问

Because of the first 64-bit in this struct is unused, so we can use it as a private pointer.因为这个结构体中的前 64 位是未使用的,所以我们可以将它用作私有指针。 We can access this member by its memory address instead of variable name.我们可以通过它的内存地址而不是变量名来访问这个成员。

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

A small working example, tested on my intel mac:一个小的工作示例,在我的 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;
}

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.请注意,这适用于 GCC/Clang,但 MSVC 和其他编译器也可能会弃用,因此可以提供更便携的版本。

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

Not very private, given that the calling code can cast back to a (SomeStructSource *) .不是很私密,因为调用代码可以转换回(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 #include ing the .c file directly.编辑:我错过了它在 .c 文件中,但确实没有什么可以阻止客户端将其复制出来,甚至可能直接#include ing .c 文件。

An anonymous struct can be of use here.这里可以使用匿名结构。

#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

In any file that should have access to the private members, define MYSTRUCT_PRIVATE as an empty token before including this header.在应该有权访问私有成员的任何文件中,在包含此标头之前将MYSTRUCT_PRIVATE定义为空标记。 In those files, the private members are in an anonymous struct and can be accessed using mj , but in all other places they can only be accessed using m.MYSTRUCT_PRIVATE.j .在这些文件中,私有成员位于匿名结构中,可以使用mj访问,但在所有其他地方,它们只能使用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;
}

I do not recommend putting public members after private members.我不建议将公共成员放在私人成员之后。 Initializing a struct without member designators, such as with { 10, 20, 30 } can still initialize private members.初始化没有成员指示符的结构,例如使用{ 10, 20, 30 }仍然可以初始化私有成员。 If the number of private members changes, this will also silently break all initializers without member designators.如果私有成员的数量发生变化,这也会默默地破坏所有没有成员指示符的初始化程序。 It's probably best to always use member designators to avoid this.最好总是使用成员指示符来避免这种情况。

You must design your structs, and especially the private members, to be zero initialized since there are no automatic constructors as in C++.您必须将结构,尤其是私有成员设计为零初始化,因为在 C++ 中没有自动构造函数。 As long as the members are initialized to 0 then they won't be left in an invalid state even without an initialization function.只要将成员初始化为 0,即使没有初始化函数,它们也不会处于无效状态。 Barring a member designator initialization, initializing to simply { 0 } should be designed to be safe.除非成员指示符初始化,否则初始化为简单的{ 0 }应该被设计为安全的。

The only downside I've found is that this does mess with things like debuggers and code completion, they typically don't like it when one type has one set of members in one file, and a different set in another file.我发现的唯一缺点是这确实会干扰调试器和代码完成之类的东西,当一种类型在一个文件中有一组成员,而在另一个文件中有一组不同的成员时,他们通常不喜欢它。

Here's a very organized way to do it using macros.这是使用宏的一种非常有条理的方法。 This is how I've seen it used in some of the big projects.这就是我看到它在一些大型项目中的使用方式。 I will assume the following:我将假设以下内容:

  • Header file with the struct带有结构的头文件
  • Source file with access to private fields可以访问私有字段的源文件
  • Source file with no access to private fields (the fields exist but are renamed).无法访问私有字段的源文件(这些字段存在但已重命名)。

Header file:头文件:

// You can put this part in a header file
// and share it between multiple header files in your project
#ifndef ALLOW_PRIVATE_ACCESS
#define PRIVATE(T) private_##T
#else
#define PRIVATE(T) T
#endif
#define PUBLIC(T) T

typedef struct {
  int PRIVATE(m1); // private member
  int PUBLIC(m2); // public member
} mystruct;

mystruct *mystruct_create(void);
int mystruct_get_m1(mystruct *t);

Source file with access to private fields:可以访问私有字段的源文件:

#include <stdlib.h>
#define ALLOW_PRIVATE_ACCESS
#include "mystruct.h"

mystruct *mystruct_create(void) {
  mystruct *p = (mystruct *)malloc(sizeof(mystruct));
  p->m1 = 42; // works (private)
  p->m2 = 34; // works (public)
  return (mystruct *)p;
}

int mystruct_get_m1(mystruct *t) {
    return t->m1; // works (private)
}

Source file with no access to private fields:无法访问私有字段的源文件:

#include <stdio.h>
#include <stdlib.h>
#include "mystruct.h"

int main() {
    mystruct *t = mystruct_create();
    printf("t->m1 = %d\n", t->m1); // error (private)
    printf("t->m1 = %d\n", mystruct_get_m1(t)); // works (using function)
    printf("t->m2 = %d\n", t->m2); // works (public)
    free(t);
    return 0;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM