C结构信息隐藏(不透明指针)



我目前对C结构的信息隐藏概念有点困惑。

这个问题的背景是一个几乎没有OOP知识的嵌入式c项目。

到目前为止,我总是在相应模块的头文件中声明我的typedef结构。因此,每个想要使用这个结构的模块都知道结构类型。

但在进行了MISRA-C检查后,我发现了中等严重性警告:MISRAC2012-Dir-4.8-结构的实现不必要地暴露于翻译单元。

经过一点研究,我发现了C结构的信息隐藏概念,将结构成员的可见访问限制在私有范围内。

我立即尝试了一个简单的例子,如下所示:

struct_test.h

//struct _structName;
typedef struct _structName structType_t;

struct_test.c

#include "struct_test.h"
typedef struct _structName
{
int varA;
int varB;
char varC;
}structType_t;

main.c

#include "struct_test.h"
structType_t myTest;
myTest.varA = 0;
myTest.varB = 1;
myTest.varC = 'c';

这会产生编译器错误,即对于main.c,myTest的大小是未知的。当然,main.c只知道structType_t类型的结构存在,其他什么都不知道。

所以我继续我的研究,偶然发现了不透明指针的概念。

所以我尝试了第二次尝试:

struct_test.h

typedef struct _structName *myStruct_t;

struct_test.c

#include "struct_test.h"
typedef struct _structName
{
int varA;
int varB;
char varC;
}structType_t;

main.c

#include "struct_test.h"
myStruct_t myTest;
myTest->varA = 1;

我得到了编译器错误:取消引用指向不完整类型struct _structName的指针

很明显,我还没有理解这个技术的基本概念。我的主要困惑点是struct对象的数据将在哪里?

到目前为止,我还知道指针通常指向数据类型的"物理"表示,并读取/写入相应地址上的内容。

但是使用上面的方法,我声明了一个指针myTest,但从未设置它应该指向的地址

我从这篇帖子中得到了这个想法:什么是C中的不透明指针?

在帖子中提到,访问是用set/get接口方法处理的,所以我尝试添加一个类似的方法:

void setVarA ( _structName *ptr, int valueA )
{
ptr->varA = valueA;
}

但这也不起作用,因为现在他告诉我_structName是未知的。。。那么,我只能在其他接口方法的帮助下访问结构吗?如果是,我如何在我的简单示例中实现这一点?

我更大的问题仍然是我的结构的对象在内存中的位置。我只知道指针的概念:

varA-地址:10-值:1

ptrA-地址:22-值:10

但在这个例子中,我只有

myTest-地址:xy-值:??

我很难理解相应myTest指针的"物理"表示位于何处?

此外,在我是模块的生产者和消费者的相对较小范围的嵌入式项目中,我看不到这样做的好处。

有人能解释一下,这种方法对于有1-2名开发人员使用代码的中小型嵌入式项目是否真的合理吗?目前,制作所有这些接口指针方法似乎要比在头文件中声明结构付出更多的努力。

提前感谢

我的主要困惑点是结构对象的数据将在哪里?

关键是,您不在其他翻译单元中使用struct表示(即其大小、字段、布局等),而是调用为您完成工作的函数。你需要使用一个不透明的指针,是的。

如何在我的简单示例中实现这一点?

您必须将所有使用结构字段(真正的结构)的函数放在一个文件(实现)中。然后,在头中,只公开接口(希望用户调用的函数,以及那些使用不透明指针的函数)。最后,用户将使用标头仅调用这些函数。他们将无法调用任何其他函数,也无法知道结构内部的内容,因此尝试这样做的代码将无法编译(这就是重点!)。

此外,在我是模块的生产者和消费者的相对较小范围的嵌入式项目中,我看不到这样做的好处。

这是一种强制模块相互独立的方法。有时它被用来向客户隐藏实现,或者能够保证ABI的稳定性。

但是,是的,对于内部使用来说,这通常是一种负担(并阻碍优化,因为除了使用LTO等之外,所有东西都会成为编译器的黑匣子)。在C++等其他语言中,像public/private这样的语法方法要好得多。

然而,如果你一定要遵循MISRA到这样的程度(即,如果你的项目必须遵循该规则,即使它只是咨询性的),你也无能为力

有人能解释一下,对于有1-2名开发人员使用代码的中小型嵌入式项目,这种方法是否真的合理吗?

这取决于你。有些非常的大项目没有遵循这一建议,并且取得了成功。通常情况下,对私有字段的注释或命名约定就足够了。

正如您所推断的,当使用这样的不透明类型时,主源文件无法访问结构的成员,而且实际上不知道结构有多大。因此,您不仅需要访问器函数来读/写结构的字段,还需要一个函数来为结构分配内存,因为只有库源知道结构的定义和大小。

因此,您的头文件将包含以下内容:

typedef struct _structName structType_t;
structType_t *init();
void setVarA(structType_t *ptr, int valueA );
int getVarA(structType_t *ptr);
void cleanup(structType_t *ptr);

此接口允许用户创建结构的实例,获取和设置值,并对其进行清理。库的来源看起来是这样的:

#include "struct_test.h"
struct _structName
{
int varA;
int varB;
char varC;
};
structType_t *init()
{
return malloc(sizeof(structType_t ));
}
void setVarA(structType_t *ptr, int valueA )
{
ptr->varA = valueA;
}
int getVarA(structType_t *ptr)
{
return ptr->varA;
}
void cleanup(structType_t *ptr)
{
free(ptr);
}

请注意,您只需要定义typedef一次。这既定义了类型别名,又向前声明了结构。然后在源文件中,实际的结构定义显示为没有typedef。

调用方使用init函数为结构分配空间并返回一个指向它的指针。然后可以将该指针传递给getter/setter函数。

所以现在你的主代码可以这样使用这个界面:

#include "struct_test.h"
int main()
{
structType_t *s = init();
setVarA(s, 5);
printf("s->a=%dn", getVarA(s));
cleanup(s);l
}

在帖子中提到,访问是用set/get接口方法处理的,所以我尝试添加一个类似的方法:

void setVarA ( _structName *ptr, int valueA )
{
ptr->varA = valueA;
}

但这也不起作用,因为现在他告诉我_structName是未知的。。。

类型不是_structName,而是struct _structName或(根据定义)structType_t

我更大的问题仍然是我的结构的对象在内存中的位置。

有了这种技术,就会有一个方法返回这样一个不透明对象的地址。它可以是静态分配的,也可以是动态分配的。当然,也应该有一个方法来释放一个对象。

此外,在我是模块的生产者和消费者的相对较小范围的嵌入式项目中,我看不到这样做的好处。

我同意你的看法。

最新更新