为了嵌入式c代码的可维护性,可以#包括.c源文件吗?

为了嵌入式c代码的可维护性,可以#包括.c源文件吗?,c,performance,embedded,stm32,maintainability,C,Performance,Embedded,Stm32,Maintainability,我不是一个专业的C程序员,我知道从另一个源文件中包含.C被认为是不好的做法,但我有一种情况,我认为这可能有助于维护性 我有一个包含很多元素的大结构,我使用#define来保存索引 #define TOTO_IND 0 #define TITI_IND 1 … #define TATA_IND 50 static const MyElements elems [] = { {"TOTO", 18, "French"}, {"TITI", 27, "English"},

我不是一个专业的C程序员,我知道从另一个源文件中包含
.C
被认为是不好的做法,但我有一种情况,我认为这可能有助于维护性

我有一个包含很多元素的大结构,我使用
#define
来保存索引

#define TOTO_IND 0 
#define TITI_IND 1 
…
#define TATA_IND 50

static const MyElements elems [] = {
    {"TOTO", 18, "French"},
    {"TITI", 27, "English"},
    ...,
    {"TATA", 45, "Spanish"}
}
因为我需要从索引访问结构,所以我需要保持
#define
和结构声明的同步。这意味着我必须在正确的位置插入新元素,并相应地更新
#define

它很容易出错,我并不喜欢它(但出于性能考虑,我没有找到更好的解决方案)

无论如何,该文件还包含许多处理此结构的函数。我还希望保持代码的分离,避免全局变量

为了使事情“更简单”,我考虑将这个“容易出错的定义”移动到一个
.c
源文件中,该文件只包含这个结构。该文件将是“危险的小心文件”,并将其包含在我实际的“正常功能”文件中


你觉得怎么样?包含
.c
源文件是否有效?还有其他更好的方法处理我的结构吗?

在多个文件中将
常量定义为
static
,这不是一个好主意,因为它会创建大变量
MyElements
的多个实例。这将增加嵌入式系统中的内存。需要删除
静态
限定符

以下是建议的解决方案:

在file.h中

在file.c中


修改后,将
#include“file.h”
放在所需的.c文件中。

您可以使用指定的初始值设定项来初始化
元素[]
,而无需知道每个索引标识符(或宏)的显式值

数组元素将以相同的方式初始化,即使更改它们在源代码中的显示顺序:

const MyElements elems[] = {
    [TITI_IND] = {"TITI", 27, "English"},
    [TATA_IND] = {"TATA", 45, "Spanish"},
    [TOTO_IND] = {"TOTO", 18, "French"},
};
如果如上所述从初始值设定项自动设置数组长度(即使用
[]
而不是
[NUM_ELEMS]
),则长度将比最大元素索引大一个


这允许您将
元素
数组的索引值和外部声明保存在.h文件中,并在单独的.c文件中定义
元素
数组内容。

您应该使用指定的初始值设定项,如Ian Abbot的回答所示

此外,如果数组索引是相邻的,如此处所示,则可以使用枚举:

托托

typedef enum
{
  TOTO_IND,
  TITI_IND,
  ...
  TATA_IND,
  TOTO_N    // this is not a data item but the number of items in the enum
} toto_t;
托托

const MyElements elems [] = {
  [TITI_IND] = {"TITI", 27, "English"},
  [TATA_IND] = {"TATA", 45, "Spanish"},
  [TOTO_IND] = {"TOTO", 18, "French"},
};
现在,您可以使用静态断言验证整个阵列的数据完整性:

其中,
ERR\u MSG
定义为

#define STR(x) STR2(x)
#define STR2(x) #x
#define ERR_MSG "Mismatching toto_t. Holding on line " STR(__LINE__)

其他答案已经以一种更清晰的方式涵盖了这一点,但为了完整性起见,这里有一个x-macros方法,如果你愿意走这条路,冒着同事的愤怒

X宏是使用内置C预处理器生成代码的一种形式。目标是将重复次数减少到最低限度,尽管有一些缺点:

  • 如果您不习惯使用预处理器生成枚举和结构的源文件,它可能看起来很复杂
  • 与生成源文件的外部构建脚本相比,使用x宏,除非使用编译器设置并手动检查预处理文件,否则在编译过程中,您永远看不到生成的代码是什么样子
  • 由于看不到预处理的输出,因此无法使用调试器以与处理外部脚本生成的代码相同的方式逐步处理生成的代码
  • 首先,在单独的文件中创建宏调用列表,例如
    elements.inc
    ,而不定义宏此时的实际功能:

    // elements.inc
    
    // each row passes a set of parameters to the macro,
    // although at this point we haven't defined what the
    // macro will output
    
    XMACRO(TOTO, 18, French)
    XMACRO(TITI, 27, English)
    XMACRO(TATA, 45, Spanish)
    
    然后每次需要包含此列表时都定义宏,这样每次调用都会呈现到要创建的构造的一行中——通常在一行中重复多次,即

    // concatenate id with "_IND" to create enums, ignore code and description
    // (notice how you don't need to use all parameters each time)
    // e.g. XMACRO(TOTO, 18, French) => TOTO_IND,
    #define XMACRO(id, code, description) id ## _IND,
    typedef enum
    {
    #    include "elements.inc"
         ELEMENTS_COUNT
    }
    Elements;
    #undef XMACRO
    
    // create struct entries
    // e.g. XMACRO(TOTO, 18, French) => [TOTO_IND] = { "TOTO", 18, "French" },
    #define XMACRO(id, code, description) [id ## _IND] = { #id, code, #description },
    const MyElements elems[] = {
    {
    #    include "elements.inc"
    };
    #undef XMACRO
    
    这将被预处理成如下内容:

    typedef enum
    {
        TOTO_IND,
        TITI_IND,
        TATA_IND,
        ELEMENTS_COUNT
    }
    Elements;
    
    const MyElements elems[] = {
    {
        [TOTO_IND] = { "TOTO", 18, "French" },
        [TITI_IND] = { "TITI", 27, "English" },
        [TATA_IND] = { "TATA", 45, "Spanish" },
    };
    

    显然,列表的频繁维护变得更加容易,而生成的代码变得更加复杂。

    要解决有关使用
    #include
    .c
    文件的具体问题(其他答案建议更好的选项,特别是Groo的答案),通常没有必要

    .c
    文件中的所有内容都可以在外部显示和访问,因此您可以通过函数原型和
    #extern
    对其进行引用。例如,您可以使用
    #extern const MyElements elems[]引用表在主
    .c
    文件中


    或者,您可以将定义放在
    .h
    文件中并包含该文件。这允许您根据需要隔离代码。请记住,
    #include
    所做的一切就是在
    #include
    语句所在的位置插入包含文件的内容,因此它不必具有任何特定的文件扩展名
    .h
    按惯例使用,大多数IDE会自动将
    .c
    文件添加到要编译的文件列表中,但就编译器而言,命名是任意的。

    即使文件包含变量定义,您仍然可以使用
    .h
    结尾命名该文件。如果您将要包含的文件命名为“file.inc”或“file.c.inc”,可能会更好。您是否知道,如果包含该文件,则需要
    #define
    内容的其他所有c文件也将获得其自己的数组副本?我缺少什么?为什么不把这个结构放在它自己的.C文件中,然后
    extern
    将它放在需要访问它的地方呢?将数据声明在一个某种格式的文件中是可以接受的吗
    _Static_assert(sizeof elems/sizeof *elems == TOTO_N, ERR_MSG);
    
    #define STR(x) STR2(x)
    #define STR2(x) #x
    #define ERR_MSG "Mismatching toto_t. Holding on line " STR(__LINE__)
    
    // elements.inc
    
    // each row passes a set of parameters to the macro,
    // although at this point we haven't defined what the
    // macro will output
    
    XMACRO(TOTO, 18, French)
    XMACRO(TITI, 27, English)
    XMACRO(TATA, 45, Spanish)
    
    // concatenate id with "_IND" to create enums, ignore code and description
    // (notice how you don't need to use all parameters each time)
    // e.g. XMACRO(TOTO, 18, French) => TOTO_IND,
    #define XMACRO(id, code, description) id ## _IND,
    typedef enum
    {
    #    include "elements.inc"
         ELEMENTS_COUNT
    }
    Elements;
    #undef XMACRO
    
    // create struct entries
    // e.g. XMACRO(TOTO, 18, French) => [TOTO_IND] = { "TOTO", 18, "French" },
    #define XMACRO(id, code, description) [id ## _IND] = { #id, code, #description },
    const MyElements elems[] = {
    {
    #    include "elements.inc"
    };
    #undef XMACRO
    
    typedef enum
    {
        TOTO_IND,
        TITI_IND,
        TATA_IND,
        ELEMENTS_COUNT
    }
    Elements;
    
    const MyElements elems[] = {
    {
        [TOTO_IND] = { "TOTO", 18, "French" },
        [TITI_IND] = { "TITI", 27, "English" },
        [TATA_IND] = { "TATA", 45, "Spanish" },
    };