Как пометить код с побочными эффектами? - PullRequest
4 голосов
/ 14 апреля 2009

Я работаю над проектом на 8051, где учитывается каждый байт. Таким образом, я использую некоторые глобальные переменные, где я бы не стал. Обычный метод передачи указателей в функцию добавляет здесь слишком много накладных расходов.

У меня есть ряд функций, которые используют однобитовые переменные (специфичное для компилятора расширение C ), чтобы сигнализировать результат функции в дополнение к нормальному возвращаемому значению.

bit global_error_flag = 0;
bit global_data_ready_flag = 0;

unsigned char A_Function (void) {
    // Do some stuff

    if ( badness ) {
        global_error_flag = 0;
        global_data_ready_flag = 1;

        return 0;
    }

    if ( data_is_ready_use ) {
        global_data_ready_flag = 1;
    }

    return a_value;    
}

void Other_Function (void) {
    unsigned char c;

    c = A_Function();

    if( global_error_flag) {
        // Do error stuff here.
    }
    else
    if( global_data_ready_flag ) {
        // Do data processing here.
    }
    global_error_flag = 0;
    global_data_ready_flag = 0;

}

Учитывая, что техника злая, есть ли способ сделать код более понятным?

Как лучше всего указать, какие вызовы функций будут иметь побочный эффект от установки этих флагов? Достаточно ли комментариев? Должен ли я назвать функции, чтобы указать их API (квази-венгерский стиль)? Должен ли я использовать макрос для маркировки таких вызовов:

#define FUNCTION_SETS_FLAGS(code) (code)

FUNCTION_SETS_FLAGS( c = A_Function() );

Есть еще идеи?

Ответы [ 9 ]

6 голосов
/ 14 апреля 2009

Использование соглашения, хотите ли вы назвать его «венгерским» или нет, является лучшим способом помечать это не случайно. Стилистически, какой-то префикс именования предпочтительнее пустого #define, по крайней мере для меня.

Это на самом деле довольно часто, я думаю. Я знаю, что среда программирования S60 использует множество обычных тегов для функций, чтобы указать, например, что они генерируют исключения.

3 голосов
/ 14 апреля 2009

Если вы просто хотите упомянуть, что функция влияет на глобальную переменную (переменные), тогда может помочь простой (венгерский) префикс.

Но если вы хотите упомянуть каждый отдельный флаг (ы), на которые он влияет, тогда, вероятно, стоит использовать заголовок функции. Как например,

  /*************************************************************************
     * FUNCTION    : <function_name>
     * DESCRIPTION : <function description> 
     * PARAMETERS  : 
     *  Param1  - <Parameter-1 explanation>
     *  Param2  - <Parameter-2 explanation>
     *  Param3  - <Parameter-3 explanation>
     * RETURN      : <Return value and type>
     * GLOBAL VARIABLES USED: 
     *  Global1 - <Global-1 explanation>
     *  Global2 - <Global-2 explanation>
     *  Global3 - <Global-3 explanation> 
  *************************************************************************/
3 голосов
/ 14 апреля 2009

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

3 голосов
/ 14 апреля 2009

Ваши глобалы помечены для ясности, это хорошее начало.

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

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

2 голосов
/ 14 апреля 2009

Вы можете использовать макрос для имитации функции, чтобы иметь больше параметров:


unsigned char _a_function(void);

#define A_Function(ret_val) (*(ret_val) = _a_function(), !global_error_flag)

...
unsigned char var;
/* call the function */
if (!A_Function(&var))
{
    /* error! */
}
else
{
    /* use var */
    var++;
}

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

2 голосов
/ 14 апреля 2009

Это на самом деле вам не поможет, но у GCC есть способ сделать напротив того, что вы хотите: пометить функции, которые имеют нет побочных эффектов. См. Атрибуты const и pure. Это больше для оптимизации, чем для документации, подумал: если компилятор знает, что данная функция не проверяет какие-либо данные, кроме своих аргументов, он может выполнять более умные оптимизации, такие как движение с использованием инвариантного кода цикла .

0 голосов
/ 22 мая 2009

Если вы еще этого не сделали, вы можете также попробовать проект sdcc на sourceforge , это компилятор C, специально предназначенный для использования во встроенной разработке, который также нацелен на 8051, в Кроме того, компилятор поддерживает ряд пользовательских, специфичных для цели и нестандартных встроенных функций компилятора для различных случаев использования, также я лично обнаружил, что команда разработчиков очень открыта и реагирует на идеи для новых улучшений и других запросов связанных функций.

0 голосов
/ 16 апреля 2009

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

unsigned char A_Function (bit *p_error_flag, bit *p_data_ready_flag)
{
  ...
}
0 голосов
/ 14 апреля 2009

Сначала я попытался бы закодировать его таким образом, чтобы для каждого из этих флагов был только один производитель и только один потребитель. Тогда я бы очистил / установил флаг только при необходимости. Что касается указания побочного эффекта, стандартного заголовка в верхней части функции, стиль doxygen, должно быть достаточно:

    // Function func
    // Does something
    // Consumes ready_flag and  sets error_flag on error.

    int func()
    {
        if (ready_flag)
        {
            //do something then clear the flag
            if (some_error)
                error_flag = x;
            ready_flag = 0;
        }
        //don't mess with the flags outside of their 'scope'
        return 0;
    }

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

0 для ошибки, 1 для неготовности / без ошибок и 2 для готовности / без ошибок (или -1, 0, 1 и т. Д.)

IIRC, стандартный набор команд 8051 не работает с одиночными битами, поэтому использование целого байта для (различных) флагов не должно привести к огромному снижению производительности.

...