11.3 Использование gcov с оптимизацией GCC
Если вы планируете использовать gcov для оптимизации кода, сначала необходимо скомпилировать программу с помощью специального параметра GCC ‘--coverage’. Помимо него можно использовать любые другие параметры GCC; однако, если вы хотите убедиться, что была выполнена каждая строка программы, не следует одновременно использовать оптимизацию при компиляции. На некоторых машинах оптимизатор может исключить некоторые простые строки кода, объединив их с другими строками. Например, такой код:
if (a != b) c = 1; else c = 0;
на некоторых машинах может быть скомпилирован в одну инструкцию. В этом случае gcov не сможет подсчитать отдельное количество выполнений для каждой строки, поскольку для каждой строки нет отдельного кода. Поэтому вывод gcov при компиляции программы с оптимизацией выглядит так:
100: 12:if (a != b) 100: 13: c = 1; 100: 14:else 100: 15: c = 0;
В выводе показано, что этот блок кода, объединённый в результате оптимизации, выполнился 100 раз. В некотором смысле этот результат верен, поскольку все четыре строки представлены одной инструкцией. Однако в выводе не указано, сколько раз результат был равен 0, а сколько раз — 1.
Встраиваемые функции могут приводить к неожиданному подсчёту строк. Количество выполнений показывается для исходного кода встраиваемой функции, но отображаемые значения зависят от того, где функция встраивается, или от того, что она вообще не встраивается.
Если функция не встраивается, компилятор должен создать её отдельную копию в любом объектном файле, которому она требуется. Если в fileA.o и fileB.o содержатся отдельные тела одной и той же встраиваемой функции, в них также будут содержаться счётчики покрытия для этой функции. При компоновке fileA.o и fileB.o компоновщик во многих системах выберет одно из этих отдельных тел для всех вызовов функции и удалит или проигнорирует другое. К сожалению, он не удалит счётчики покрытия неиспользуемого тела функции. Поэтому при инструментировании у всех, кроме одного, вариантов использования этой функции будут нулевые значения счётчиков.
Если функция встраивается в нескольких местах, структура блоков в каждом месте может отличаться. Например, в некоторых случаях условие может стать вычислимым во время компиляции. Поскольку покрытие всех вариантов использования встроенной функции будет показано для одних и тех же строк исходного кода, сами значения счётчиков строк могут показаться несогласованными.
В долго работающих приложениях можно использовать средства __gcov_reset и __gcov_dump, чтобы ограничить сбор профиля интересующей областью программы. Вызов __gcov_reset(void) обнулит все счётчики профиля времени выполнения, а вызов __gcov_dump(void) приведёт к записи собранных к этому моменту данных профиля в выходные файлы .gcda. Инструментированные приложения используют статический деструктор с приоритетом 99 для вызова функции __gcov_dump. Таким образом, __gcov_dump выполняется после всех пользовательских статических деструкторов, а также обработчиков, зарегистрированных с помощью atexit.
Если исполняемый файл загружает динамический разделяемый объект с помощью функции dlopen, для сброса всех данных профиля требуется -Wl,--dynamic-list-data.
Библиотека времени выполнения для профилирования сообщает о различных ошибках, связанных с обработкой и сохранением профиля. Сообщения об ошибках выводятся в стандартный поток ошибок или в файл ‘GCOV_ERROR_FILE’, если используется переменная окружения. Чтобы завершать работу сразу после возникновения ошибки, задайте переменную окружения ‘GCOV_EXIT_AT_ERROR’. Это поможет обнаружить конфликт профилей, приводящий к недостоверному профилю.
© Free Software Foundation
Licensed under the GNU Free Documentation License, Version 1.3.
https://gcc.gnu.org/onlinedocs/gcc-15.3.0/gcc/Gcov-and-Optimization.html