将 std::cout 添加到全局运算符新掩码 SIGSEGV
Adding std::cout to global operator new masks SIGSEGV
这个问题是我学习重写全局运算符 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
导致无限递归。
"Fix"
取消注释 operator new(size_t)
中的 std::cout
行。
编译和 运行 此更改会导致 cout
流到控制台,正如预期的那样,但任何地方都没有 "Segmentation fault (core dumped)" 消息,也没有 .stackdump 文件。
这对我来说毫无意义。
我不相信引入std::cout
可以解决问题,但我也无法解释为什么它似乎掩盖了它。
迫切想了解这里发生了什么。感谢您的任何见解。
(重申一下,内存泄漏和无限递归是为了这个问题的目的而故意的。这个问题偶然出现在我正在处理的更合适的代码中,代码只是一个 MCV演示问题的示例。)
更新
在真正的 Linux 框上,程序 SIGSEGV 具有两个版本的代码,即有和没有 std::cout
。这至少帮助我恢复了一些理智,因为这符合预期。
更新
我将停止积极研究这个主题,因为它首先只是我在做其他事情时的一个偶然发现,而且因为代码的行为符合预期——即 SIGSEGV 有和没有std::cout
- 在真正的 Linux 框上。不过,我会保留这个问题,以防万一有人最终可以提供明确的答案。这个问题的出现仍然有点令人担忧,因为这意味着 Cygwin "masks" 在某些情况下可靠地呈现错误。
代码 应该 在您的 operator new()
中有或没有 std::cout
行无论如何都会出现段错误,因为它应该得到 [=12= 的递归调用] 和 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
我没有解释(我打算接下来调试它)。我有 a simillar strange problem,2 年前(其他平台上没有其他人能够重现它;只有一个其他 cygwin 用户能够重现它;完全重新安装 cygwin 后这个问题就消失了)。
可以合理猜测 std::cout
影响观察结果的原因:这是迄今为止最复杂的调用。通过与 printf
的可能同步、替换流缓冲等,它可以阻止内联。
内联对于递归函数来说当然是个问题,因为它会在天真的编译器中导致堆栈溢出。但是一个体面的编译器可以将该递归函数转换为迭代函数,这可能会影响堆栈溢出的可见症状。您的示例显然仍然 运行 内存不足,但现在可能是堆内存而不是堆栈内存。
这个问题是我学习重写全局运算符 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
导致无限递归。
"Fix"
取消注释 operator new(size_t)
中的 std::cout
行。
编译和 运行 此更改会导致 cout
流到控制台,正如预期的那样,但任何地方都没有 "Segmentation fault (core dumped)" 消息,也没有 .stackdump 文件。
这对我来说毫无意义。
我不相信引入std::cout
可以解决问题,但我也无法解释为什么它似乎掩盖了它。
迫切想了解这里发生了什么。感谢您的任何见解。
(重申一下,内存泄漏和无限递归是为了这个问题的目的而故意的。这个问题偶然出现在我正在处理的更合适的代码中,代码只是一个 MCV演示问题的示例。)
更新
在真正的 Linux 框上,程序 SIGSEGV 具有两个版本的代码,即有和没有 std::cout
。这至少帮助我恢复了一些理智,因为这符合预期。
更新
我将停止积极研究这个主题,因为它首先只是我在做其他事情时的一个偶然发现,而且因为代码的行为符合预期——即 SIGSEGV 有和没有std::cout
- 在真正的 Linux 框上。不过,我会保留这个问题,以防万一有人最终可以提供明确的答案。这个问题的出现仍然有点令人担忧,因为这意味着 Cygwin "masks" 在某些情况下可靠地呈现错误。
代码 应该 在您的 operator new()
中有或没有 std::cout
行无论如何都会出现段错误,因为它应该得到 [=12= 的递归调用] 和 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
我没有解释(我打算接下来调试它)。我有 a simillar strange problem,2 年前(其他平台上没有其他人能够重现它;只有一个其他 cygwin 用户能够重现它;完全重新安装 cygwin 后这个问题就消失了)。
可以合理猜测 std::cout
影响观察结果的原因:这是迄今为止最复杂的调用。通过与 printf
的可能同步、替换流缓冲等,它可以阻止内联。
内联对于递归函数来说当然是个问题,因为它会在天真的编译器中导致堆栈溢出。但是一个体面的编译器可以将该递归函数转换为迭代函数,这可能会影响堆栈溢出的可见症状。您的示例显然仍然 运行 内存不足,但现在可能是堆内存而不是堆栈内存。