隐藏 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;
}
从这里您可以将一个结构转换为另一个结构。 这被认为是不好的做法吗?或者经常这样做吗?
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++. But then it came to my mind that you could create 2 structures. 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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(16)
sizeof(SomeStruct) != sizeof(SomeStructSource)
。这会导致有人找到你并在某一天谋杀你。sizeof(SomeStruct) != sizeof(SomeStructSource)
. This will cause someone to find you and murder you someday.就我个人而言,我更喜欢这样:
毕竟是 C,如果人们想搞砸,他们应该被允许 - 不需要隐藏东西,除了:
如果你需要的是保持 ABI/API 兼容,有 2从我所见的情况来看,这是更常见的方法。
不要让你的客户访问结构体,给他们一个不透明的句柄(一个带有漂亮名字的 void* ),为所有东西提供 init/destroy 和访问器函数。这确保您可以更改
如果您正在编写库,则无需重新编译客户端即可更改结构。
提供一个不透明的句柄作为结构的一部分,您可以根据需要进行分配。这种方法甚至在 C++ 中用于提供 ABI 兼容性。
例如
Personally, I'd more like this:
It's C after all, if people want to screw up, they should be allowed to - no need to hide stuff, except:
If what you need is to keep the ABI/API compatible, there's 2 approaches that's more common from what I've seen.
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. 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.
e.g
您即将拥有它,但还远远不够。
在标头中:
在 .c 中:
重点是,现在消费者对 SomeStruct 的内部结构不了解,即使没有消费者,您也可以随意更改它,随意添加和删除成员需要重新编译。他们也不能“意外地”直接 munge 成员,或在堆栈上分配 SomeStruct。这当然也可以被视为一个缺点。
You almost have it, but haven't gone far enough.
In the header:
In the .c:
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. They also can't "accidentally" munge members directly, or allocate SomeStruct on the stack. This of course can also be viewed as a disadvantage.
我不建议使用公共结构模式。对于 C 中的 OOP 来说,正确的设计模式是提供访问每个数据的函数,而绝不允许公共访问数据。类数据应该在源头声明,以使其私有,并以前向方式引用,其中
Create
和Destroy
进行数据的分配和释放。这样,公私困境将不再存在。另一方面,如果您不想使用 Malloc/Free(在某些情况下这可能是不必要的开销),我建议您将结构隐藏在私有文件中。私人成员将可以访问,但这取决于用户的权益。
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. The class data should be declared at the source, in order to be private, and be referenced in a forward manner, where
Create
andDestroy
does allocation and free of the data. In a such way the public/private dilemma won't exist any more.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. Private members will be accessible, but that on user's stake.
永远不要那样做。如果你的 API 支持任何以 SomeStruct 作为参数的东西(我希望它会这样做),那么他们可以在堆栈上分配一个并将其传递进去。尝试访问私有成员时,你会遇到重大错误,因为编译器为客户端类分配的空间不包含它的空间。
隐藏结构中成员的经典方法是将其设为 void*。它基本上是一个只有您的实现文件知道的句柄/cookie。几乎每个 C 库都对私有数据执行此操作。
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.
The classic way to hide members in a struct is to make it a void*. It's basically a handle/cookie that only your implementation files know about. Pretty much every C library does this for private data.
有时确实会使用与您提出的方法类似的方法(例如,请参阅 BSD 套接字 API 中 struct sockaddr* 的不同变体),但在不违反 C99 严格别名规则的情况下几乎不可能使用。
不过,您可以安全地执行此操作:
somestruct.h
:somestruct.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.You can, however, do it safely:
somestruct.h
:somestruct.c
:我会编写一个隐藏结构,并使用公共结构中的指针引用它。例如,您的 .h 可能有:
而您的 .c:
它显然不能防止指针算术,并且会增加一点分配/解除分配的开销,但我想这超出了问题的范围。
I'd write a hidden structure, and reference it using a pointer in the public structure. For example, your .h could have:
And your .c:
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.
有更好的方法可以做到这一点,例如使用指向公共结构中的私有结构的
void *
指针。你这样做的方式是在欺骗编译器。There are better ways to do this, like using a
void *
pointer to a private structure in the public struct. The way you are doing it you're fooling the compiler.这种方法是有效的、有用的、标准的 C。
由 BSD Unix 定义的套接字 API 使用的稍微不同的方法是 struct sockaddr 所使用的样式。
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
.使用以下解决方法:
结果是:
Use the following workaround:
Result is:
我发现如果您确实想隐藏某些内容,
位字段
可能是一个很好的解决方案。person 结构体的第一个成员是一个未命名的位域。在本例中用于
64 位指针
。它是完全隐藏的,并且无法通过结构变量名称访问。由于该结构体中的前 64 位未使用,因此我们可以将其用作私有指针。我们可以通过内存地址而不是变量名来访问该成员。
一个小的工作示例,在我的 intel mac 上测试:
I found that
bit-field
might be a good solution if you really want to hide something.The first member of struct person is an unnamed bit-field. used for a
64-bit pointer
in this case. It's completely hidden and cannot be accessed by struct variable name.Because of the first 64-bit in this struct is unused, so we can use it as a private pointer. We can access this member by its memory address instead of variable name.
A small working example, tested on my intel mac:
这是一种使用宏来完成此操作的非常有组织的方法。这就是我在一些大型项目中看到的它的使用方式。我将假设以下内容:
头文件:
可访问私有字段的源文件:
不可访问私有字段的源文件:
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:
Source file with access to private fields:
Source file with no access to private fields:
相关,但并不完全隐藏。
就是有条件地弃用成员。
请注意,这适用于 GCC/Clang,但 MSVC 和其他编译器也可以弃用,
因此有可能推出更便携的版本。
如果您使用相当严格的警告或警告作为错误进行构建,这至少可以避免意外使用。
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.
我的解决方案是仅提供内部结构的原型,然后在 .c 文件中声明定义。对于显示 C 接口并在后面使用 C++ 非常有用。
.h :
.c :
注意:在这种情况下,变量必须是指针,因为编译器无法知道内部结构的大小。
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 :
.c :
Note: In that case, the variable have to be a pointer because the compiler is unable to know the size of the internal struct.
不是很私密,因为调用代码可以转换回
(SomeStructSource *)
。另外,当您想添加另一个公共成员时会发生什么?您必须破坏二进制兼容性。编辑:我错过了它在 .c 文件中,但确实没有什么可以阻止客户端将其复制出来,甚至可能直接
#include
ing .c 文件。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
#include
ing the .c file directly.此处可以使用匿名结构。
在任何应有权访问私有成员的文件中,在包含此标头之前将 MYSTRUCT_PRIVATE 定义为空标记。在这些文件中,私有成员位于匿名结构中,可以使用
mj
访问,但在所有其他位置,只能使用m.MYSTRUCT_PRIVATE.j
访问它们。我不建议将公共成员放在私人成员之后。初始化没有成员指示符的结构体,例如使用
{ 10, 20, 30 }
仍然可以初始化私有成员。如果私有成员的数量发生变化,这也会默默地破坏所有没有成员指示符的初始值设定项。最好始终使用成员指示符来避免这种情况。您必须将结构(尤其是私有成员)设计为零初始化,因为没有像 C++ 那样的自动构造函数。只要成员初始化为 0,即使没有初始化函数,它们也不会处于无效状态。除非进行成员指示符初始化,否则初始化为简单的
{ 0 }
应该被设计为安全的。我发现的唯一缺点是,这确实会扰乱调试器和代码完成等功能,当一种类型在一个文件中具有一组成员,而在另一个文件中具有不同的一组成员时,它们通常不喜欢它。
An anonymous struct can be of use here.
In any file that should have access to the private members, define
MYSTRUCT_PRIVATE
as an empty token before including this header. In those files, the private members are in an anonymous struct and can be accessed usingm.j
, but in all other places they can only be accessed usingm.MYSTRUCT_PRIVATE.j
.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. 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++. As long as the members are initialized to 0 then they won't be left in an invalid state even without an initialization function. Barring a member designator initialization, initializing to simply
{ 0 }
should be designed to be safe.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.