0

A quick thought experiment before getting to the question. Imagine someone is implementing std::malloc (say, one of the JEMalloc or TCMalloc folks). One of the very basic things they would need is the ability to know that the program will not call back into malloc once execution has entered the implementation of std::malloc.

For example,

void* malloc(...) {
    auto lck = std::unique_lock{malloc_mutex};
    // .. memory allocation business logic
}

Now if there is a signal in between the lock and the business logic for the allocation, we can deadlock if the signal handler calls back into std::malloc. It is not designed to be re-entrant, the C++ standard requires that a signal handler registered with std::signal does not call back into operator new (which can possibly call back into malloc, therefore it is required that a user-defined signal handler not call back into malloc if it is to be considered portable across all implementations of the language).

§[support.signal]p3 in the most recent version of the standard outlines this requirement

  1. An evaluation is signal-safe unless it includes one of the following:

a call to any standard library function, except for plain lock-free atomic operations and functions explicitly identified as signal-safe. [ Note: This implicitly excludes the use of new and delete expressions that rely on a library-provided memory allocator. — end note ]


However, the C++ standard seemingly says nothing about how function stacks are to be implemented for threads of execution (see this question: C++ threads stack address range), this means that a function dispatch within std::malloc's implementation might call into operator new if the program is compiled with segmented stacks.

How can one possibly implement a function like std::malloc in that case? If indeed, the C++ standard offers no such guarantees, then what does? How can we know that the implementation of a regular function goes through the regular stack allocation process (stack pointer increment)? Which standard (eg. ABI, compiler, POSIX) covers this?

Community
  • 1
  • 1
Curious
  • 20,870
  • 8
  • 61
  • 146
  • Since you're talking about signals, the most relevant standard is the POSIX standard. And it doesn't list `malloc` among its async-signal safe functions. In short, `malloc` (or its siblings) should *never* be called in a signal handler. If there is such a call in a signal handler, then it's a fault with the program as written, and not really anything you should bother with as an implementer of `malloc`. – Some programmer dude Feb 18 '19 at 08:34
  • Regarding POSIX, see e.g. [this official reference about signals](http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04). – Some programmer dude Feb 18 '19 at 08:35
  • @Someprogrammerdude I meant to ask less about malloc and its signal-safety, but more about where I can get a guarantee that function stacks will not do some crazy magic calls into an allocator, and rather just be implemented with a simple stack pointer increment/decrement. It seems like a programmer should be able to assert this basic requirement somehow.. But no standard I am reading seems to mandate this anywhere. – Curious Feb 18 '19 at 08:36
  • Note that you may be unable to implement malloc() in conforming C/C++ code (that is, without triggering UB). See e.g. [1](https://stackoverflow.com/q/38515179), [2](https://stackoverflow.com/q/38510557). – Alexey Frunze Feb 18 '19 at 10:42
  • @AlexeyFrunze You mean without to ABI compilation? Strict separate compilation is to the ABI. There is no such thing "effective type" in the ABI. – curiousguy Feb 20 '19 at 20:49
  • @curiousguy It's not about any ABI, it's about the language, specifically aliasing. You can't just have a large array of chars and slice it and reuse it as storage for other types through ingenious pointer casts. – Alexey Frunze Feb 21 '19 at 05:35
  • @AlexeyFrunze You can't when effective types or object lifetime exist, but if you compile to the ABI, you can. At the ABI level these don't make sense. Once you cross an ABI interface, you can reinterpret the bytes. Are you saying real compilers don't have ABI interfaces? – curiousguy Feb 21 '19 at 12:35
  • @curiousguy I see what you mean. But then the implementation must be very careful about using the memory it gets from the OS or the library across the ABI boundary. This actually begs another question. If you get a pointer from those, what can you legally do with it in C? The language doesn't define it. IOW, there must be another document (POSIX and such) to say it's OK to use such a pointer in such and such ways (e.g. do pointer arithmetic on it, etc). – Alexey Frunze Feb 22 '19 at 08:41

2 Answers2

2

The implementation is required to use a signal-safe allocator for its stack frames. This follows from the fact that function calls (to non-library functions) in signal handlers are permitted. The implementation can use malloc or operator new, but only if those allocators are themselves signal-safe.

Florian Weimer
  • 32,022
  • 3
  • 48
  • 92
0

Under the logic of the C++ Standard, the implementation is considered as a whole. In particular, any part of an implementation may assume anything about any other part of the implementation.

That means for this question that std::malloc and the signal handler may assume things about each other. Some implementations may decide that their std::malloc implementation is async-safe, others may decide it's not. But there are a myriad of other assumptions that might exist - alignment, contiguity, recycling of free'd addresses, etc. Since this is all internal to implementations, there's no Standard describing this.

That is a problem for "replacement mallocs". You can implement JE::malloc but std:: is special. C++ at least acknowledged the possibility of a replacement operator new but even that was never specified to this detailed level.

MSalters
  • 173,980
  • 10
  • 155
  • 350