10.3 Использование gcov с оптимизацией GCC ¶
Если вы планируете использовать gcov для оптимизации кода, необходимо сначала скомпилировать программу с помощью специального параметра GCC ‘--coverage’. Помимо этого, можно использовать любые другие параметры GCC; но если вы хотите убедиться, что каждая строка вашей программы была выполнена, не следует компилировать с оптимизацией. На некоторых машинах оптимизатор может устранить некоторые простые строки кода, объединив их с другими строками.
if (a != b) c = 1; else c = 0;
Например, код такого вида:
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.
Функции, которые могут быть встроены (inlineable), могут создавать неожиданные подсчёты строк. Подсчёты строк показаны для исходного кода встроенной функции, но то, что отображается, зависит от того, где функция встраивается или не встраивается вообще.
Если функция не встраивается, компилятор должен сгенерировать внестрочную (out of line) копию функции в любом объектном файле, который её требует. Если 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-10.5.0/gcc/Gcov-and-Optimization.html