隐藏C结构中的成员


68

我一直在阅读有关C语言中的OOP的知识,但我从未喜欢过如何像C ++中那样拥有私有数据成员。但是后来我想到可以创建2个结构。一个在头文件中定义,另一个在源文件中定义。

从这里您可以将一个结构转换为另一个结构。这被认为是不良做法吗?还是经常做?


1
为什么要使它如此复杂-如果该工具不能满足您的需要,请使用其他工具。
罗曼·希波

我认为这至少在C99中会违反对象别名规则。我知道在C ++中会。
James McNellis

34
关闭这个太可怕了!人们为什么对这个有效的问题投反对票?因为忘记了如何用C做事?
Heath Hunnicutt

不要试图隐藏东西。只需为“私人”成员使用名称即可,这绝对清楚地表明,没有人可以触摸它们。如果有人使用它们,请告诉他们。或更改名称以对其进行教育。
gnasher729 2014年

Answers:


42

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

毕竟是C,如果人们想搞砸了,应该允许他们-无需隐藏任何东西,除了:

如果您需要保持ABI / API兼容,那么从我所见,有2种方法更常见。

  • 不要让您的客户访问该结构,不要给他们一个不透明的句柄(一个带有漂亮名称的void *),为所有内容提供init / destroy和accessor函数。如果要编写库,这可以确保即使不重新编译客户端也可以更改结构。

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

例如


1
我发现一个非常糟糕的设计允许客户端访问结构的任何成员。整个结构应该是私有的。对其成员的访问应通过getter和setter完成。
Felipe Lavratti

7
@fanl在C语言中这样做有很多含义,例如,以这种方式隐藏一个结构,将其分配到堆栈中或作为另一个结构的成员内联非常困难。解决此问题的简单方法是动态分配结构并仅暴露void *或handle,虽然这样做在某些情况下可能还可以,但在许多情况下,影响太大,这将使您无法利用C为您提供了什么。
2013年

1
恕我直言,这里给出的第二个例子应该是回答,只记得提供析构函数。
2014年

在对性能有严格要求的情况下,请避免void *隐含的跳跃,而直接在行内分配私有数据-私密性高不可攀(在这种情况下,您可以做的是在下划线前加前缀)。
工程师

隐藏结构的成员可以作为大多数封装形式的应用程序很好地工作,并且可以打破编译依赖关系,但是在许多受约束的(嵌入式)系统中,使用malloc是不切实际的,因此结构需要静态分配或在堆栈上分配。完全隐藏该结构会使此操作变得困难(不可能?),因此无论如何您最终都会暴露它。然后您必须对其进行记录,然后它可能很明确。
davidA

54

sizeof(SomeStruct) != sizeof(SomeStructSource)。这导致某人找到您并有朝一日谋杀您。


21
任何陪审团都会放任他们。
gnud

25
“总是进行编码,就像最终维护您的代码的人是知道您的住所的暴力精神病患者一样。” (归因于Rick Osborne)
Dietrich Epp

25

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

在标题中:

在.c中:

问题的关键是,这里现在的消费者有没有SomeStruct的内部的知识,你可以肆无忌惮地改变它,添加和随意删除成员,即使没有消费者无需重新编译。他们也不能直接“偶然地”改变成员,或者在堆栈上分配SomeStruct。当然这也可以看作是不利的。


18
有些人认为使用typedef隐藏指针不是一个好主意,特别是因为它SomeStruct *SomeThing看起来像普通堆栈变量的显然更需要释放。确实,您仍然可以声明struct SomeStruct;,并且只要不定义它,人们将被迫使用SomeStruct *指针而无法取消引用其成员,从而在不隐藏指针的情况下具有相同的效果。
克里斯·卢茨

19

我不建议使用公共结构模式。对于C语言中的OOP而言,正确的设计模式是提供访问每个数据的功能,而决不允许公开访问数据。类数据应在源宣布,为了是私有的,并且在前进的方式来引用,其中CreateDestroy不会分配和释放的数据。这样,公共/私人困境就不再存在。

另一方面,如果您不想使用Malloc / Free(在某些情况下可能不必要的开销),建议您将结构隐藏在私有文件中。私有成员将可以访问,但这取决于用户。


2
这种方法的一个困难是,即使在应该可以简单地将结构创建为堆栈变量然后在方法退出时消失的情况下,也需要使用malloc / free。如果在其他代码的创建/销毁之间需要创建持久对象,则将malloc / free用于应具有堆栈语义的事物可能导致内存碎片。如果提供一种使用传入的存储块来容纳对象的方法,并为此目的键入一个适当大小的int [],则可以缓解这种问题。
2013年

@supercat是的,如果您不想在嵌入式系统中使用malloc / free,则将结构私有给程序员,而不是代码私有。我已经编辑了答案以处理它。
Felipe Lavratti

1
这是一个合理的方法。可以更严格地强制正确使用的方法是定义a typedef int module_store_t[20];,然后使用a module_t *Module_CreateIn(module_store_t *p)。代码可以创建类型的自动变量,module_store_t然后使用该变量Module_CreateIn从该指针派生一个新初始化的模块,该模块的寿命将与自动变量的寿命匹配。
2013年

第二种方法无助于结构封装。不幸的是,它仍然允许直接引用私有结构成员!请在您的代码中尝试。
阿迪

2
尽管我喜欢C的简单性,但在应用设计模式时却是如此烦人。C和设计模式根本不兼容。令人沮丧的是,在C语言存在40年之后,再没有一种技术可以让您利用C语言中的最佳实践编码规则。如果我们忽略堆栈分配问题,那么ADT技术确实可以使用,但前提是将有一个适当的malloc实现,它不会引起任何碎片问题。我真的很惊讶没有标准C库实现<待续>
2013年

9

绝对不要那样做。如果您的API支持将SomeStruct作为参数的任何东西(我希望它能做到),那么他们可以在堆栈上分配一个参数并将其传递给您。尝试访问私有成员会遇到重大错误,因为该成员是编译器为客户端类分配的空间不包含空间。

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


7

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

但是,您可以安全地执行此操作:

somestruct.h

somestruct.c


4

我会编写一个隐藏的结构,并在公共结构中使用指针对其进行引用。例如,您的.h可能具有:

而您的.c:

它显然不能防止指针算术,并且会增加一些分配/取消分配的开销,但是我认为这超出了问题的范围。


3

使用以下解决方法:

结果是:


有趣的是...这种方法隐藏了“私有”结构的声明,但是如果您获得T结构的实例,则允许您访问私有部分。
mkonvisar

这将在我的udemy教程中进行。很棒
Abhishek Sagar

2

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


1

这种方法是有效,有用的标准C。

由BSD Unix定义的sockets API使用的方法稍有不同,它是用于的样式struct sockaddr


1

我的解决方案是仅提供内部结构的原型,然后在.c文件中声明定义。显示C接口并在其后使用C ++非常有用。

。H :

。C :

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


1

我发现bit-field如果您真的想隐藏某些东西,这可能是一个很好的解决方案。

struct person的第一个成员是未命名的位域。64-bit pointer在这种情况下用于。它是完全隐藏的,不能通过结构变量name进行访问

由于此结构中的前64位未使用,因此我们可以将其用作私有指针。我们可以通过其内存地址而不是变量名来访问该成员。

一个小示例,在我的intel mac上进行了测试:


0

鉴于调用代码可以转换回,因此它不是非常私有(SomeStructSource *)。另外,当您想添加另一个公共成员时会发生什么?您必须破坏二进制兼容性。

编辑:我想念它是在一个.c文件中,但实际上并没有阻止客户端将其复制出来,甚至#include直接阻止该.c文件的操作。


这就是为什么在源文件中定义SomeStructSource的原因。
马龙2010年

1
仅在发布SomeStructSource时如此。C ++对象指针与此类似,可以使用offsetof()和指针数学运算来获取私有成员。
Heath Hunnicutt

0

相关,尽管不完全隐藏。

是有条件地弃用成员。

请注意,这适用于GCC / Clang,但MSVC和其他编译器也可以弃用,因此可能会提供一个更可移植的版本。

如果您使用相当严格的警告或警告作为错误进行构建,则至少可以避免意外使用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.