C结构体编译失败

3
我正在为Microchip Harmony Framework编写驱动程序。它看起来像是一个Linux驱动程序。我有一个结构体(NRF24L01_MainStateInfo),它存储驱动程序所需的所有状态,它只是由枚举值组成的“集合”。我已经努力了2天,但还是遇到了这个错误:
"../../../../framework/driver/nrf24l01/src/states/initialization_state/../../../drv_nrf24l01.h:51:2: error: unknown type name 'NRF24L01_MainStateInfo'"
该结构体具有一个该类型的成员(错误指向的位置)如下:
#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_MainStateInfo 结构在 src/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,微控制器是 PIC32MX110F016B


1
可能不是你问题的原因,但值得注意的是,在C语言中以两个下划线开头或者下划线后跟大写字母的符号是被保留的,自己定义这些符号会导致未定义的行为。 - Christian Gibbons
你在头文件中查找未定义的枚举了吗? - Jay
@MFisherKDX,请检查一下,只有2个匹配项(如果包括对“#endif”的注释,则为3个)。 - gabbla
@Jay 我没有未定义的枚举。 - gabbla
1
NRF24L01_INIT_STATE 是在哪里和如何定义的? - MiCo
显示剩余13条评论
1个回答

3

您的代码存在循环头文件依赖,这是一种糟糕的设计,几乎总是导致失败。问题由过于复杂的命名策略复杂化了,使得代码难以阅读。

以下是基本问题,使用大大简化的名称:

文件 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

所以这两个头文件相互包含。头文件保护将防止它们被包含两次,但它们不能保证声明按正确的顺序读取。发生的情况取决于您 #include 这两个头文件的顺序:
- 如果您首先 #include "driver.h",它将设置其头文件保护并立即 #include "state.h"。它之前没有被包含过,因此头文件保护未设置,编译器开始处理它。它立即命中 #include driver.h",但是该头文件保护现在已经设置,即使尚未真正处理该头文件,因此避免了循环包含。最终到达引用尚未定义的类型 Driver 的原型。
我假设您已经因 state.h 中的冗余 struct 声明而遇到了此问题。在插入这些声明后,您可以删除 #include "driver.h",但可能还有其他需要。
- 另一方面,如果您首先 #include "state.h",则编译器会看到其头文件保护未设置,设置头文件保护并处理头文件。 它立即看到 #include "driver.h"; 尚未设置该头文件保护,因此设置该头文件保护并处理该头文件。现在当它在 driver.h 头文件中命中 #include "state.h" 时,它不会执行任何操作,因为头文件保护已设置。
不幸的是,那个 #include 真的是必要的。当尝试定义尚未定义的类型 State 的成员时,编译失败。在这种情况下,您实际上正在包含整个 State 对象,而不仅是使用指针,因此无法只使用前向声明结构来解决问题。
总之,在一个头文件包含顺序下事情将正常工作,但在另一个头文件包含顺序下则会失败。不幸的是,很难预测复杂项目中头文件的包含顺序,因为头文件包含并不总是可见的。它们可能出现在被包含的其他头文件内。这可能导致头文件按“错误”的顺序被包含。
通常,编写必须以特定顺序 #include 的头文件不是一个好主意。它几乎总是会导致这种问题。
当您有多个彼此相关的类型都由同一小组件使用时,最好将它们全部放入单个头文件中。您仍然需要正确排序,但至少它们在一个地方。在该单个头文件中,可能需要前向声明结构以允许从其他结构中引用指针。将类型定义放在原型之前可以减少原型引用的前向声明需求。
如果你有很多类型,可以将它们的声明放在一个名为project/types.h 的内部头文件中。然后,你可以按照自己喜欢的复杂的文件组织方式来安排这些原型。这是一种非常常见的范例。对于外部原型头文件——也就是用于声明希望全局可见的函数的头文件——通过仅仅提前声明被原型使用的结构体,你可以减少混乱。假设原型只使用指向结构体的指针(这当然是最常见的情况),则不需要使结构体的定义可见。 警告:接下来是主观风格建议。如果你不喜欢这种东西,可以停止阅读。
一旦你解决了头文件问题,并且假设它们只在内部使用,你可以通过从内部结构体和枚举名称中省略不必要的前缀来简化自己和读者的工作。typedef、结构体/联合标记和枚举没有链接;它们不能泄漏到另一个单独编译的翻译单元中。因此,如果它们不是用于全局使用,则没有必要使它们全局唯一。
无论你在别人的代码中看到什么,都没有必要使typedef名称不同于结构体标记,即使你将来打算使用C++进行编译。在C中,这些名称处于两个完全独立的命名空间中;只有在结构体前加上struct令牌时,才能将其识别为结构体标记。因此,
typedef struct MyStructure MyStructure;

这是完全有效的。实际上,即使struct MyStructure尚未被完善,它也是有效的,这对于包含指向相同类型的指针的结构类型非常方便。

我倾向于使用上面代码片段中显示的样式,总是将typedef放在结构体定义之前。我发现这比在结构体定义的末尾远离typedef名称更易读,即使在我的风格中名称总是相同的。此外,前向typedef可以直接复制到需要它们的头文件中。如果您多次将相同名称的typedef为相同类型,C不会抱怨,因此这是完全安全的。


非常感谢您的回答,今晚我会仔细研究并根据您的建议进行一些重构!我会告诉您的 ;) - gabbla
好的,我花了一些时间进行了一些测试,是的,@rici就是这样!无论如何,我决定重新编写驱动程序的某些部分,并尽可能地简化它。再次感谢您宝贵的建议! - gabbla

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接