当两个文件相互包含时,为什么我不能有一个内部结构?

Why can't I have an inner structure when two files include each other?

对于上下文,我正在编写一个操作系统:

我有一个 struct vt_device_s 和一个 struct __vt_device_s,它们是特定于体系结构的,位于 vt_device_s 内部,如下所示:

struct
vt_device_s
{
    struct __vt_device_s __device;
    size_t cursor_x;
    size_t cursor_y;
};

现在是架构结构:

struct
__vt_device_s
{
    uint16_t *memory;
    size_t memory_len;
};

header <dev/vt.h> 知道 <sys/_vt.h> 中定义的 __vt_device_s 因为它包含在内,但我得到这个错误:

error: field '__device' has incomplete type
   48 |  struct __vt_device_s __device;
      |

我意识到这是因为两个文件相互依赖(整个冲突是由 _vt.c including _vt.h including vt.h including _vt.h 引起的)但我不知道怎么理解是编译问题。我在两个文件中都包含了守卫!

PS:我知道如果我使用指针,这将是一个 non-issue,但由于它是一个操作系统,这个 driver 需要在设置分页之前运行(即是,mallocfree 还不存在)。

这是有问题的三个文件:

dev/vt.h

#ifndef _DEV_VT_H_
#define _DEV_VT_H_ 1

#include <stddef.h>

#include <sys/_vt.h>

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

struct
vt_device_s
{
    struct __vt_device_s __device;
    size_t cursor_x;
    size_t cursor_y;
};

void vt_init(struct vt_device_s *);

#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif /* _DEV_VT_H_ */

sys/_vt.h

#ifndef _I386__VT_H_
#define _I386__VT_H_ 1

#include <stddef.h>
#include <stdint.h>

#include <dev/vt.h>

#define __VT_WIDTH  80
#define __VT_HEIGHT 25
#define __VT_MEMOFF 0xb8000

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

struct
__vt_device_s
{
    uint16_t *memory;
    size_t memory_len;
};

void __vt_init(struct vt_device_s *);

#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif /* _I386__VT_H_ */

sys/_vt.c

#include <sys/_vt.h>

void
__vt_init(struct vt_device_s *device)
{
    device->__device.memory = (uint16_t *) __VT_MEMOFF;
    device->__device.memory_len = __VT_WIDTH * __VT_HEIGHT;
}

你的双重包含守卫防止一个文件在被另一个 re-included 时包含自己。解决这个问题的唯一方法是你必须打破这个循环。确定哪个 header 是“更高”的,并将包含“更低”,不要试图从较低的一个中包含较高的。较低的一个必须单独有效。

原因是 pre-processor 必须将多个文件转换为编译器的一个线性行序列。编译器必须先查看一组文件内容。

如果你有这样的循环包含,你可以让代码的最终用户知道谁先出现。如果它们包含文件 A,那么它将包含文件 B,这将尝试再次包含文件 A 但它会被包含守卫阻止,因此 B 的内容将首先被解析。另一方面,如果最终用户首先包含 B,那么编译器将首先看到 A 的内容。

因此,如果您保持原样,那么首先包含哪个文件实际上是随机的。如果你打破这个循环,你可以自己决定先包含哪个。

一旦你决定了,你可以修复关于不完整类型的编译器错误,方法是让你选择放在第一个的文件能够独立存在,然后让第二个使用第一个文件的定义。