将std :: cout添加到全局运算符新掩码SIGSEGV

时间:2016-09-06 04:48:45

标签: c++ new-operator core sigsegv

这个问题是我学习重写全球运营商新课程的一部分。我需要请求社区帮助了解运行时行为,因为我不知道如何理解这一点。

此代码故意产生内存泄漏和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导致无限递归。

&#34;修复&#34;

取消注释std::cout中的operator new(size_t)行。

使用此更改进行编译和运行会导致cout流到控制台,正如预期的那样,但没有&#34;分段错误(核心转储)&#34;消息在任何地方,没有.stackdump文件。

这对我没有意义。

相信引入std::cout会解决问题,但我也无法解释为什么它似乎掩盖了它。

迫切希望了解这里发挥作用的是什么。感谢您的任何见解。

重申一下,内存泄漏和无限递归都是故意出现在这个问题的目的。这个问题偶然发生在我正在研究的更合适的代码上,代码只是一个MCV示例来演示问题。

更新

在一个真正的Linux机器上,程序SIGSEGVs包含两个版本的代码,即有和没有std::cout。这至少有助于我恢复一些理智,因为这符合期望。

更新

我将停止积极调查此主题,因为它开始时只是偶然发现,而我正在努力实现其他目标,同时也因为代码的行为符合预期 - 即SIGSEGV既有和无std::cout - 在真正的Linux机器上。不过,我会将问题保持开放,万一有人最终可以提供明确的答案。它仍然有点担心这个问题显而易见,因为它意味着Cygwin&#34;掩盖&#34;在某些情况下可靠地呈现错误。

3 个答案:

答案 0 :(得分:3)

代码应该在您的std::cout中使用或不使用operator new()行来获取段错误,因为它应该获得std::set::insert()和{{1}的递归调用最终得到堆栈溢出。

  1. 您的operator new()来电operator new
  2. ns::gFoo.Add(p)来电Foo::Add()
  3. std::set::insert()将分配新内存并调用insert()
  4. 现在它得到了递归。
  5. 但是您表示删除operator new()可以解决问题。

    所以我猜想在您的环境中,std::cout不会为小对象或小尺寸动态分配内存,因此它不会调用std::std::insert(),因此可以避免递归。

    虽然operator new()将分配内存,但它会调用std::cout << "In overridden operator new!"并获取递归。

    无论如何,您可以使用operator new()调试段错误,查看调用堆栈并找到递归作为根本原因。

答案 1 :(得分:2)

可以合理地猜测为什么std::cout会影响观察到的结果:它是迄今为止最复杂的调用。通过与printf的可能同步,替换了streambufs等,它可以阻止内联。

内联是递归函数的一个问题,当然,因为它会在一个天真的编译器中导致堆栈溢出。但是一个不错的编译器可以将该递归函数转换为迭代函数,这可能会影响堆栈溢出的可见症状。你的例子显然仍然会耗尽内存,但现在可能是堆内存而不是堆栈内存。

答案 2 :(得分:0)

我有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

我没有解释(我打算接下来调试它)。我有a simillar strange problem,2年前(其他平台上没有其他人能够重现它;只有一个其他cygwin用户能够重现它;这个问题在完全重新安装cygwin后就消失了。)