使用#define来对结构成员进行别名

问题描述 投票:3回答:3

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

我已经为安全关键系统采用了一些嵌入式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来对一个深层结构成员进行别名和遮盖。大多是两三个,但偶尔有四个深。 BTW:这是一个简单的例子,真正的代码要复杂得多,但我不能在这里发布任何部分。

使用它并不一致,在某些地方,别名变量直接用于别名,代码的某些部分没有别名。

IMO这是一种不好的做法,因为它使代码模糊不清,降低了可维护性和可读性,从而导致未来的错误和误解。

如果风格100%一致,那么也许我会对它更满意。

然而,对于安全至关重要的改变是昂贵的。因此,我不想修复'wot aint broke',我对其他论点持开放态度。

我应该修理它还是单独留下来?

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

c coding-style c-preprocessor misra do178-b
3个回答
2
投票

然而,对于安全至关重要的改变是昂贵的。因此,我不想修复'wot aint broke',我对其他论点持开放态度。

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

你对于对这段代码做一个简单的,重复的重构犹豫不决,告诉我代码要么没有测试,要么你不相信测试。当您害怕改进代码时,因为您可能会破坏代码,这会延迟代码的改进。你可能做的最小的事情会使代码变得更脆弱和不安全。

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


使宏看起来像单个变量的技术是我之前在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源代码充满了它们。那里发生了很多不透明的魔法。其中一些需要是宏来允许赋值,但许多可能是内联函数。尽管是公共API they are indifferently documented的一部分,部分原因是它们是宏而不是函数。

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

像许多宏观黑客一样,虽然这项技术非常聪明,但对于许多程序员来说,它也是令人费解的并且不熟悉。在安全关键代码中,精神错乱不是您想要的。你想要简单,无聊的代码。仅这一点就是用命名良好的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更改为指针以节省内存)。您正在使用的变量很清楚,使整个代码更容易理解。函数名称清楚地解释了正在发生的事情。因为它们是功能,所以它们具有明显的文档放置位置。相信编译器知道如何最好地优化它。


1
投票

从维护的角度来看,是的,这绝对是需要修复的代码。

但是,只有从这个角度来看,代码才需要修复。它不会损害程序的正确性,如果代码是正确的,那么这是最重要的考虑因素。

这就是为什么除非彻底的单元测试和回归测试方案已经到位,否则不应该修复这样的代码。如果您可以确定在过程中没有破坏正常运行的代码,那么您应该只修复这样的代码。


1
投票

是的,你应该摆脱它。模糊的宏是危险的。

在较老的C中常见的是避免拼写出深层嵌套来做类似的事情

#define a inst.a

在这种情况下,您只需键入inst.a而不是inst.a.a。尽管这是一个值得怀疑的实践,但是这些宏用于修复语言中的缺点,即缺少匿名结构。 Modern C支持C11。我们可以使用匿名结构来摆脱不必要的嵌套结构:

typedef struct {
  struct
  { 
    int a;
  };
}s1;

但是MISRA-C:2012不支持C11,因此可能不是一种选择。

你可以用来摆脱长名称的另一个技巧是这样的:

int* x = &longstructname.somemember.anotherstruct.x; 
// use *x from here on

x是局部变量,范围有限。这比晦涩的宏更具可读性,并且在机器代码中得到优化。

© www.soinside.com 2019 - 2024. All rights reserved.