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

时间:2020-01-31 14:50:56

标签: c struct misra information-hiding opaque-pointers

我目前对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;

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,但从未设置它应该指向的地址。

我从这篇文章中得到了这个主意: What is an opaque pointer in C?

在文章中提到,访问是使用set / get接口方法处理的,因此我尝试添加一个类似的符号:

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

但这也不起作用,因为现在他告诉我_structName是未知的... 因此,我只能在其他接口方法的帮助下访问该结构吗?如果可以,如何在我的简单示例中实现该目标?

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

varA-地址:10-值:1

ptrA-地址:22-值:10

但是在这个例子中我只有

myTest-地址:xy-值:??

我无法理解相应的myTest指针的“物理”表示形式在哪里?

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

有人可以向我解释这种方法对于由1-2个开发人员使用代码的中小型嵌入式项目是否真的合理吗? 目前看来,要使所有这些接口指针方法都比在我的头文件中声明结构要付出更多的努力。

提前谢谢

3 个答案:

答案 0 :(得分:4)

我的主要困惑是struct对象的数据在哪里?

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

如何通过简单的示例实现这一目标?

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

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

这是强制模块彼此独立的方法。有时,它用于向客户隐藏实现或确保ABI稳定性。

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

但是,如果您一定要严格遵循MISRA(即,即使您的项目必须遵循该规则,即使只是咨询性的建议),您也无能为力。

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

由您决定。有很多大项目没有遵循该建议而成功。通常,对于私有字段的注释或命名约定就足够了。

答案 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=%d\n", getVarA(s));
    cleanup(s);l
}

答案 2 :(得分:1)

在文章中提到,访问是使用set / get接口方法处理的,因此我尝试添加一个类似的符号:

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

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

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

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

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

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

我同意你的看法。

相关问题