Этот вопрос является частью моего упражнения по переопределению глобального оператора 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 «маскирует» надежное представление ошибок при определенных обстоятельствах.

1
StoneThrow 6 Сен 2016 в 07:48

3 ответа

Лучший ответ

Есть разумное предположение, почему std::cout влияет на наблюдаемые результаты: это, безусловно, самый сложный вызов. При возможной синхронизации с printf, замене streambufs и т.д. он может блокировать встраивание.

Встраивание - это, конечно, проблема для рекурсивных функций, потому что в наивном компиляторе это может вызвать переполнение стека. Но приличный компилятор может превратить эту рекурсивную функцию в итеративную, и это может повлиять на видимые симптомы переполнения стека. В вашем примере, очевидно, по-прежнему будет не хватать памяти, но теперь это может быть память кучи, а не память стека.

2
MSalters 6 Сен 2016 в 09:37

Код должен в любом случае получить ошибку сегмента с или без строки std::cout в вашем operator new(), потому что он должен получать рекурсивные вызовы std::set::insert() и operator new() и в конечном итоге происходит переполнение стека.

  1. Ваши operator new звонки ns::gFoo.Add(p)
  2. Foo::Add() звонков std::set::insert()
  3. insert() выделит новую память и вызовет operator new()
  4. Теперь это рекурсия.

Но вы указываете, что удаление std::cout решает проблему.

Поэтому я предполагаю, что в вашей среде std::std::insert() не выделяет динамически память для небольших объектов или для небольшого размера, тогда он не вызывает operator new(), что позволяет избежать рекурсии.

В то время как std::cout << "In overridden operator new!" будет выделять память, он вызывает operator new() и получает рекурсию.

В любом случае, вы можете использовать gdb для отладки ошибки сегмента, увидеть стек вызовов и найти рекурсию как основную причину.

3
Mine 6 Сен 2016 в 06:15

У меня 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).

0
Community 23 Май 2017 в 10:33