Ответ 1
Несмотря на многочисленные бесполезные ответы, полученные этим вопросом, я думаю, что он имеет много преимуществ в контексте устаревшей базы кода.
Представьте себе, что за многие годы накопилось много утверждений, но из-за отсутствия привычки к созданию/тестированию с помощью NDEBUG некоторые побочные эффекты просочились в утверждения, и теперь вы не смеете больше отключать утверждения.
Вы можете включить NDEBUG и обнаружить некоторые отказы в тестировании в вашем тестовом наборе, но совершенно не сложно связать неудачу теста с "эффективным" утверждением, потому что это может быть очень далеко от того места, где вы обнаруживаете ошибку. И даже набор тестов с хорошим охватом не может считаться полным.
Вы можете провести обзор кода всех утверждений в коде, но это потенциально большая работа и склонна к человеческой ошибке. Было бы намного лучше, если бы какой-либо статический анализ уже мог устранить все утверждения, в которых он может доказать, что никаких побочных эффектов не возникает, и вам нужно только исследовать те случаи, когда их отсутствие не гарантируется.
Вот как вы можете использовать оптимизатор своего компилятора для проведения такого статического анализа. Предположим, что вы решили заменить определение макроса assert
на:
extern int not_supposed_to_survive;
#define assert(expr) ((void)(not_supposed_to_survive || (expr)))
Если expr
имеет какой-либо побочный эффект, выполнение эффекта зависит от значения глобальной переменной not_supposed_to_survive
. Но если expr
не имеет никакого побочного эффекта, значение глобальной переменной не имеет значения (обратите внимание, что результат expr
отбрасывается). Хороший оптимизатор знает это и устранит нагрузку глобальной переменной not_supposed_to_survive
, следовательно, имя переменной.
Если наша программа не содержит определения символа not_supposed_to_survive
, мы получим ошибку связи, когда загрузка не будет устранена, и мы можем использовать ее для обнаружения потенциально эффективного утверждения.
например. с gcc 4.8:
int g;
int foo() { return ++g; }
int main() {
assert(foo());
return 0;
}
gcc -O2 assert_effect.c
/tmp/ccunynya.o: In function `main':
assert_effect.c:(.text.startup+0x2): undefined reference to `not_supposed_to_survive'
collect2: error: ld returned 1 exit status
Компилятор помог мне найти сомнительное утверждение! С другой стороны, если я заменил ++g
на g+1
, ошибка ссылки исчезнет, и мне не нужно ее расследовать. В самом деле, это утверждение гарантировано безвредно.
Конечно, концепция доказуемого побочного эффекта ограничена тем, что оптимизатор "может видеть". Для более точного анализа я бы рекомендовал использовать оптимизацию времени соединения (gcc -flto
) для анализа блоков компиляции.
Обновление: Я применил это на реальной базе кода С++, используя gcc 5.3. Чтобы использовать оптимизацию времени соединения, вы по существу используете gcc -flto -g
в качестве компилятора/компоновщика (параметр -g
в компиляторе/компоновщике для получения ссылки на ссылки на ошибки ссылок) и gcc-ar
и gcc-ranlib
в качестве архиватора/индексатора для любых статических библиотек.
Эта настройка может значительно уменьшить количество утверждений, которые я должен был исследовать. С минимальными трудовыми ресурсами я смог получить утверждения чистыми. Ложные срабатывания, которые я все еще должен был отключить вручную, были вызваны:
- Виртуальные вызовы функций
- Нетривиальные петли/рекурсии (где оптимизатор не может доказать, что они конечны)
Кроме того, я бы также получил некоторые утверждения, которые действительно содержали побочные эффекты, но они безвредны или несущественны, например:
- Функции, содержащие записи журнала
- Функции, которые кэшируют их результат (ы)