Search code examples
c++segmentation-faultnew-operatorcoredump

Adding std::cout to global operator new masks SIGSEGV


This question is part of my exercise in learning about overriding global operator new. I need to ask the community for help understanding runtime behavior, because I'm at a loss trying to understand this.

This code intentionally produces memory leaks and a SIGSEGV

The Code

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;
}

Compilation and Error

Note: environment is Cywin, therefore a.exe instead of a.out below.

>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)
>

This error is expected: note that the overridden operator new eventually calls std::set::insert(...), which itself uses new leading to infinite recursion.

The "Fix"

Uncomment the std::cout line in operator new(size_t).

Compiling and running with this change results in a stream of couts to the console, as expected, but no "Segmentation fault (core dumped)" message anywhere, and no .stackdump file.

This makes no sense to me.

I do not believe that introducing the std::cout fixes the problem, but neither can I explain why it seems to be masking it.

Desperate to understand what's at play here. Thank you for any insight.

(Just to reiterate, the memory leak and infinite recursion are intentional for the purpose of this question. This issue arose incidentally to more proper code I was working on, and the code is just a MCV example to demonstrate the issue.)

Update

On a true Linux box, the program SIGSEGVs with both versions of the code, i.e. with and without the std::cout. This at least helps me reclaim some sanity, as this conforms to expectation.

Update

I'm going to stop actively investigating this topic since it was, to begin with, just an incidental discovery while I was working towards something else, and also because the code behaves as expected - i.e. SIGSEGVs both with and without the std::cout - on a true Linux box. I will leave the question open, though, in case someone can eventually offer a definitive answer. It's still slightly concerning that this issue manifests at all, because it means Cygwin "masks" the reliable presentation of errors under certain circumstances.


Solution

  • There's a reasonable guess why std::cout affects the observed results: it is by far the most complex call. With a possible synchronization with printf, replaced streambufs, etcetera, it can block inlining.

    Inlining is a problem for recursive functions, of course, because it would cause a stack overflow in a naive compiler. But a decent compiler can turn that recursive function into an iterative one, and this may affect the visible symptoms of a stack overflow. Your example obviously still is going to run out of memory, but that could now be heap memory instead of stack memory.