Этот вопрос является частью моего упражнения по переопределению глобального оператора new. Мне нужно попросить сообщество помочь понять поведение во время выполнения, потому что я не понимаю, что это такое.
Этот код намеренно вызывает утечку памяти и сообщение SIGSEGV
Код
main.cpp
:
#include <iostream>
#include <functional>
#include <new>
#include <set>
#include <string>
#include <memory>
namespace ns
{
class Foo
{
public:
Foo()
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
virtual ~Foo()
{
if (!mSet.empty()) { std::cout << "You have unfreed heap allocations!" << std::endl; }
else { std::cout << "Good job! No unfreed heap allocations!" << std::endl; }
}
void Add(void* p) { mSet.insert(p); }
void Delete(void* p) { mSet.erase(p); }
protected:
std::set< void*, std::less<void*> > mSet;
};
Foo gFoo;
}
void* operator new(size_t size)
{
// std::cout << "In overridden operator new!" << std::endl;
void* p = malloc(size);
ns::gFoo.Add(p);
return p;
}
int main(int argc, char* argv[])
{
int* p1 = new int(5);
int* p2 = new int(6);
return 0;
}
Компиляция и ошибка
Примечание: среда - Cywin, поэтому ниже a.exe
вместо a.out
.
>g++ --version
g++ (GCC) 5.4.0
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
>g++ -std=c++14 -g main.cpp
>
>./a.exe
ns::Foo::Foo()
Segmentation fault (core dumped)
>
Эта ошибка ожидаема: обратите внимание, что переопределенный operator new
в конечном итоге вызывает std :: set :: insert (...), который сам использует new
, что приводит к бесконечной рекурсии.
«Исправление»
Раскомментируйте строку std::cout
в operator new(size_t)
.
Компиляция и запуск с этим изменением приводит к потоку cout
на консоль, как и ожидалось, но нигде не выводится сообщение «Ошибка сегментации (дамп ядра)» и нет файла .stackdump.
Для меня это не имеет смысла.
Я не верю, что введение std::cout
устраняет проблему, но я также не могу объяснить, почему он, кажется, маскирует ее.
Отчаянно пытаясь понять, что здесь происходит. Спасибо за понимание.
( Повторюсь, утечка памяти и бесконечная рекурсия преднамерены для цели этого вопроса. Эта проблема возникла случайно из-за более правильного кода, над которым я работал, и этот код является просто примером MCV для демонстрации проблемы. < / em>)
Обновить
На настоящем Linux-компьютере программа SIGSEGV использует обе версии кода, то есть с std::cout
и без него. Это, по крайней мере, помогает мне восстановить разум, поскольку это соответствует ожиданиям.
Обновить
Я собираюсь прекратить активно исследовать эту тему, поскольку это было для начала всего лишь случайным открытием, когда я работал над чем-то другим, а также потому, что код ведет себя так, как ожидалось, то есть SIGSEGV как с {{X0}, так и без него. } - на настоящем Linux-компьютере. Однако я оставлю вопрос открытым, на случай, если кто-то в конце концов сможет предложить окончательный ответ. По-прежнему вызывает некоторое беспокойство то, что эта проблема вообще проявляется, потому что это означает, что Cygwin «маскирует» надежное представление ошибок при определенных обстоятельствах.
3 ответа
Есть разумное предположение, почему std::cout
влияет на наблюдаемые результаты: это, безусловно, самый сложный вызов. При возможной синхронизации с printf
, замене streambufs и т.д. он может блокировать встраивание.
Встраивание - это, конечно, проблема для рекурсивных функций, потому что в наивном компиляторе это может вызвать переполнение стека. Но приличный компилятор может превратить эту рекурсивную функцию в итеративную, и это может повлиять на видимые симптомы переполнения стека. В вашем примере, очевидно, по-прежнему будет не хватать памяти, но теперь это может быть память кучи, а не память стека.
Код должен в любом случае получить ошибку сегмента с или без строки std::cout
в вашем operator new()
, потому что он должен получать рекурсивные вызовы std::set::insert()
и operator new()
и в конечном итоге происходит переполнение стека.
- Ваши
operator new
звонкиns::gFoo.Add(p)
Foo::Add()
звонковstd::set::insert()
insert()
выделит новую память и вызоветoperator new()
- Теперь это рекурсия.
Но вы указываете, что удаление std::cout
решает проблему.
Поэтому я предполагаю, что в вашей среде std::std::insert()
не выделяет динамически память для небольших объектов или для небольшого размера, тогда он не вызывает operator new()
, что позволяет избежать рекурсии.
В то время как std::cout << "In overridden operator new!"
будет выделять память, он вызывает operator new()
и получает рекурсию.
В любом случае, вы можете использовать gdb
для отладки ошибки сегмента, увидеть стек вызовов и найти рекурсию как основную причину.
У меня Cygwin. Я создал минимизированный тест и подтвердил результаты OP.
#include <iostream>
#include <new>
#include <set>
#include <memory>
#include <cstdlib>
#include <cstdio>
std::set< void*, std::less<void*> > mSet;
unsigned long long counter=0;
void* operator new(size_t size)
{
counter++;
printf("counter: %d\n",counter);
void* p = malloc(size);
mSet.insert(p);
return p;
}
int main(int argc, char* argv[])
{
int* p1 = new int(5);
std::cerr << "Exiting=" << counter << std::endl;
}
Если printf закомментирован, он выдал SIGSEGV за несколько секунд следующим образом:
~> ./a.exe
zsh: segmentation fault (core dumped) ./a.exe
При наличии printf счетчик достигает 4811, не получает SIGSEGV, но выходит тихо, не доходя до конца main, как показано ниже. Когда я оптимизирую его с помощью -O3, происходит то же самое (но теперь со значением счетчика 64936). С -O0 счетчик также увеличился до 4811.
zsh-user> ./a.exe
....
При наличии printf и перенаправлении вывода он выдал SIGSEGV следующим образом. Уровни оптимизации не изменили поведение этого теста.
~> ./a.exe > out
zsh: segmentation fault (core dumped) ./a.exe > out
У меня нет объяснения (я планировал отладить его дальше). У меня была похожая странная проблема 2 года назад (где никто другой на других платформах не смог воспроизвести его; только один пользователь cygwin смог воспроизвести его; эта проблема исчезла после полной переустановки cygwin).
Похожие вопросы
Связанные вопросы
Новые вопросы
c++
C++ — это язык программирования общего назначения. Изначально он разрабатывался как расширение C и имел аналогичный синтаксис, но теперь это совершенно другой язык. Используйте этот тег для вопросов о коде, который будет скомпилирован с помощью компилятора C++. Используйте тег версии для вопросов, связанных с конкретной стандартной версией [C++11], [C++14], [C++17], [C++20] или [C++23]. и т.д.