在思考问题之前进行快速思考实验。想象有人正在实现std :: malloc(例如,JEMalloc或TCMalloc之一)。他们需要的非常基本的功能之一就是能够知道一旦执行进入std :: malloc的实现,程序就不会回调到malloc中。
例如
void* malloc(...) {
auto lck = std::unique_lock{malloc_mutex};
// .. memory allocation business logic
}
现在,如果在锁和分配的业务逻辑之间存在一个信号,则如果信号处理程序回调回std :: malloc,我们可以死锁。它并非设计为可重入的,C ++标准要求在std :: signal注册的信号处理程序不得回调操作符new(可能可以回调malloc,因此需要用户定义的信号如果要在语言的所有实现中将其视为可移植的,则处理程序不会回调malloc。
最新版本的标准中的§[support.signal]p3
概述了此要求
- 除非其中包括以下之一,否则评估是信号安全的:
对任何标准库函数的调用,但无锁无原子操作和明确标识为信号安全的函数除外。 [注意: 这隐式排除了依赖于库提供的内存分配器的new和delete表达式的使用。 —结尾说明]
但是,C ++标准似乎没有说明如何为执行线程实现函数堆栈(请参见此问题:C++ threads stack address range),这意味着std :: malloc的实现中的函数分派可能会调用operator new
(如果程序是用分段堆栈编译的)。
在这种情况下,如何实现std::malloc
之类的功能?如果确实,C ++标准不提供此类保证,那怎么办?我们如何知道常规函数的实现要经过常规堆栈分配过程(堆栈指针增加)?哪个标准(例如ABI,编译器,POSIX)涵盖了这一点?
答案 0 :(得分:2)
要求实现对它的堆栈帧使用信号安全分配器。这是由于允许在信号处理程序中进行函数调用(对非库函数)。该实现可以使用malloc
或operator new
,但前提是这些分配器本身是信号安全的。
答案 1 :(得分:0)
根据C ++标准的逻辑,将实现视为一个整体。特别地,实现的任何部分都可以承担关于实现的任何其他部分的任何事情。
对于这个问题,这意味着std::malloc
和信号处理程序可能会假设彼此有关。一些实现可能会确定其std::malloc
实现是异步安全的,而其他实现可能会决定不是。但是可能还有无数其他假设-对齐,连续性,自由地址的回收等。由于这是实现的全部内部内容,因此没有标准对此进行描述。
这是“替换malloc”的问题。您可以实现JE::malloc
,但是std::
是特殊的。 C ++至少承认有可能替换operator new
,但即使在此详细级别也从未指定。