C 结构编译失败

C struct compilation fails

我正在为 Microchip Harmony Framework 开发驱动程序。它看起来像一个 Linux 驱动程序。我有一个 struct (NRF24L01_MainStateInfo) 存储驱动程序需要的所有状态,它只是一个由 enum 组成的 "collection"。我为此苦苦挣扎了 2 天:

../../../../framework/driver/nrf24l01/src/states/initialization_state/../../../drv_nrf24l01.h:51:2: error: unknown type name 'NRF24L01_MainStateInfo''

具有该类型成员(以及错误指向的位置)的 struct 是以下成员:

#ifndef __DRV_NRF24L01_H__
#define __DRV_NRF24L01_H__

// Framework include
//...
// Project specific include
#include "src/memory_map.h"
#include "src/nrf_definitions.h"
#include "src/states/drv_nrf24l01_main_state.h" // NRF24L01_MainStateInfo defined here
#include "src/bus/drv_nrf24l01_bus.h"
//...
typedef struct _nrf24l01_driver_info {
    // Driver in use? (already configured)
    bool inUse;
    // Driver's mutex
    OSAL_MUTEX_HANDLE_TYPE drvMutex;
    // Driver configuration
    NRF24L01_ConfigData config;
    // Client count. Useless since there is a mapping 1:1 between driver and client
    uint8_t clientCnt;
    // Driver system status (returned by status)
    SYS_STATUS status;
    // FSM state
    NRF24L01_MainStateInfo state; // <-- This member generate the error

    // Bus information
    NRF24L01_BusVTable vTable;
    void *busInfo;

} NRF24L01_DriverInfo;

//...

#endif

结构NRF24L01_MainStateInfosrc/states/drv_nrf24l01_main_state.h中声明如下:

#ifndef __DRV__NRF24L01_MAIN_STATE_H__
#define __DRV__NRF24L01_MAIN_STATE_H__

//#include "../../drv_nrf24l01.h"
#include "initialization_state/drv_nrf24l01_init_state.h"

struct _nrf24l01_driver_info;

/*
  Main driver state. These are the state that the developer will see.
*/
typedef enum {
  NRF24L01_MAIN_STATE_UNINITIALIZED = 0,
  NRF24L01_MAIN_STATE_INITIALIZATION,
  NRF24L01_MAIN_STATE_RUNNING,
  NRF24L01_MAIN_STATE_CLOSING,
  NRF24L01_MAIN_STATE_CLOSED
} NRF24L01_MAIN_STATE;

typedef struct _nrf24l01_mainstate_info {
  NRF24L01_MAIN_STATE mainState;
  NRF24L01_INIT_STATE initState;
} NRF24L01_MainStateInfo;

int32_t DRV_nRF24L01_MainStateTask(struct _nrf24l01_driver_info *pDrv);

#endif /* end of include guard: __DRV__NRF24L01_MAIN_STATE_H__ */

现在我不明白为什么会出现这个错误。

目录树如下:

nrf24l01 .
│   drv_nrf24l01.h
│   LICENSE
│   README.md
│
├───config
│       .gitignore
│       drv_nrf.hconfig
│       drv_nrf24l01.hconfig
│       drv_nrf24l01_idx.ftl
│
├───src
│   │   drv_nrf24l01.c
│   │   memory_map.h
│   │   nrf_definitions.h
│   │
│   ├───bus
│   │   │   drv_nrf24l01_bus.h
│   │   │
│   │   └───spi
│   │           drv_nrf24l01_spi.c
│   │           drv_nrf24l01_spi.h
│   │
│   ├───internal
│   │       drv_nrf_internal.c
│   │       drv_nrf_internal.h
│   │
│   └───states
│       │   drv_nrf24l01_main_state.c
│       │   drv_nrf24l01_main_state.h
│       │
│       ├───closing_state
│       ├───initialization_state
│       │       drv_nrf24l01_init_state.c
│       │       drv_nrf24l01_init_state.h
│       │
│       └───running_state
└───templates
        system_config.h.ftl
        system_definitions.h.INC.ftl
        system_definitions.h.OBJ.ftl
        system_init.c.DRV.ftl
        system_init.c.INIT.ftl
        system_interrupt.c.ftl
        system_tasks.c.ftl

也许我遗漏了什么?

编译器是 xc32-gcc uC 是 PIC32MX110F016B.

你有一个循环 header 依赖,这是糟糕的设计,几乎总是会导致失败。 overly-complicated 命名策略使问题变得复杂,这使得代码很难阅读。

这是基本问题,使用显着简化的名称:

文件driver.h

#ifndef DRIVER_H
#define DRIVER_H

#include "state.h"

/* See answer text for an explanation of this declaration style. */
typedef struct Driver Driver;
struct Driver {
  // ...
  State state;
  // ...
};

#endif

文件state.h

#ifndef STATE_H
#define STATE_H

// Needed because the Driver type is used in a prototype
#include "driver.h"

typedef struct State State;
struct State {
  // ...
};

// Attempt to fix circular dependency with a redundant declaration.
typedef struct Driver Driver;
int32_t stateTask(Driver* driver);
#endif

因此这两个 header 相互包含。 header 守卫将防止它们被包含两次,但它们不能保证以正确的顺序阅读声明。会发生什么取决于您 #include 两个 header 的顺序:

  • 如果您先 #include "driver.h",它将设置其 header 守卫并立即 #include "state.h"。它之前没有被包含,所以 header 守卫没有设置,编译器开始处理它。它立即命中 #include driver.h",但 header 守卫现在已设置,即使 header 尚未真正处理,因此避免了循环包含。最终到达引用类型 Driver 的原型,该类型尚未定义。

    由于 state.h 中的冗余 struct 声明,我假设您已经遇到了这个问题。插入这些声明后,您可以删除 #include "driver.h" 但也许您还有其他需要。

  • 另一方面,如果你先 #include "state.h",编译器发现它的 header 守卫没有被设置,设置 header 守卫,并继续 header。它立即看到 #include "driver.h"; header 守卫尚未设置,因此它设置 header 守卫并继续 header。现在,当它命中 driver.h header 中的 #include "state.h" 时,它什么也不做,因为现在已经设置了 header 守卫。

    不幸的是,#include 确实很有必要。尝试定义尚未定义的 State 类型的成员时编译失败。在这种情况下,您实际上包括了整个 State object,而不仅仅是使用指针,因此您无法仅向前声明 struct 选项卡。

简而言之,使用一个 header include 命令可以正常工作,但使用另一个则失败。不幸的是,很难预测 header 被包含在一个复杂项目中的顺序,因为 header 包含并不总是可见的。它们可能出现在包含的其他 header 中。这可能导致 headers 被包含在 "wrong" 订单中。

一般来说,写 headers 不是一个好主意,必须按特定顺序 #included。它几乎总是以这种问题结束。

当同一个小组件使用多个 inter-related 类型时,最好将它们全部放入一个 header 中。您仍然需要正确地整理订单,但至少它们都在一个地方。在那个单独的 header 文件中,可能需要 forward-declare 结构以允许从其他结构指向它们的指针。将类型定义放在原型之前可以减少 forward-declare 原型引用的需要。

如果你有很多类型,你可以将它们的所有声明放在一个内部 project/types.h header 中。然后你可以按照你喜欢的任何复杂的文件组织方式来安排原型。这是一个很常见的范例。对于外部原型 headers——也就是说,headers 声明了全局可见的函数——你可以通过向前声明原型使用的结构来减少混乱。假设原型只使用指向结构体的指针,这肯定是最常见的,没有必要让结构体的定义可见。

警告: 以下是自以为是的风格建议。如果你不喜欢那种东西,你可以在这里停止阅读。

整理好 header 后,假设它们仅在内部使用,您可以通过从内部结构和枚举名称中删除不必要的前缀来为您自己和您的读者简化事情。 Typedef、结构和联合标记以及枚举没有链接;它们不能泄漏到另一个单独编译的翻译单元中。因此,如果它们不打算供全球使用,则无需使它们在全球范围内唯一。

无论您在其他人的代码中看到什么,绝对没有必要使 typedef 名称与 struct 标签不同,即使您有一天打算使用 C++ 进行编译。在 C 中,名称位于两个完全独立的名称空间中; struct 标记只有在前面带有标记 struct 时才会被识别。所以

typedef struct MyStructure MyStructure;

绝对有效。事实上,即使 struct MyStructure 还没有充实它也是有效的,这使得包含指向相同类型的指针的结构类型很方便。

我倾向于使用上面代码片段中显示的样式,始终将 typedef 放在结构定义之前。我发现这比将 typedef 名称放在结构定义,即使在我的风格中名称总是相同的。此外,转发 typedefs 可以简单地复制到需要它们的 headers。如果您 typedef 同一类型多次使用相同的名称,C 不会报错,所以这是完全安全的。