是否可以#include .c源文件以获得嵌入式C代码的可维护性?

nce*_*rar 29 c embedded maintainability performance stm32

我不是专业的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"}
}
Run Code Online (Sandbox Code Playgroud)

因为我需要从索引访问结构,所以我需要保持#define和结构声明同步.这意味着我必须在正确的位置插入新元素并相应地更新#define.

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

无论如何,这个文件还包含很多处理这个结构的函数.我还希望保持代码分离并避免全局变量.

为了使事情变得"更容易",我正在考虑将这个"容易出错的定义"转移到.c仅包含此结构的单个源文件中.这个文件将是"危险的小心文件",并将其包含在我的实际"正常功能"文件中.

你怎么看待这件事?包含.c源文件是否有效?还有另一种更好的方法来处理我的结构吗?

Ian*_*ott 39

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

const MyElements elems[] = {
    [TOTO_IND] = {"TOTO", 18, "French"},
    [TITI_IND] = {"TITI", 27, "English"},
    [TATA_IND] = {"TATA", 45, "Spanish"},
};
Run Code Online (Sandbox Code Playgroud)

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

const MyElements elems[] = {
    [TITI_IND] = {"TITI", 27, "English"},
    [TATA_IND] = {"TATA", 45, "Spanish"},
    [TOTO_IND] = {"TOTO", 18, "French"},
};
Run Code Online (Sandbox Code Playgroud)

如果如上所述从初始化程序自动设置数组长度(即通过使用[]而不是[NUM_ELEMS]),则长度将比最大元素索引多一个.

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

  • 重要的是要注意指定的初始化程序是C99功能,不能在C95中编译,也不能在C++ 11中使用 (5认同)
  • 这仍然不能阻止OP编写`[TOTO_IND] = {"TITI",27,"English"}`. (2认同)
  • 为了解决@ Groo的观点,OP可以使用宏(例如`#define decl(idx,n,lang)[idx ## _ IND] = {#idx,n,lang}`. (2认同)

Lun*_*din 33

你应该使用指定的初始化器,如Ian Abbot的答案所示.

此外,如果数组索引相邻,就像这里的情况一样,您可以使用枚举:

toto.h

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;
Run Code Online (Sandbox Code Playgroud)

toto.c

const MyElements elems [] = {
  [TITI_IND] = {"TITI", 27, "English"},
  [TATA_IND] = {"TATA", 45, "Spanish"},
  [TOTO_IND] = {"TOTO", 18, "French"},
};
Run Code Online (Sandbox Code Playgroud)

现在,您可以使用静态断言验证整个数组的数据完整性:

_Static_assert(sizeof elems/sizeof *elems == TOTO_N, 
               "Mismatch between toto_t and elems is causing rain in Africa");
Run Code Online (Sandbox Code Playgroud)

_Static_assert(sizeof elems/sizeof *elems == TOTO_N, ERR_MSG);
Run Code Online (Sandbox Code Playgroud)

在哪里ERR_MSG定义为

#define STR(x) STR2(x)
#define STR2(x) #x
#define ERR_MSG "Mismatching toto_t. Holding on line " STR(__LINE__)
Run Code Online (Sandbox Code Playgroud)

  • @GiacomoAlzetta这是一个糟糕的笑话 - 非洲的雨绝对是一种祝福.我现在已经更新了这个问题,并断言指出了违规行. (7认同)
  • @cat我会在20年前做到的,是的.目前大多数嵌入式系统编译器都支持C11.其中一些可能仍然只支持C99.此外,C(和C++)标签使用策略是假设当前标准(C17),除非OP明确告知我们. (2认同)

Gro*_*roo 18

其他答案已经以更清晰的方式涵盖了它,但为了完整起见,这里有一个x-macros方法,如果你愿意沿着这条路走下去并冒着你同事的风险.

X-macro是一种代码生成形式,使用内置的C预处理器.目标是尽量减少重复,尽管有一些缺点:

  1. 如果您不习惯使用预处理器生成枚举和结构的源文件可能看起来很复杂.
  2. 与生成源文件的外部构建脚本相比,使用x-macros,除了使用编译器设置并手动检查预处理文件之外,您永远不会看到编译期间生成的代码的样子.
  3. 由于您没有看到预处理的输出,因此您无法使用调试器来逐步执行生成的代码,就像使用外部脚本生成的代码一样.

首先,在单独的文件中创建宏调用列表,例如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)
Run Code Online (Sandbox Code Playgroud)

然后在每次需要包含此列表时定义宏,以便每次调用都呈现为您要创建的构造的单行 - 并且您通常连续多次重复此行,即

// 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
Run Code Online (Sandbox Code Playgroud)

哪个将被预处理成类似的东西:

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" },
};
Run Code Online (Sandbox Code Playgroud)

显然,频繁维护列表变得更容易,代价是生成代码变得更复杂.

  • @Groo:使用预处理器的一个优点是它避免了对构建C程序所需的任何工具的依赖性,这反过来又有助于移植到其他构建系统. (3认同)

Ris*_*aje 5

在多个文件中定义constas static并不是一个好主意,因为它会创建大变量的多个实例MyElements.这将增加嵌入式系统的内存.在static预选赛中需要删除.

这是一个建议的解决方案:

在file.h中

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

extern const MyElements elems[MAX_ELEMS];
Run Code Online (Sandbox Code Playgroud)

在file.c中

#include "file.h"
const MyElements elems [MAX_ELEMS] = {
    {"TOTO", 18, "French"},
    {"TITI", 27, "English"},
    ...,
    {"TATA", 45, "Spanish"}
}
Run Code Online (Sandbox Code Playgroud)

修改后,放入#include "file.h"所需的.c文件.

  • @EricPostpischil如果多个文件中需要该数组,则将其设置为静态将具有该阵列的多个副本.OP提到阵列的大小很大,所以这个选项被忽略了. (3认同)