8

Related to: C++ new int[0] -- will it allocate memory?

The standard says, in 5.3.4/7:

When the value of the expression in a direct-new-declarator is zero, the allocation function is called to allocate an array with no elements.

...and, in 3.7.3.1/2:

The effect of dereferencing a pointer returned as a request for zero size is undefined.

...yet, the pointer can't be a null pointer.

Since actually dereferencing the pointer is undefined behavior, does any implementation return a pointer to a guard page? I imagine that it'd be easy, and help detect bugs/improve security.

Community
  • 1
  • 1
Alexander Riccio
  • 283
  • 2
  • 10
  • Uniqueness issues might (might) precent reuse of the same page, which would mean ~4k of memory space per zero byte allocation... or, we could instead allocate an 'unused' page, then a set of pages where we use each address for a distinct array, and follow it with another empty guard page. However, this all seems over engineering for something not often used, no? – Yakk - Adam Nevraumont Jul 22 '15 at 22:38
  • Over engineering? We're talking about *implementation of dynamic memory allocation*, which is used by nearly every part of a program! Surely, a bit of extra engineering is worth it here? Unless you're talking about complexity/performance - yes this *is* another code path to test, a bit of additional complexity, and a bit more code in a very hot function. – Alexander Riccio Jul 22 '15 at 22:52
  • 2
    I think @Yakk meant that zero element arrays are not often used. Which I tend to agree with. – Jonathan Potter Jul 22 '15 at 22:56
  • What is a fundamental difference between accessing an element at index `0` for `0`-size array and an element at index `1` for 1-size array? Why would we do special allocation in the first case? – AlexD Jul 22 '15 at 23:00
  • @AlexD in fact, debugging heaps that stuff every allocation at the end of a page with a protected page afterwards to catch "off-by-one" errors are a neat trick that is used in real tool chains (I saw a talk by ... ubisoft Montreal guy mayhap?). The equivalent for zero sized arrays costs you a page per allocation. – Yakk - Adam Nevraumont Jul 22 '15 at 23:44
  • @Yakk Nice :). But again, how is it worse than to allocate a page for `new[1]` or `new [2]`? All in all, it seems to me that `new[0]` is rather a _particular case_ of `new[...]`, which does not justify special handling. I think it is close to what you said in the first comment, correct? – AlexD Jul 22 '15 at 23:53
  • @AlexD well, `new char[1]` has ~400000% overhead under this system. `new char[0]` would have infinite% overhead! ;) But yes, I'd say that special handing wouldn't be all that warrented here. Zero sized `new` just isn't common enough. I could see being lazy in that it might sometimes hit the edge of a page, and dereferencing might break things, but putting effort into it seems a waste. – Yakk - Adam Nevraumont Jul 23 '15 at 00:11
  • @Yakk Application Verifier uses that trick (in "full page" heap) to detect buffer overruns. – Alexander Riccio Jul 23 '15 at 02:29
  • Related, see [Qualys Security Advisory - The Stack Clash](http://www.openwall.com/lists/oss-security/2017/06/19/1) on OSS-Security mailing list. Its shows off some neat tricks, and its pretty damning of the guard page. Its amazing how many OS'es they took down with it. – jww Jun 19 '17 at 22:06

2 Answers2

4

I looked at the (draft) standard and I couldn't find anything that explicitly prohibits this. I would guess that the answer is "no", though. This is why:

  • As you say, new is required to return a non-null pointer.
  • Further, whatever it returns must be safe to pass to delete.
  • So a "random value" won't work because it will break delete.
  • It's also required to return different values for each call (at least until they are deleted) - see the C++ standard section basic.stc.dynamic.allocation.
  • At this point only one option remains: return "fake pointers" which are somehow recognizable by delete as "fake pointers".

One possible implementation could be done by reserving a range of unpaged memory, and every time someone calls new int[0] it could return a different address in that range (easy to do if the allocator keeps a global counter or something like that).

On the plus side, you will gain the ability to immediately detect dereferences on this type of pointer. On the other hand, you will lose the ability to detect double-frees because delete on your pointer effectively becomes a no op, and you will make new and delete more complex and slower for all the normal cases.

So because it's tricky to do, and the cons outweigh the pros, I'm pretty confident that nobody does that.

gcc on linux just allocates a small amount of memory and returns that. I believe that this is pretty much a standard way to do this.

AaronI
  • 782
  • 4
  • 11
  • You are right - I found the relevant section in the standard (basic.stc.dynamic.allocation) and updated the answer. – AaronI Jul 23 '15 at 00:22
2

To track how much memory needs to be released most allocators I've looked within or wrote look something like:

 void *malloc_example( size_t bytes ) {
     size_t *ret = get_memory(bytes + sizeof size_t);   /* find free memory */
     *ret = bytes;                /* remember size of allocated area */
     return (void *)( ret + 1 );
 }

So when you allocate zero-length blocks you actually get a sneaky word ahead of the address returned to you. This also assures each allocation is assigned a unique address. This is important and pointing to dedicated guard memory for zero-length blocks is more of a waste as each free must test for what my experience says is a rare condition.

Some debugging allocators include additional overhead to catch double-frees, memory leaks, guard words to catch overflow of previous buffers, etc. Also, pointing to some magic memory can make such debugging more difficult to do.

Gilbert
  • 3,364
  • 13
  • 17