<div dir="ltr"><div dir="ltr"><br></div><br><div class="gmail_quote gmail_quote_container"><div dir="ltr" class="gmail_attr">On Tue, Mar 10, 2026 at 12:17 PM Nathan Myers <<a href="mailto:ncm@cantrip.org">ncm@cantrip.org</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">On 3/10/26 6:56 AM, Jonathan Wakely wrote:<br>
> On Mon, 9 Mar 2026 at 15:18, Nathan Myers <<a href="mailto:ncm@cantrip.org" target="_blank">ncm@cantrip.org</a>> wrote:<br>
>><br>
>> On 3/9/26 6:01 AM, Jonathan Wakely wrote:<br>
>>><br>
>>><br>
>>> On Sun, 8 Mar 2026, 23:39 Nathan Myers, <<a href="mailto:ncm@cantrip.org" target="_blank">ncm@cantrip.org</a><br>
>>> <mailto:<a href="mailto:ncm@cantrip.org" target="_blank">ncm@cantrip.org</a>>> wrote:<br>
>>><br>
>>> Have you thought about how std::allocator<>::allocate_at_least<br>
>>> might usefully discover whether the ::op new (it is obliged by the<br>
>>> Standard to use) is the one in libstdc++, and will supply extra<br>
>>> details about memory allocated, not one the user has supplied and<br>
>>> the linker has substituted in its place? Ideally it would all be<br>
>>> decided at link time, and not conditionally in every call, but<br>
>>> linker magic is fragile even without LTO. And users can call ::op<br>
>>> new directly, with no #include to declare it.<br>
>>><br>
>>> Maybe pass an extra argument to ::op new that the user's version<br>
>>> won't notice, and that ours may discover is present by looking at<br>
>>> the stack frame? The extra argument might be a pointer to a place<br>
>>> to scribble extra details. (A hint argument might be placed there,<br>
>>> besides.) But stack frame conventions vary too.<br>
>>><br>
>>><br>
>>> We don't need to do that. This feature is a customisation point for non-<br>
>>> standard allocators (and program-defined specializations of<br>
>>> std::allocator<User type>), we don't need to make std::allocator use<br>
>>> it. P0901 proposed changes to operator new which would have been useful<br>
>>> here, but that got abandoned.<br>
>><br>
>> I don't understand that. If a user displaces ::operator new, they<br>
>> will expect std::allocator members to call it. The Standard seems<br>
>> to require all members obtain whatever memory they deliver by<br>
>> calling ::operator new. So, when calls to ::operator new are visible,<br>
>> we seem to be obliged to use it.<br>
>><br>
>>> I did post a prototype in <a href="https://gcc.gnu.org/PR106477" rel="noreferrer" target="_blank">https://gcc.gnu.org/PR106477</a> <https://<br>
>>> <a href="http://gcc.gnu.org/PR106477" rel="noreferrer" target="_blank">gcc.gnu.org/PR106477</a>> which can detect whether operator new has been<br>
>>> replaced, but I don't think we would need that here.<br>
>>><br>
>>> We could just have a thread_local size_t* which is initially null and<br>
>>> which std::allocator would set to a local size_t, and operator new could<br>
>>> check for non-null and conditionally write the allocated size to it. But<br>
>>> that assumes that malloc provides a way to get the size. If a user<br>
>>> interposes their own malloc but doesn't work replace malloc_usable_size<br>
>>> then you'd have UB.<br>
>><br>
>> Thread-local storage is a big hammer. Linker magic seems fragile.<br>
>> We could make calling ::operator new(0) poke a global if it knows<br>
>> about that global:<br>
>><br>
>> std::atomic<int> ::__op_new_state{0};<br>
>><br>
>> auto allocate_at_least(size_t __n) -> allocation_result<pointer><br>
>> {<br>
>> auto __state = __op_new_state.load(memory_order_relaxed);<br>
>> if (__state > 0)<br>
>> return _M_allocate_at_least(__n);<br>
>> else if (__state < 0)<br>
>> return { ::operator new(__n * sizeof(_Tp)), __n };<br>
>><br>
>> ::operator delete(::operator new(0)); // sample it<br>
>> if (__op_new_state.load(memory_order_relaxed) == 0) // still?<br>
>> __op_new_state.store(-1, memory_order_relaxed);<br>
>> return allocate_at_least(n);<br>
>> }<br>
>><br>
>> void* ::operator new(size_t n)<br>
>> {<br>
>> if (n == 0 && ::__op_new_state.load(memory_order_relaxed) == 0)<br>
>> ::__op_new_state.store(1, memory_order_relaxed);<br>
>> ...<br>
>> }<br>
>><br>
>> Relaxed semantics is safe here because all writers would write<br>
>> the same value. I don't think there is any repetitive performance<br>
>> penalty for relaxed reads to an atomic value that is not changing.<br>
>><br>
>> Allowing the user to poke __op_new_state in their ::op new and<br>
>> supply their own __allocate_at_least to use is trivially more<br>
>> complicated.<br>
> <br>
> How would operator new return the real size to the caller?<br>
<br>
It doesn't. std::allocator<>::_M_allocate_at_least() does that job.<br></blockquote><div>What would _M_allocate_at_least do? As far as I know there is no</div><div>interface in malloc that allows asking for and then using returned bytes.</div><div>malloc_usable_size (<a href="https://man7.org/linux/man-pages/man3/malloc_usable_size.3.html">https://man7.org/linux/man-pages/man3/malloc_usable_size.3.html</a>) </div><div>It is close but does not allow the number to be used, without realloc</div><div>call, according to docs.</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<br>
All that is new in ::operator new is establishing for std::allocator<br>
that the user has not displaced ::op new(): seeing __op_new_state==1,<br>
allocate_at_least() knows it can do whatever it likes because calls<br>
to ::op new are not user-visible, so users cannot determine whether<br>
memory has been obtained from it.<br>
<br>
> operator new can't encode the result in the return value somehow,<br>
> because that would be an ABI break for all callers that aren't aware<br>
> that operator new would be doing that now.<br>
> <br>
> Poking something to the stack seems a lot less portable and more<br>
> fragile than using a thread-local variable. And likely to cause<br>
> problems with -fstack-protector and/or AddressSanitizer.<br>
> Maybe something with __builtin_return_address would be possible, but<br>
> operator new has no idea if the caller is opting in to having its<br>
> stack fiddled with (maybe the atomic was set by a caller in another<br>
> thread, and the caller in the current thread is not<br>
> allocate_at_least).<br>
<br>
Thread-local variables are a snake pit. Fortunately, we don't<br>
need anything like that. Literally all the machinery is right<br>
there in the code presented above, except the implementation of<br>
_M_allocate_at_least(), which may call jemalloc or whatever it<br>
likes, and communicate its extra information to the caller via<br>
an ordinary returned allocation_result<T*>{p, n}.<br>
<br>
-N<br>
<br>
</blockquote></div></div>