2

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

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

到目前为止,我总是在相应模块的头文件中声明我的 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;

主程序

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

主程序

#include "struct_test.h"

myStruct_t myTest;

myTest->varA = 1;

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

所以很明显我还没有理解这种技术的基本概念。我的主要困惑是结构对象的数据将在哪里?

到目前为止,我的理解是指针通常指向数据类型的“物理”表示,并在相应地址上读取/写入内容。

但是使用上面的方法,我声明了一个指针 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 名开发人员使用代码的中小型嵌入式项目是否真的合理?目前,制作所有这些接口指针方法似乎比仅仅在我的头文件中声明结构更努力。

先感谢您

4

3 回答 3

6

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

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

我怎样才能在我的简单示例中实现这一点?

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

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

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

但是,是的,对于内部使用,这通常是一种负担(并且会阻碍优化,因为除非您使用 LTO 等,否则一切都成为编译器的黑匣子)。public像/在其他语言(如 C++)中的句法方法private对此要好得多。

但是,如果您必须遵守 MISRA 到这样的程度(即,如果您的项目必须遵守该规则,即使它只是建议性的),您也无能为力。

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

那取决于你。有非常大的项目不遵循该建议并取得了成功。通常,对私有字段的注释或命名约定就足够了。

于 2020-01-31T15:09:24.500 回答
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=%d\n", getVarA(s));
    cleanup(s);l
}
于 2020-01-31T15:13:48.627 回答
1

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

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

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

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

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

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

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

我同意你的看法。

于 2020-01-31T15:15:01.850 回答