将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
导致无限递归。
"修复"
取消operator new(size_t)
中的std::cout
行注释。
编译和运行此更改导致cout
s流到控制台,如预期的那样,但没有"分割错误(核心转储)"消息,也没有.stackdump文件。
这对我来说没有意义。
我不相信引入std::cout
可以解决问题,但我也不能解释为什么它似乎掩盖了它。
迫切想知道这是怎么回事。谢谢你的建议。
(重申一下,为了解决这个问题,内存泄漏和无限递归是有意为之的。这个问题偶然出现在我正在处理的更合适的代码中,代码只是一个MCV示例来演示这个问题。)
在一个真正的Linux机器上,程序sigsegv与两个版本的代码,即有和没有std::cout
。这至少帮助我恢复了一些理智,因为这符合预期。
我将停止积极地调查这个话题,因为它是,首先,只是一个偶然的发现,而我正在朝着其他的东西工作,也因为代码的行为是预期的-即sigsegv有和没有std::cout
-在一个真正的Linux机器上。不过,我将保留这个问题,以防有人最终能给出一个明确的答案。我们仍然有点担心这个问题是否会出现,因为这意味着Cygwin在某些情况下"掩盖"了错误的可靠表示。
代码无论如何都应该在operator new()
中有或没有std::cout
行得到段错误,因为它应该得到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
来调试段故障,查看调用堆栈并找到递归作为根本原因。
有一个合理的猜测为什么std::cout
会影响观察到的结果:它是迄今为止最复杂的调用。通过与printf
的可能同步,替换streambufs等,它可以阻止内联。
内联对于递归函数是一个问题,因为它会在幼稚的编译器中导致堆栈溢出。但是一个好的编译器可以将递归函数转换为迭代函数,这可能会影响堆栈溢出的可见症状。显然,您的示例仍然会耗尽内存,但现在可能是堆内存而不是堆栈内存。
我有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: %dn",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)。使用- 0时,计数器上升到4811。
zsh-user> ./a.exe
....
在printf存在并且输出重定向的情况下,它给出的SIGSEGV如下所示。优化级别没有改变这个测试的行为。
~> ./a.exe > out
zsh: segmentation fault (core dumped) ./a.exe > out
我没有一个解释(我打算调试它下一步)。2年前我也遇到过类似的问题(游戏邦注:当时其他平台上没有人能够复制这款游戏;只有另一个cygwin用户能够复制它;
- 位移操作和位掩码未检测到重复字符
- OpenCV - 带有掩码的absdiff
- 生成前缀位掩码
- 如何从__m64值的 lsb 创建 8 位掩码?
- 如何对无符号长 int 进行位掩码?
- 删除K的背景掩码-意味着Python或C++中的集群/
- 如何在C++中优雅地处理位掩码
- 将uint64_t位掩码转换为 std::布尔数组
- 使输入二进制掩码适应 ITK 网格生成器
- 如何从 getifaddr 读取子网掩码
- 优化从子位掩码生成父位掩码
- 基于模式创建位掩码作为 constexpr
- 使用二进制掩码 C++ ITK 获取感兴趣区域
- C++中的运行时位复制(位掩码)
- 根据 IP 和掩码C++打印所有 IP
- C++设置"blank"或重置 ifstrean (ios) 的异常掩码
- OpenCV 检测带有掩码的斑点
- OPENCV:如何创建多边形形状的掩码
- 递归函数,用于使用位掩码 c++ 显示集合的所有子集
- 将std::cout添加到全局操作符新掩码SIGSEGV中