У меня есть несколько модульных тестов C++, использующих тест Google. Собрал некоторый код для переопределения операторов new/delete для проверки утечек в модульных тестах. Однако есть проблема. Некоторые из новых/удаляемых тестов Google используют мои переопределенные методы, а некоторые нет, поэтому я получаю ложные ошибки в коде отслеживания - иногда вижу, что память была утеряна, хотя она действительно была удалена, а иногда вижу, что malloc возвращает
Вот мои минимальные новые/удаленные переопределения (просто печатает адреса для ручной проверки):
void * operator new(size_t size)
{
void * addr = malloc(size);
std::cout << " tracking create: " << addr << "(size " << size << ")" << std::endl;
return addr;
}
void * operator new[](size_t size)
{
void * addr = malloc(size);
std::cout << " tracking create: " << addr << "(size " << size << ")" << std::endl;
return addr;
}
void operator delete(void * addr) noexcept
{
std::cout << " tracking delete: " << addr << std::endl;
free(addr);
}
void operator delete[](void * addr) noexcept
{
std::cout << " tracking delete: " << addr << std::endl;
free(addr);
}
А вот тестовая строка Google, которая НЕ проходит через мое переопределенное удаление (gtest-port.h):
void reset(T* p = NULL) {
if (p != ptr_) {
if (IsTrue(sizeof(T) > 0)) { // Makes sure T is a complete type.
delete ptr_;
}
ptr_ = p;
}
}
Когда я прерываю строку delete ptr_
в gdb, затем шаг, он переходит непосредственно к строке ptr_ = p
, так что нет ничего другого, переопределяющего это удаление.
Я создаю gtest в виде файла архива и подключаю его при создании модульных тестов. В случае, если это имеет значение: я занимаюсь сборкой Windows с помощью mingw, используя cygwin.
Вот минимальный пример, 2 файла min.cpp и minmain.cpp. Вот мин.cpp:
#include <iostream>
#include <string>
// Overload the new/delete operators to check for memory errors
void * operator new(size_t size)
{
void * addr = malloc(size);
std::cout << " tracking create: " << addr << "(size " << size << ")" << std::endl;
return addr;
}
void * operator new[](size_t size)
{
void * addr = malloc(size);
std::cout << " tracking create: " << addr << "(size " << size << ")" << std::endl;
return addr;
}
void operator delete(void * addr) noexcept
{
std::cout << " tracking delete: " << addr << std::endl;
free(addr);
}
void operator delete[](void * addr) noexcept
{
std::cout << " tracking delete: " << addr << std::endl;
free(addr);
}
minmain.cpp:
#include "gtest/gtest.h"
TEST(MinTest, MinimalTest)
{
int test = 5;
test++;
test++;
test++;
ASSERT_EQ(test, 8);
}
int main(int argc, char *argv[])
{
char* t = new char();
t[0] = 't'; std::cout << "t is " << t[0] << std::endl;
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
скомпилировано с:
/bin/x86_64-w64-mingw32-g++.exe -std=c++11 -D_USE_MATH_DEFINES -g -Wall -I../third_party/googletest-1.8.0/googletest/include -c min.cpp -o min.o
, чтобы создать min.o, затем скомпилировать main и связать все вместе с помощью:
/bin/x86_64-w64-mingw32-g++.exe -std=c++11 -D_USE_MATH_DEFINES -g -Wall -I../third_party/googletest-1.8.0/googletest/include -o minmain minmain.cpp min.o ../third_party/googletest-1.8.0/googletest/make/gtest_main.a
Используя gtest версии 1.8.0, разбейте gtest-port.h:1145, чтобы перейти к строке delete ptr_
, затем выполните шаг
Вот пример вывода при выполнении приведенного выше примера (первые несколько строк вывода):
tracking create: 0x30e4c0(size 392)
tracking create: 0xa477e0(size 392)
tracking create: 0xa477e0(size 392)
tracking create: 0xa477e0(size 392)
tracking create: 0xa477e0(size 392)
tracking create: 0xa47b80(size 28)
tracking delete: 0xa47b80
Тот факт, что я получаю отслеживаемые создания по тому же адресу без отслеживаемых удалений между ними, является проблемой, потому что между ними были удаления, позволяющие снова выделить тот же адрес, но эти удаления не прошли через мой переопределенный оператор удаления .
Почему эта строка delete ptr_;
в gtest не использует мою переопределенную функцию удаления?
Потому что
ptr_
равноnullptr
?Хорошая мысль, но нет, иначе у меня не было бы проблем с несоответствием. Я просто снова запустил его в gdb и напечатал
ptr_
, чтобы убедиться, что он не нулевой.Подумайте о том, чтобы иметь минимальный воспроизводимый пример вместо того, чтобы заставлять нас гадать, какой тип UB вы вызвали где-то еще.
Абсолютно, опубликуйте минимальный воспроизводимый пример.
А что там в заголовочном файле? Что такое "ТЕСТ" (макрос?)?
Не рекомендуется выводить в стандартные потоки в глобальных перегрузках операторов
new
иdelete
. Буферы, используемые выходными потоками, могут (и часто используют) использовать перегруженные операторы. Результат может быть бесконечно рекурсивным (вызванныйoperator delete()
, который вызывает операторы потока, которые вызываютoperator delete()
и т. д.). Пошаговое выполнение рекурсивных функций с помощью отладчика может сбивать с толку (например, разрыв строки может прерываться только при глубоко рекурсивном вызове, а не там, где вы ожидаете).@ user202729: файл заголовка является частью библиотеки gtest. Его можно скачать онлайн, я не думаю, что было бы конструктивно публиковать весь исходный код gtest.
@ Питер, да, это хороший момент, поверь мне, я столкнулся с бесконечной рекурсией, делая это. Но с тем, как я печатал, это не вызывает такой тип рекурсии, программа работает до завершения
@user202729 user202729 Предпосылка проблемы связана с подключением к внешней библиотеке. У меня нет проблем, если я не подключаюсь к внешней библиотеке, но в этом случае у меня также нет ничего полезного. Мне просто не задавать сложных вопросов?
Хм... вы не можете воспроизвести это без включения библиотеки? Хорошо, тогда это может быть ошибка/особенность/и т.д. с библиотекой. Вы можете оставить вопрос в его текущем состоянии, но (также) может быть лучше задать вопрос на форуме библиотеки.
Я не думаю, что это имеет какое-то отношение к самому гугл-тесту. Я посмотрел на код в google-тесте, который не делает то, что я ожидаю (и разместил этот блок кода в вопросе). Скорее всего, в том, как я его связываю, есть что-то, что заставляет его не использовать мою переопределенную функцию, поэтому я разместил в стеке, надеясь, что кто-то может пролить свет на то, что я неправильно понимаю о связывании/переопределении.
Если проблема связана с подключением к внешней библиотеке, объяснение может заключаться в том, что библиотека скомпилирована/связана и использует свои собственные (например, по умолчанию для вашей реализации) операторы
new
иdelete
. Хотя стандарт требует, чтобы все модули компиляции в программе использовали одни и те же версии этих функций (т. е. то, что вы делаете, теоретически не является проблемой), некоторые реализации менее умны в этом отношении, когда используются библиотеки ссылок. Возможно, вы захотите проверить, есть ли у mingw такие проблемы (я не знаю навскидку).@Peter, спасибо, что указали мне в этом направлении, я смог найти и убедиться, что в mingw действительно есть ошибка, и смог найти обходной путь.