Компиляция структуры C не удалась - PullRequest
0 голосов
/ 10 сентября 2018

Я работаю над драйвером для Microchip Harmony Framework . Это похоже на драйвер Linux. У меня есть struct (NRF24L01_MainStateInfo), в котором хранится все состояние, необходимое драйверу, это просто «коллекция» из enum s. 2 дней я боролся с этим:

.. / .. / .. / .. / framework / driver / nrf24l01 / src / states / initialization_state /../../../ drv_nrf24l01.h: 51: 2: ошибка: неизвестное имя типа ' 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_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 Ответ

0 голосов
/ 11 сентября 2018

У вас круговая зависимость заголовка, что является плохим дизайном и почти всегда приводит к сбоям. Проблема осложняется слишком сложной стратегией именования, которая делает код действительно трудным для чтения.

Вот основная проблема, использующая значительно упрощенные имена:

Файл драйвера.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, который еще не определен.

    Полагаю, вы уже столкнулись с этой проблемой из-за избыточных объявлений struct в state.h. Вставив эти декларации, вы могли бы удалить #include "driver.h", но, возможно, у вас возникли другие потребности.

  • С другой стороны, если вы сначала #include "state.h", компилятор видит, что его защита заголовка не установлена, устанавливает защиту заголовка и продолжает работу с заголовком. Он сразу видит #include "driver.h"; защита заголовка еще не установлена, поэтому она устанавливает защиту заголовка и продолжает работу с этим заголовком. Теперь, когда он попадает в #include "state.h" в заголовке driver.h, он ничего не делает, потому что защита заголовка уже установлена.

    К сожалению, это #include действительно необходимо. Компиляция завершается неудачно, когда делается попытка определить член типа State, который еще не был определен. В этом случае вы на самом деле включаете целый объект State, а не просто указатель, поэтому вы не можете с легкостью просто объявить вкладку struct вперед.

Короче говоря, все будет работать нормально с одним заголовком, включая порядок, но не с другим. К сожалению, очень сложно предсказать порядок, в котором заголовки будут включены в сложный проект, потому что заголовки не всегда видны. Они могут появляться внутри других заголовков, которые включены. Это может привести к тому, что заголовки будут включены в «неправильный» порядок.

В общем случае не стоит писать заголовки, которые должны быть #include d в определенном порядке. Это почти всегда заканчивается такой проблемой.

Когда у вас есть несколько взаимосвязанных типов, которые все используются одним и тем же небольшим компонентом, вам лучше поместить их все в один заголовок. Вам все равно нужно будет правильно отсортировать заказ, но, по крайней мере, все они находятся в одном месте. В этом единственном заголовочном файле может потребоваться предварительное объявление структур, чтобы позволить указатели на них из других структур. Помещение определений типов перед прототипами уменьшает необходимость предварительного объявления ссылок на прототипы.

Если у вас много типов, вы можете поместить все их объявления в один внутренний project/types.h заголовок. Затем вы можете расположить прототипы в любой сложной файловой организации, которая вам нравится. Это довольно распространенная парадигма. Для внешних заголовков прототипа, то есть заголовков, которые объявляют функции, предназначенные для отображения в глобальном масштабе, вы можете уменьшить беспорядок, просто объявив структуры, используемые прототипами. Предполагая, что прототип использует только указатель на структуру, что, безусловно, является наиболее распространенным, нет необходимости делать определение структуры видимым.

Предупреждение: Следуют рекомендациям по стилю. Если вам не нравятся такие вещи, вы можете перестать читать здесь.

После того как ваши заголовки отсортированы, если они используются только для внутренних целей, вы можете упростить вещи для себя и своих читателей, удалив ненужные префиксы из внутренней структуры и имен перечислений. Typedefs, теги структуры и объединения и перечисления не имеют связи; они не могут проникнуть в другой отдельно скомпилированный модуль перевода. Поэтому нет необходимости делать их глобально уникальными, если они не предназначены для глобального использования.

Независимо от того, что вы можете увидеть в чужом коде, совершенно не нужно делать имена typedef отличными от тегов struct, даже если вы когда-нибудь намереваетесь скомпилировать с C ++. В C имена находятся в двух совершенно разных пространствах имен; тег struct распознается как таковой только в том случае, если ему предшествует токен struct. Так

typedef struct MyStructure MyStructure;

абсолютно верно. Фактически, это действительно, даже если struct MyStructure еще не выделено, что делает его удобным для типов структур, которые включают указатели на тот же тип.

Я обычно использую стиль, показанный во фрагментах кода выше, всегда ставя typedef перед определением структуры. Я нахожу это более читабельным, чем указывать имя typedef в конце определения структуры, даже если в моем стиле имена всегда одинаковы. Кроме того, форвард typedef можно просто скопировать в заголовки, которые в них нуждаются. C не жалуется, если вы typedef указали одно и то же имя более одного раза, так что это абсолютно безопасно.

...