1

In C++, is there any way to ensure that a function does no heap allocations?

I am imagining something like this would be very useful in a non-release build.

int doSomething() {
   enable_no_heap_allowed();
   // Do lots of complex work.
   // Program would crash/assert here if heap is allocated to.
   disable_no_heap_allowed();
}

Currently it seems like in order to determine if a heap allocation happens I would have to actually audit all the code being run (including all nested functions).

Does such functionality exist? Are there any languages other than C++ which have such a feature?

papplesharp
  • 272
  • 1
  • 9
  • 3
    You'd have to make your own heap governor mechanism. Which means either making your own heap, or intercepting the current heap machinery (which will be platform specific) and splicing in your governor mechanism. – Eljay Oct 23 '21 at 21:05
  • 3
    For standard library things you usually know if it's heap allocated or not. But no builtin way as far as I know. The only thing that sort of does that in C++ is using compile-time constructs (with templates or `constexpr`) – ShadowMitia Oct 23 '21 at 21:07
  • 1
    No, there's nothing like that in C++. – Sam Varshavchik Oct 23 '21 at 21:08
  • Maybe #ifdef ENABLE_HEAP_USE 1 #undef ENABLE_HEAP_USE when needed? – Omid CompSCI Oct 23 '21 at 21:16
  • 3
    As an aside and a bit of bikeshedfing `enable_no_heap_allowed` is a poor name. It's in the realm of double negatives. The plain verbs `disable_heap` and `enable_heap` will be easier to reason about. – StoryTeller - Unslander Monica Oct 23 '21 at 21:16
  • 1
    If you're looking for a compile-time check, I'm afraid there is nothing. – Piotr Siupa Oct 23 '21 at 21:25
  • 3
    If you were to do this, it should come with a big warning. Thanks to [short string optimization](https://stackoverflow.com/questions/10315041/meaning-of-acronym-sso-in-the-context-of-stdstring), you could potentially cause "no heap allocation" to become "heap allocation" simply by adding a single character to a string. When you think about the implications, think about copying strings that are global data or that are passed in as parameters. **A change in a seemingly unrelated part of the codebase could break a function that has disabled heap allocations.** – JaMiT Oct 23 '21 at 22:06
  • Why you want to know? In general, at compile time it is impossible to know unless you always treat the same data as some allocations are conditional (for example, if the length of a string is greater than a threshold). If it is for performance reason, then you would generally prefer to use a profiler instead. Making either an assertion or a crash will rarely if never be useful either in DEBUG or RELEASE. Finally, if it is to write that must respect very strong security practice in a specialized domain, then you would probably use a specialized langage instead of C++. – Phil1970 Oct 23 '21 at 22:11
  • Probably the easiest solution would be to write the `// Do lots of complex work.` part complety in a C function in it own file as you have far less allocations that might occurs behind the scene. That might make sense maybe on an embedded device if you have severe restriction for code called in an interrupt or something similar. – Phil1970 Oct 23 '21 at 22:27
  • 1
    I agree with @JaMiT, there are lots of dangers with this for introducing hard to find, difficult to eradicate bugs. – Galik Oct 23 '21 at 23:03
  • I would intend to use this functionality in a debug build only, so such hard to find scenarios wouldn't occur in a release build. – papplesharp Oct 24 '21 at 05:11
  • @Phil1970 one use-case would be to verify a real-time audio-processing callback, where any heap access introduces the possibility of audio glitches if the allocator routine takes too much time to return. – Jeremy Friesner Oct 24 '21 at 15:36

1 Answers1

2

You could have enable_no_heap_allowed() increment a thread-local int, and decrement_no_heap_allowed() decrement it. Then write a global-new operator that checks the thread-local variable and throws an exception/assert if it’s non-zero, or allocates the requested memory otherwise.

Note that this approach isn't a full solution since it only looks at calls to the new and delete operators; as @HolyBlackCat points out, heap-accesses that are done by directly calling malloc() or free() or other C-level heap-manipulation functions will not be detected.

Regardless, here is some C++11 example code (which btw you can use to figure out what std::string considers a "short string" for purposes of its short-string optimization):

#include <stdio.h>
#include <string>

static thread_local int _heapDisallowedCount = 0;

void begin_no_heap_allowed() {_heapDisallowedCount++;}
void end_no_heap_allowed()   {_heapDisallowedCount--;}
void check_heap_allowed(const char * desc, size_t numBytes)
{
   if (_heapDisallowedCount != 0)
   {
      printf("ERROR, heap access in heap-disallowed section (reported by %s, %zi bytes)\n", desc, numBytes);
      // add code to assert() or throw an exception here if you want      
   }
}

void * operator new(size_t s) throw(std::bad_alloc)
{
   check_heap_allowed("operator new", s);
   void * ret = malloc(s);
   if (ret == NULL) throw std::bad_alloc();
   return ret;
}

void * operator new[](size_t s) throw(std::bad_alloc)
{
   check_heap_allowed("operator new[]", s);
   void * ret = malloc(s);
   if (ret == NULL) throw std::bad_alloc();
   return ret;
}

void operator delete(void * p) throw()
{
   check_heap_allowed("delete", -1);
   return free(p);
}

void operator delete[](void * p) throw()
{
   check_heap_allowed("delete[]", -1);
   return free(p);
}

static void SomeFunction()
{
   char buf[256];
   fgets(buf, sizeof(buf), stdin);
   char * newline = strchr(buf, '\n');
   if (newline) *newline = '\0';
   printf("You typed:  [%s]\n", buf);

   std::string s(buf);
   printf("As a std::string, that's:  [%s]\n", s.c_str());
}

int main(int, char **)
{
   printf("Enter a string:\n");
   SomeFunction();

   printf("Enter another string (now with new/delete calls being detected:\n");
   begin_no_heap_allowed();
   SomeFunction();
   end_no_heap_allowed();

   return 0;
}
Jeremy Friesner
  • 70,199
  • 15
  • 131
  • 234
  • 1
    `malloc` also needs to be hooked somehow... – HolyBlackCat Oct 23 '21 at 21:19
  • OP has written it is meant to be a protection from a crash. This looks to be a way to still cause a crash, just a different one. – Piotr Siupa Oct 23 '21 at 21:27
  • 1
    @NO_NAME I don’t see anywhere where OP wrote about protection from a crash; can you point that part out? – Jeremy Friesner Oct 23 '21 at 21:50
  • @JeremyFriesner the comment in the code snippet. ("Program would crash/assert here if heap is allocated to.") – Piotr Siupa Oct 23 '21 at 22:00
  • 1
    @NO_NAME There is some ambiguity to "would" in *"Program would crash/assert here if heap is allocated to."* Did the OP mean that the crash/assert would happen if the desired functionality could not be obtained? Or that the crash/assert would happen as part of the desired functionality? – JaMiT Oct 23 '21 at 22:10
  • While there might be some ambiguity, to me, it make little sense why it would crash exactly at that point (if some allocation were done)... On the other hand, making the program crash or assert volontary for that reason seems too drastic. – Phil1970 Oct 23 '21 at 22:23
  • @NO_NAME I interpret that to mean the OP *wants* the program to crash/assert on a heap-allocation, to make inappropriate heap-allocations easier to detect/debug/remove. That makes sense in context of a “non-release build”, as mentioned at the beginning of the question. – Jeremy Friesner Oct 23 '21 at 23:00
  • I meant that the expected behavior would be to crash if a heap allocation occurs, as Jeremy interpreted it. This would help more easily detect the undesired heap allocation. – papplesharp Oct 24 '21 at 05:07