【问题标题】:Why can't I have an inner structure when two files include each other?当两个文件相互包含时,为什么我不能有一个内部结构?
【发布时间】:2021-05-18 14:37:07
【问题描述】:

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

我有一个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;
};

标头<dev/vt.h> 知道__vt_device_s<sys/_vt.h> 中定义,因为它已包含在内,但我收到此错误:

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

我意识到这是因为两个文件相互依赖(整个冲突是由_vt.c 包括_vt.h 包括vt.h 包括_vt.h 引起的)但我不明白这是一个编译问题。我在两个文件中都包含了警卫!

PS:我知道如果我使用指针,这将不是问题,但由于它是一个操作系统,因此该驱动程序需要在设置分页之前运行(即,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;
}

【问题讨论】:

    标签: c struct compiler-errors dependencies circular-dependency


    【解决方案1】:

    当一个文件被另一个文件重新包含时,您的双重包含保护可防止一个文件包含它自己。解决这个问题的唯一方法是你必须打破这个循环。确定哪个标题“更高”并将包含“较低”,并且不要尝试从较低的标题中包含较高的标题。较低的必须单独有效。

    这样做的原因是预处理器必须将多个文件转换为一个线性的行序列供编译器使用。编译器必须先查看一组文件内容。

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

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

    一旦您做出决定,您就可以修复关于不完整类型的编译器错误,方法是让您选择首先放置的任何文件都能够独立存在,然后让第二个文件使用第一个文件的定义。

    【讨论】:

    • 我不明白您对我的命名约定有什么问题,下划线是用户不应该看到的内部功能的既定标准。此外,我的代码位于以下目录树中:sys/i386/_vga.h。您看到的是安装后的位置。鉴于完全相同的存根位于sys/amd64/_vga.h 等等,这不是问题。无论如何,这是一个个人项目,我不是为客户创建的。至于你的解决方案,我已经知道了。我在问为什么这是个问题。
    • 通过以下划线开头隐藏标识符是可以的。故意创建多组几乎相同的标识符是可怕的!我将编辑我的答案以进一步解释包含的问题,我们可以继续讨论 cmets 中的命名约定:-)。
    • 如果我的话还不够,也许您会想知道即使像 FreeBSD 这样的项目也使用这样的命名约定,例如 herectype.h 包含存根,_ctype.h 是一个实现。
    • 我不明白您对我的命名约定有什么问题,下划线是用户不应该看到的内部函数的既定标准。 名称以双下划线或下划线和大写字母是reserved identifiers for the C implementation,它们不是用于“用户不应该看到的内部功能”。
    • @AndrewHenle 我想那是我在可预见的未来会承担的风险。我只是玩得很​​开心,鉴于我在现实世界中不使用这样的语法,我可以把它看作是给我的编译器一个手指。 :-) (PS:感谢草稿,这将有助于完善我的标准)
    猜你喜欢
    • 2010-10-19
    • 1970-01-01
    • 2020-05-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-15
    • 2013-04-22
    • 2011-01-11
    相关资源
    最近更新 更多