Обнаружить вызов функции в аргументах printf () - PullRequest
2 голосов
/ 06 декабря 2011

Долгое время у нас была система ведения журнала, которая работала примерно так:

#define LOG( LEVEL, FORMAT, ... ) my_log_function( LEVEL, __FUNCTION__, \
                                         __LINE__, FORMAT, __VA_ARGS__ )

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

Теперь проблема в том, что у этого макроопределения есть два существенных недостатка:

1 / При использовании аргументы, передаваемые в этом макросе, оцениваются , что означает большое снижение производительности, когда ваш код переполнен вызовами LOG ().

Смотрите пример здесь:

LOG( INFO, "The parameters: %s %d %d\n", heavyMethod().name(),     
        heavyMethod2().id(), work_done_in_this_function());

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

  • вызов heavyMethod()
  • вызов name()
  • звонок на heavyMethod2()
  • вызов id()
  • вызов work_done_in_this_function()
  • (наконец) звонок на my_log_function()

Это очень плохо, когда у вас 1000 вызовов LOG ().

Решение простое: возьмите из my_log_function код, который проверяет уровень, и измените определение LOG () следующим образом:

#define LOG( LVL, FMT, ... ) do{ if( level_enabled(LVL) )        \ 
                                   {                             \
                                     my_log_function( LVL, ...); \
                                   }                             \
                               }while(0)

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

2 / Как вы видели в моем примере, последняя вызываемая функция выполняет какую-то работу, которая не была бы выполнена, если бы функция LOG () не была вызвана.
Это часто случается в нашем коде (и я знаю, что это отстой, люди уже потеряли из-за этого пальцы).

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

Здесь вы, ребята, вводите: знаете ли вы простой метод, который бы проверял, действительно ли аргумент функции изменяет что-то?

Проект написан на C ++, и большинство функций, которые "ничего не меняют", помечены как const.

Обратите внимание, что это включает в себя некоторые хитрые вещи, такие как: LOG( INFO, "Number of items: %d\n", _item_number++);, где _item_number является членом класса объекта (таким образом, не увеличивается, если уровень INFO не активирован :-().

TL; DR : НИКОГДА, НИКОГДА не выполняйте работу в printf (). ВСЕГДА делайте это заранее:

// BAD
printf("Number: %d\n",++i);

// GOOD
i++;
printf("Number: %d\n", i);

1 Ответ

2 голосов
/ 06 декабря 2011

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

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

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

Каждый из них применяется путем добавления __attribute__(pure) (или __attribute__(const)) в объявлении метода.К сожалению, я не думаю, что есть способ применить это за вызов , хотя может быть способ использовать указатели на функции.Я обновлю, если найду один.

Ссылка: http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html

РЕДАКТИРОВАТЬ: Как указано здесь , может быть возможно применить это к указателю на функцию, хотяЯ не думаю, что есть способ сделать это без встраивания указателя функции в каждый вызов метода в вашем макросе (который должен знать расположение аргументов каждой функции, во всяком случае) или объявления всех ваших функций как чистых.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...