c - 使用#define 为结构成员起别名

标签 c coding-style c-preprocessor misra do178-b

这是一个主观问题,所以我会接受“没有答案”但要完整阅读,因为这是专门针对代码对安全至关重要的系统。

我为安全关键系统采用了一些嵌入式 C 代码,其中原作者(在随机位置)使用了如下语法:

#include <stdio.h>

typedef struct tag_s2 {
    int a;
}s2;

typedef struct tag_s1 {
  s2 a;
}s1;

s1 inst;

#define X_myvar inst.a.a

int main(int argc, char **argv)
{
   X_myvar = 10;
   printf("myvar = %d\n", X_myvar + 1);
   return 0;
}

有效地使用#define 来别名和隐藏深层结构成员。多为两三层,偶尔有四层。 顺便说一句:这是一个简单的例子,真正的代码要复杂得多,但我不能在这里发布其中的任何部分。

this的使用不一致,有的地方直接使用别名变量,有的地方代码没有别名。

在我看来,这是一种不好的做法,因为它模糊了代码,没有降低可维护性和可读性,从而导致 future 的错误和误解。

如果样式是 100% 一致的,那么我可能会更满意。

但是,对于安全至关重要的更改是昂贵的。因此,我不想解决“我还没有破产”的问题,我对其他争论持开放态度。

我应该修复它还是让 well 一个人呆着?

是否有任何指南(例如通用 C、MISRA 或 DO178B 风格指南)对此有意见?

最佳答案

However, being safety critical a change is costly. So not wanting to fix 'wot aint broke' I am open to other arguments.

这是自相矛盾的死亡螺旋,最关键的代码得到最少的关注,因为人们害怕改变它。

您犹豫要不要对这段代码进行简单的、死记硬背的重构,这告诉我代码要么没有测试,要么您不信任这些测试。当您因为可能会破坏代码而害怕改进代码时,就会延迟对代码的改进。您可能会做尽可能小的事情,这会使代码更加脆弱和不安全。

我建议首先要做的是进行一些测试以及用于试验的暂存环境。那么所有的变化都会变得更安全。当您发现这段代码正在做的所有奇怪和危险的事情时,最初可能会有一些失误,但这就是暂存区的用途。从中长期来看,每个人都会更快、更有信心地改进这段代码。使代码更容易和更安全地更改可以使代码更容易和更安全地更改;然后螺旋上升,而不是下降。


使宏看起来像单个变量的技术是我以前在 Perl 5 代码库中看到的一种技术。它用 C 宏编写的比用 C 编写的多。例如,here's a bit of manipulating the Perl call stack .

#define SP sp
#define MARK mark
#define TARG targ

#define PUSHMARK(p) \
    STMT_START {                                                      \
        I32 * mark_stack_entry;                                       \
        if (UNLIKELY((mark_stack_entry = ++PL_markstack_ptr)          \
                                           == PL_markstack_max))      \
        mark_stack_entry = markstack_grow();                      \
        *mark_stack_entry  = (I32)((p) - PL_stack_base);              \
        DEBUG_s(DEBUG_v(PerlIO_printf(Perl_debug_log,                 \
                "MARK push %p %" IVdf "\n",                           \
                PL_markstack_ptr, (IV)*mark_stack_entry)));           \
    } STMT_END

#define TOPMARK S_TOPMARK(aTHX)
#define POPMARK S_POPMARK(aTHX)

#define INCMARK \
    STMT_START {                                                      \
        DEBUG_s(DEBUG_v(PerlIO_printf(Perl_debug_log,                 \
                "MARK inc  %p %" IVdf "\n",                           \
                (PL_markstack_ptr+1), (IV)*(PL_markstack_ptr+1))));   \
        PL_markstack_ptr++;                                           \
} STMT_END
#define dSP     SV **sp = PL_stack_sp
#define djSP        dSP
#define dMARK       SV **mark = PL_stack_base + POPMARK
#define dORIGMARK   const I32 origmark = (I32)(mark - PL_stack_base)
#define ORIGMARK    (PL_stack_base + origmark)

#define SPAGAIN     sp = PL_stack_sp
#define MSPAGAIN    STMT_START { sp = PL_stack_sp; mark = ORIGMARK; } STMT_END

#define GETTARGETSTACKED targ = (PL_op->op_flags & OPf_STACKED ? POPs : PAD_SV(PL_op->op_targ))
#define dTARGETSTACKED SV * GETTARGETSTACKED

这些是宏上的宏。 Perl 5 源代码中充斥着它们。那里发生了很多不透明的魔法。其中一些需要是宏以允许赋值,但许多可以是内联函数。尽管是公共(public) API 的一部分 they are indifferently documented部分原因是它们是宏而不是函数。

如果您已经非常熟悉 Perl 5 源代码,这种风格非常聪明且有用。对于其他人来说,这使得 Perl 5 的内部结构非常难以使用。虽然一些编译器会为宏扩展提供堆栈跟踪,但其他编译器只会报告扩展的宏,这让人摸不着头脑 const I32 origmark = (I32)(mark - PL_stack_base) 是什么鬼,因为它从不出现在您的来源中。

与许多宏 hack 一样,虽然该技术非常巧妙,但对许多程序员来说也是令人费解和陌生的。 在安全关键代码中,您不希望费脑筋。您需要简单、乏味的代码。仅此一项就是用命名良好的 getter 和 setter 函数替换它的最简单的论据。相信编译器会优化它们。


这方面的一个很好的例子是 GLib,它仔细使用文档齐全的类函数宏来制作通用数据结构。例如,adding a value to an array .

#define             g_array_append_val(a,v)

虽然这是一个宏,但它的行为和记录就像一个函数。它是宏,仅作为一种创建安全类型通用数组的机制。它不隐藏任何变量。您可以安全地使用它,而不会意识到它是一个宏。


总之,是的,改吧。但是,与其简单地将 X_myvar 替换为 inst.a.a,不如考虑创建继续提供封装的函数。

void s1_set_a( s1 *s, int val ) {
    s->a.a = val;
}

int s1_get_a( s1 *s ) {
    return s->a.a;
}

s1_set_a(&inst, 10);
printf("myvar = %d\n", s1_get_a(&inst) + 1);

s1 的内部结构是隐藏的,以便以后更改内部结构(例如,将 s1.a 更改为指针以节省内存)。您正在使用的变量很清楚,使整个代码更容易理解。函数名称清楚地解释了正在发生的事情。因为它们是函数,所以它们有一个明显的文档位置。相信编译器知道如何最好地优化它。

关于c - 使用#define 为结构成员起别名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55893524/

相关文章:

python - 为什么 PEP8 声明导入通常应该在不同的行上?

c++ - 标题和模板类中的 namespace 包含

C 预处理器魔法

c - 如何将三个无符号整数打包成 C 中的一个无符号短整数

c - 为什么 NtOpenSection 返回错误号 0xc0000024?

Python 样式 : lowercase class names for "namespaces"?

c - 为 make 中的所有文件定义一个预处理器变量

c - 在 if 语句中使用 strcmp

c - Yacc, union 结构指针

c++ - 在编译时将文件读入字符串