如何在c中隐藏结构实现并避免变量同时具有不完整的类型?

How to hide the struct implementation and avoid variable has incomplete type at the same time in c?

在InputBuffer.c

中定义InputBuffer
typedef struct InputBuffer_t {
  char* buffer;
  size_t buffer_length;
  ssize_t input_length;
} InputBuffer;

在 InputBuffer.h

中隐藏 InputBuffer 的实现
#ifndef INPUTBUFFER_H
#define INPUTBUFFER_H

typedef struct InputBuffer_t InputBuffer;

#endif

然后在testBuffer.c

中使用InputBuffer
#include "InputBuffer.h"

void testBuffer() {
   InputBuffer b = sizeof(InputBuffer);
}

但是,编译 testBuffer 会导致“variable has incomplete type 'struct InputBuffer'”,因为完整的 InputBuffer 实现不在 InputBuffer.h.

所以想知道有没有什么方法可以隐藏一个struct类型的实现,同时避免不完整的类型错误

在 C 语言中不透明结构和数据隐藏的架构注意事项和方法

解决您问题中的代码:

sizeof(InputBuffer)

您不能获取隐藏结构(通常称为“不透明结构”)的大小! testBuffer.c 不知道结构的大小,因为它是隐藏的!它无权访问实现。

我也不知道你想在这里做什么:

#include "InputBuffer.h"

void testBuffer() {
   InputBuffer b = sizeof(InputBuffer);  // <=== What is this?
}

您不能随意将数字分配给结构。


补充说明:

你的 typedef 很笨拙。

InputBuffer.c中,执行:

typedef struct InputBuffer_s {
  char* buffer;
  size_t buffer_length;
  ssize_t input_length;
} InputBuffer_t;

然后,在 InputBuffer.htestBuffer.c 中,执​​行以下选项之一:

  1. 选项 1:对不透明(隐藏)结构进行前向声明 typedef

    InputBuffer.h中,执行:

    #ifndef INPUTBUFFER_H
    #define INPUTBUFFER_H
    
    // Forward declaration of the struct defined in InputBuffer.c, since this
    // header does not have access to that definition. You can therefore call this
    // an "opaque struct". It is a type of data hiding since this header now knows 
    // that `InputBuffer_t` **exists**, but doesn't know what is in it. 
    typedef struct InputBuffer_s InputBuffer_t;
    
    #endif
    

    并且在testBuffer.c中:

    #include "InputBuffer.h"
    
    void testBuffer(InputBuffer_t *inputBuffer) {
    
    }
    
  2. 选项 2:对不透明(隐藏)结构的 指针 进行前向声明 typdef。这个 typedef 现在是一个 pointer 到 struct

    形式的“句柄”

    有些人不推荐这个选项,尽管我以前在一些 high-quality、safety-critical、real-time C code-bases 中专业地使用过它。

    @Lundin,例如,强烈建议反对使用这种技术in their comment below this answer,其中指出:

    I strongly disagree about the advi[c]e to hide pointers behind typedef, even when they are opaque. Hiding pointers behind a typedef is very bad in general, but we also know from experience that hiding opaque pointers behind a typedef leads to bad API. Specifically the Windows API with its HANDLE, HWND and other weird types like that which lead the programmer to pass those in turn by reference HANDLE*, creating multiple levels of indirection needlessly and therefore slower and less readable code overall.

    这是一个很好的观点。因此,我建议您 考虑 typedef 将指向结构的指针转换为“句柄”,如下所示 if:

    1. 在您的整个代码库中,只有 _h 命名的“句柄”类型是指针,因此所有 _h 命名的句柄都清楚地称为指针。
    2. 您确保开发人员知道您的代码库中所有以 _h 命名的“句柄”都是指针,因此他们不会不必要地引用(在 C++ 中)或指针(在 C 或 C++ 中)到他们。

    考虑到以上两个考虑因素,我已经使用了这种“处理”技术并且对它很满意,尽管我可以理解反对它的论点。你可以看到我在我的回答中使用它:Opaque C structs: various ways to declare them

    InputBuffer.h中,执行:

    #ifndef INPUTBUFFER_H
    #define INPUTBUFFER_H
    
    // InputBuffer_h is a "handle", or pointer to an opaque struct; 
    // AKA: InputBuffer_h is an "opaque pointer", meaning it is a pointer
    // to a struct whose implementation is hidden. This is true data-hiding
    // in C.
    typedef struct InputBuffer_s *InputBuffer_h;
    
    #endif
    

    并且在testBuffer.c中:

    #include "InputBuffer.h"
    
    void testBuffer(InputBuffer_h inputBuffer) {
    
    }
    

但是,无论您在上面选择哪个选项,您实际上都不能对 inputBuffer 参数做任何事情,因为您不能取消引用它,也不能访问它在 "testBuffer.c”,因为它的实现是隐藏的,并在您未包含的不同源文件 (InputBuffer.c) 中定义!

好的方法 1 [这确实是比上面更好的方法]:将结构定义放在需要其完整定义的同一个源文件中

Therefore, I wonder if there is a way to hide the implementation of a struct type and avoid the incomplete type error at the same time.

因此,您应该在 InputBuffer.h 中声明需要访问实现的函数原型,然后在 [= 中编写函数定义256=],因此他们可以访问不透明结构的实现细节,因为结构是在 InputBuffer.c.

中定义的

这看起来像这样,例如:

InputBuffer.h中,执行:

#ifndef INPUTBUFFER_H
#define INPUTBUFFER_H

// Forward declaration of the struct defined in InputBuffer.c, since this
// header does not have access to that definition. You can therefore call this
// an "opaque struct". It is a type of data hiding since this header now knows 
// that `InputBuffer_t` **exists**, but doesn't know what is in it. 
typedef struct InputBuffer_s InputBuffer_t;

// put any public function prototypes (declarations) you may need here

#endif

并且在InputBuffer.c中:

#include "InputBuffer.h"

// Full struct definition; no need to typedef this here since it's already 
// typedef'ed in InputBuffer.h, which is included above.
struct InputBuffer_s {
  char* buffer;
  size_t buffer_length;
  ssize_t input_length;
};

void testBuffer(InputBuffer_t *inputBuffer) {
    // Now you have full access to the size of the `InputBuffer_t`, and its 
    // members, since the full definition of this struct is above.
}

vvvvvvvvv
这是一个 more-thorough 我写的关于我喜欢如何使用和编写“Object-based” C 架构使用不透明 pointers/structs 的回答:Opaque C structs: various ways to declare them
^^^^^^^^^

好的方法 2 [上述方法的替代方法]:将您的结构定义放在 _private.h header 文件中,您将仅将其包含在需要结构完整定义的其他源文件中

请注意 替代方法 使用 single-source-file 不透明 pointer/struct 架构(不透明 pointers/opaque 结构架构经常 require 使用 malloc() 的动态内存分配,正如我在上面的其他答案 linked-to 中所展示的那样,只是简单地包含在 header 中定义的“隐藏”实现s后缀为_private.h,如myheader_private.h这意味着这些“私有”header 只应包含在需要查看“隐藏”结构的完整定义的源文件中,但绝不能由 API 直接。 这是一种 slightly-less-strong 数据隐藏形式,但优点是可以让您完全访问结构定义 到多个源文件 .

示例:

InputBuffer_private.h(“私有”header 文件)中,执行:

// THIS "PRIVATE" HEADER SHOULD ONLY BE INCLUDED BY SOURCE FILES WHICH NEED FULL
// ACCESS TO THE STRUCT DEFINITION BELOW. It should NOT generally be include by
// regular users of your API, since your architectural goal is probably to have
// some level of data hiding to hide the contents of this struct from your
// regular API users. 

#ifndef INPUTBUFFER_PRIVATE_H
#define INPUTBUFFER_PRIVATE_H

// Full struct definition. No need to typedef it here since it will be 
// typedefed in the "public" header file below.
struct InputBuffer_s {
  char* buffer;
  size_t buffer_length;
  ssize_t input_length;
};

#endif

InputBuffer.h(“public”header 文件)中,执行:

#ifndef INPUTBUFFER_H
#define INPUTBUFFER_H

// Do your choice of Option 1 or 2 above, to expose the **existence** of the 
// opaque struct to the user of the API:
typedef struct InputBuffer_s InputBuffer_t;  // Option 1
// OR:
typedef struct InputBuffer_s *InputBuffer_h; // Option 2

#endif

并且在InputBuffer.c中:

#include "InputBuffer.h"

#include "InputBuffer_private.h" // <==== NOTICE THIS ADDITION!


void testBuffer(InputBuffer_t *inputBuffer) {
    // Now you have full access to the size of the `InputBuffer_t`, and its 
    // members, since the full definition of this struct is **INCLUDED** above.
}

您还可以根据需要将完整的结构定义提供给其他源文件:

例如:在SomeOtherSource.c中:

#include "SomeOtherSource.h"

#include "InputBuffer_private.h" // to expose the details of the opaque struct

// Now you can have full access to the size of the `InputBuffer_t`, and access
// to all of its members, as needed, in any function below.

// Your functions here

最后的说明:如果您在另一个 " 中包含任何 _private.h header 文件public”header文件,你刚刚丢失数据隐藏

如果您不希望真正的数据隐藏,包括任何 _private.h header 文件在另一个“public”(打算由 [=277= 的用户包含) ]) header 文件将向 API 的用户公开完整的结构定义,并且 所有真正的数据隐藏都将丢失!

这是一种有效的体系结构方法,您可以根据需要选择采用。优点是您现在可以为所有结构使用静态内存分配,而不是像不透明指针(又名:不透明结构)那样需要动态内存分配。

现在,您有 2 个选择:

  1. 保留 header 名称的 _private.h 部分 。这是一种“软数据隐藏”方法,它告诉 public API 的用户 header 打算 是私有的,并且他们 不应该直接访问其中的内容,即使它们在技术上可以。这是一种完全有效的方法,而且现在这个方法和下面的选项都允许您对所有这些结构使用完全静态内存分配,这很棒。
    1. 这基本上就是 Python 的工作原理:您只需将 _ 添加到您想要“私有”的任何函数名称前,即使 Python 不支持真正的数据隐藏,任何导入模块的人都可以访问所有“私有”成员,如果他们真的愿意的话。
  2. 如果您不想再隐藏任何数据,请删除 header 名称 _private.h 部分。结构定义现在完全公开 并且 打算完全公开。现在任何人都可以在任何地方包含这个 header,这很好。结构定义对包含 header 的任何人都是完全可用的,并且您希望 public API 的用户也能够做到这一点。这也很好,具体取决于您要采用的架构方法。选择权在你。

不要 在你的 header 文件的末尾留下 _private.h 后缀,其中包含结构定义,并允许你的 public API 直接包含您的 _private.h header。这违反了您 API 的 设计意图 。相反,要么删除 _private.h 后缀并允许 public API 的用户直接包含它,要么保留该后缀并根据其中一种方法仅包含 _private.h 文件之前在上面描述过(在私有源文件中用于真正的数据隐藏,或者在 public header 文件中用于 pseudo-data-hiding,就像存在于 Python 中)。

另见

  1. 同样,另请参阅此处的其他答案,了解一个“句柄”(typedefed 指向结构的指针)样式技术的完整示例。该技术还显示了根据需要使用动态内存分配(通过 malloc())创建不透明结构的完整方法:Opaque C structs: various ways to declare them

通过前向声明进行私有封装的缺点是调用者得到一个不完整的类型,就是这样。调用者必须使用指针类型。

如果出于某种原因需要在封装之外公开结构的大小,则必须为此目的设计一个 getter 函数。示例:

InputBuffer* b = InputBufferCreate();
size_t size = InputBufferGetSize(b);

更多信息在这里:How to do private encapsulation in C?