#include .c源文件可以实现嵌入式C代码的可维护性吗?

时间:2018-10-11 10:21:02

标签: 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"},
    ...,
    {"TATA", 45, "Spanish"}
}

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

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

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

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

您如何看待?包括.c源文件是否有效?还有其他更好的方法来处理我的结构吗?

5 个答案:

答案 0 :(得分:39)

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

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

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

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

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

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

答案 1 :(得分: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;

toto.c

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

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

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

_Static_assert(sizeof elems/sizeof *elems == TOTO_N, ERR_MSG);

其中ERR_MSG被定义为

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

答案 2 :(得分:18)

其他答案已经以更清晰的方式涵盖了它,但是出于完整性考虑,如果您愿意走这条路线并冒着同事的怒气,这是一种x宏方法。

X宏是使用内置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)

然后,每次需要包含此列表时都定义宏,以便每次调用都呈现到要创建的结构的单行中,并且通常连续重复多次,即< / p>

// 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" },
};

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

答案 3 :(得分:5)

在多个文件中将const定义为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];
  

在file.c

#include "file.h"
const MyElements elems [MAX_ELEMS] = {
    {"TOTO", 18, "French"},
    {"TITI", 27, "English"},
    ...,
    {"TATA", 45, "Spanish"}
}

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

答案 4 :(得分:1)

要解决有关将#include.c文件一起使用的特定问题(其他答案提出了更好的选择,尤其是Groo提出的选择),通常没有必要。

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

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