Использование #define для псевдонимов членов структуры - PullRequest
3 голосов
/ 28 апреля 2019

Это субъективный вопрос, поэтому я приму «ответа нет», но прочитайте его полностью, поскольку это конкретно относится к системе, в которой код критичен по безопасности.

Я принял некоторый встроенный 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 для псевдонима и затемнения члена глубокой структуры. В основном два или три, но иногда четыре глубоких. Кстати: это простой пример, реальный код гораздо сложнее, но я не могу опубликовать здесь какую-либо его часть.

Использование этого не согласуется, в некоторых местах переменная с псевдонимом используется непосредственно, другой - по псевдониму, некоторые части кода не являются псевдонимами.

IMO, это плохая практика, поскольку она скрывает код без выгоды, снижая удобство обслуживания и читаемость, что приводит к будущим ошибкам и недопониманию.

Если бы стиль был на 100% непротиворечивым, то, возможно, я был бы более доволен им.

Однако, поскольку безопасность критически важна, изменение является дорогостоящим. Так что, не желая исправлять «не сломался», я открыт для других аргументов.

Должен ли я это исправить или оставить в покое?

Есть ли какое-либо руководство (например, общие руководства по стилям C, MISRA или DO178B), в котором есть мнение по этому поводу?

Ответы [ 3 ]

2 голосов
/ 28 апреля 2019

Однако, поскольку безопасность критически важна, изменение является дорогостоящим.Поэтому, не желая исправлять 'wot aint сломано', я открыт для других аргументов.

Парадоксальная смертельная спираль в том, что самый критический код привлекает наименьшее внимание, потому что люди боятся его менять.

То, что вы не решаетесь сделать простой рефакторинг для этого кода, говорит мне, что в коде либо нет тестов, либо вы не доверяете тестам.Когда вы боитесь улучшить код, потому что можете его сломать, это задерживает улучшения в коде.Скорее всего, вы сделаете наименьшую возможную вещь, которая сделает код еще более хрупким и небезопасным.

Я бы посоветовал первым сделать несколько тестов вместе с промежуточной средой для испытаний.Тогда все изменения станут безопаснее.Сначала могут быть некоторые проблемы, когда вы обнаружите все странные и опасные вещи, которые делает этот код, но для этого и нужна область подготовки.В среднесрочной и долгосрочной перспективе каждый будет улучшать этот код быстрее и с большей уверенностью.Делая код проще и безопаснее для изменения, можно сделать его проще и безопаснее для изменения;затем спираль идет вверх, а не вниз.


Техника создания макроса, похожего на одну переменную, - это техника, которую я видел раньше в базе кода Perl 5.Он написан больше на макросах C, чем на C. Например, здесь немного манипулирует стеком вызовов Perl .

#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 , они документально оформлены отчасти потому, что они являются макросами, а не функциями.

Этот стиль очень умный и полезный , если вы уже очень хорошо знакомыс исходным кодом Perl 5 .Для всех остальных это сделало работу с внутренностями Perl 5 чрезвычайно сложной.В то время как некоторые компиляторы предоставляют трассировки стека для расширений макросов, другие сообщают только о развернутом макросе, оставляя одного поцарапанного в голове, что, черт возьми, const I32 origmark = (I32)(mark - PL_stack_base), потому что он никогда не появляется в вашем источнике.

Как и многие макро-хаки,в то время как техника очень умна, она также сногсшибательна и незнакома многим программистам. Сгибание разума - это не то, что вам нужно в критическом коде безопасности. Вам нужен простой, скучный код.Одно это - самый простой аргумент, чтобы заменить его хорошо именованными функциями получения и установки.Доверьтесь компилятору, чтобы оптимизировать их.


Хорошим примером этого является GLib, которая тщательно использует хорошо документированные функционально-подобные макросы для создания общих структур данных.Например, добавление значения в массив .

#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 голос
/ 29 апреля 2019

Да, вы должны избавиться от этого. Неизвестные макросы опасны.

В старших версиях Си было обычным делом избегать написания глубокого вложения для таких вещей, как

#define a inst.a

В этом случае вам нужно было набрать inst.a вместо inst.a.a. Хотя это сомнительная практика, подобные макросы использовались для исправления недостатка в языке, а именно отсутствия анонимных структур. Современный C поддерживает это из C11, хотя. Мы можем использовать анонимные структуры, чтобы избавиться от ненужных вложенных структур:

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

Но MISRA-C: 2012 не поддерживает C11, так что это может быть невозможно.

Еще один прием, который вы можете использовать, чтобы избавиться от длинных имен, выглядит примерно так:

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

x - локальная переменная с ограниченной областью действия. Это гораздо удобнее для чтения, чем неясные макросы, и оно оптимизируется в машинном коде.

1 голос
/ 28 апреля 2019

С точки зрения обслуживания, да, это определенно код, который необходимо исправить.

Однако с этой точки зрения только требует исправления кода. Это не вредит правильности программы, и если код верен как есть, это первостепенное значение.

Вот почему такой код никогда нельзя исправлять, если не введен режим тщательного юнит-теста и регрессионного теста. Подобный код следует исправлять только в том случае, если вы уверены, что не нарушаете правильно работающий код в процессе.

...