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

avatar
Dtor
8 апреля 2018 в 02:16
353
1
1

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

Источник
user202729
8 апреля 2018 в 02:21
0

Потому что ptr_ равно nullptr?

Dtor
8 апреля 2018 в 02:23
0

Хорошая мысль, но нет, иначе у меня не было бы проблем с несоответствием. Я просто снова запустил его в gdb и напечатал ptr_, чтобы убедиться, что он не нулевой.

user202729
8 апреля 2018 в 02:25
0

Подумайте о том, чтобы иметь минимальный воспроизводимый пример вместо того, чтобы заставлять нас гадать, какой тип UB вы вызвали где-то еще.

Jive Dadson
8 апреля 2018 в 02:27
0

Абсолютно, опубликуйте минимальный воспроизводимый пример.

user202729
8 апреля 2018 в 03:03
0

А что там в заголовочном файле? Что такое "ТЕСТ" (макрос?)?

Peter
8 апреля 2018 в 03:04
0

Не рекомендуется выводить в стандартные потоки в глобальных перегрузках операторов new и delete. Буферы, используемые выходными потоками, могут (и часто используют) использовать перегруженные операторы. Результат может быть бесконечно рекурсивным (вызванный operator delete(), который вызывает операторы потока, которые вызывают operator delete() и т. д.). Пошаговое выполнение рекурсивных функций с помощью отладчика может сбивать с толку (например, разрыв строки может прерываться только при глубоко рекурсивном вызове, а не там, где вы ожидаете).

Dtor
8 апреля 2018 в 03:06
0

@ user202729: файл заголовка является частью библиотеки gtest. Его можно скачать онлайн, я не думаю, что было бы конструктивно публиковать весь исходный код gtest.

Dtor
8 апреля 2018 в 03:07
0

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

Dtor
8 апреля 2018 в 03:08
0

@user202729 user202729 Предпосылка проблемы связана с подключением к внешней библиотеке. У меня нет проблем, если я не подключаюсь к внешней библиотеке, но в этом случае у меня также нет ничего полезного. Мне просто не задавать сложных вопросов?

user202729
8 апреля 2018 в 03:11
0

Хм... вы не можете воспроизвести это без включения библиотеки? Хорошо, тогда это может быть ошибка/особенность/и т.д. с библиотекой. Вы можете оставить вопрос в его текущем состоянии, но (также) может быть лучше задать вопрос на форуме библиотеки.

Dtor
8 апреля 2018 в 03:15
0

Я не думаю, что это имеет какое-то отношение к самому гугл-тесту. Я посмотрел на код в google-тесте, который не делает то, что я ожидаю (и разместил этот блок кода в вопросе). Скорее всего, в том, как я его связываю, есть что-то, что заставляет его не использовать мою переопределенную функцию, поэтому я разместил в стеке, надеясь, что кто-то может пролить свет на то, что я неправильно понимаю о связывании/переопределении.

Peter
8 апреля 2018 в 03:39
1

Если проблема связана с подключением к внешней библиотеке, объяснение может заключаться в том, что библиотека скомпилирована/связана и использует свои собственные (например, по умолчанию для вашей реализации) операторы new и delete. Хотя стандарт требует, чтобы все модули компиляции в программе использовали одни и те же версии этих функций (т. е. то, что вы делаете, теоретически не является проблемой), некоторые реализации менее умны в этом отношении, когда используются библиотеки ссылок. Возможно, вы захотите проверить, есть ли у mingw такие проблемы (я не знаю навскидку).

Dtor
9 апреля 2018 в 02:11
0

@Peter, спасибо, что указали мне в этом направлении, я смог найти и убедиться, что в mingw действительно есть ошибка, и смог найти обходной путь.

Ответы (1)

avatar
Dtor
9 апреля 2018 в 02:09
1

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

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

Я изменил с помощью команды compile/link следующее:

/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

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