Переопределение удаления оператора С++ не всегда используется

У меня есть несколько модульных тестов С++, использующих тест 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 не использует мою переопределенную функцию удаления?


person Dtor    schedule 08.04.2018    source источник
comment
Потому что ptr_ это nullptr?   -  person user202729    schedule 08.04.2018
comment
Хорошая мысль, но нет, иначе у меня не было бы проблем с несоответствием. Я только что снова запустил его в gdb и напечатал ptr_, чтобы убедиться, что он не нулевой.   -  person Dtor    schedule 08.04.2018
comment
Подумайте о том, чтобы иметь минимальный воспроизводимый пример вместо того, чтобы заставлять нас угадывать, какой тип UB вы вызвали где-то еще.   -  person user202729    schedule 08.04.2018
comment
Обязательно опубликуйте минимально воспроизводимый пример .   -  person Jive Dadson    schedule 08.04.2018
comment
А что там в заголовочном файле? Что такое ТЕСТ (макрос?)?   -  person user202729    schedule 08.04.2018
comment
Не рекомендуется выводить в стандартные потоки в глобальных перегрузках операторов new и delete. Буферы, используемые выходными потоками, могут (и часто используют) использовать перегруженные операторы. Результат может быть бесконечно рекурсивным (вызывается operator delete(), который вызывает операторы потока, которые вызывают operator delete(), и т. д.). Пошаговое выполнение рекурсивных функций с помощью отладчика может сбивать с толку (например, разрыв строки может прерываться только при глубоко рекурсивном вызове, а не там, где вы ожидаете).   -  person Peter    schedule 08.04.2018
comment
@ user202729: файл заголовка является частью библиотеки gtest. Его можно скачать онлайн, я не думаю, что было бы конструктивно публиковать весь исходный код gtest.   -  person Dtor    schedule 08.04.2018
comment
@ Питер, да, это хороший момент, поверь мне, я столкнулся с бесконечной рекурсией, делая это. Но с тем, как я печатал, это не вызывает такой тип рекурсии, программа работает до завершения   -  person Dtor    schedule 08.04.2018
comment
@user202729 user202729 Предпосылка проблемы связана с подключением к внешней библиотеке. У меня нет проблем, если я не подключаюсь к внешней библиотеке, но в этом случае у меня также нет ничего полезного. Мне просто не задавать сложных вопросов?   -  person Dtor    schedule 08.04.2018
comment
Хм... вы не можете воспроизвести это без включения библиотеки? Хорошо, тогда это может быть ошибка/особенность/и т.д. с библиотекой. Вы можете оставить вопрос в его текущем состоянии, но (также) может быть лучше задать вопрос на форуме библиотеки.   -  person user202729    schedule 08.04.2018
comment
Я не думаю, что это имеет какое-то отношение к самому гугл-тесту. Я посмотрел на код в google-тесте, который не делает то, что я ожидаю (и разместил этот блок кода в вопросе). Скорее всего, в том, как я его связываю, есть что-то, что заставляет его не использовать мою переопределенную функцию, поэтому я разместил в стеке, надеясь, что кто-то может пролить свет на то, что я неправильно понимаю о связывании/переопределении.   -  person Dtor    schedule 08.04.2018
comment
Если проблема связана с подключением к внешней библиотеке, объяснение может заключаться в том, что библиотека скомпилирована/слинкована и использует свои собственные (например, по умолчанию для вашей реализации) операторы new и delete. Хотя стандарт требует, чтобы все модули компиляции в программе использовали одни и те же версии этих функций (т. е. то, что вы делаете, теоретически не является проблемой), некоторые реализации менее умны в этом отношении, когда используются библиотеки ссылок. Возможно, вы захотите проверить, есть ли у mingw такие проблемы (я не знаю навскидку).   -  person Peter    schedule 08.04.2018
comment
@Peter, спасибо, что указали мне в этом направлении, я смог найти и убедиться, что в mingw действительно есть ошибка, и смог найти обходной путь.   -  person Dtor    schedule 09.04.2018


Ответы (1)


Похоже, это ошибка в MinGW: ошибка MinGW #634

Обходной путь заключается в том, чтобы связать статическую версию libstdc++ вместо того, чтобы позволить ей связать динамическую библиотеку. Не самое идеальное решение, но оно достаточно хорошо для моих модульных тестов и позволяет мне корректно переопределять.

Я изменил команду компиляции/ссылки на следующее, чтобы сделать это:

/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 /cygdrive/c/cygwin64/lib/gcc/x86_64-w64-mingw32/6.4.0/libstdc++.a

Большое спасибо Питеру за то, что он направил меня на правильный путь к поиску этого.

person Dtor    schedule 09.04.2018