Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to determine if returned pointer is on the stack or heap

Tags:

c++

c

memory

I have a plugin architecture, where I call functions in a dynamic library and they return me a char* which is the answer, it is used at some later stage.

This is the signature of a plugin function:

char* execute(ALLOCATION_BEHAVIOR* free_returned_value, unsigned int* length);

where ALLOCATION_BEHAVIOR must be either: DO_NOT_FREE_ME, FREE_ME, DELETE_ME where the plugin (in the library) tells me how the plugin allocated the string it has just returned: DO_NOT_FREE_ME tells me, this is a variable I'm not supposed to touch (such as a const static char* which never changes) FREE_ME tells me I should use free() to free the returned value and DELETE_ME tells me to use delete[] to get rid of the memory leaks.

Obviously, I don't trust the plugins, so I would like to be able to check that if he tells me to free() the variable, indeed it is something that can really be freed ... Is this possible using todays' C/C++ technology on Linux/Windows?

like image 563
Ferenc Deak Avatar asked May 23 '13 09:05

Ferenc Deak


People also ask

Are pointers on the heap or stack?

Pointer is allocated on the stack and the object it is pointing to is allocated on the heap.

Where is the pointer stored in memory?

To answer your question: ptr is stored at stack.

Do pointers always point to heap?

A pointer can point to any object, regardless of whether it resides on a "stack", on a "heap" or on same "fixed" memory, and regardless of whether it is a "global variable" or a "local variable".

Where are pointers stored on the stack?

The stack pointer always points to the item that is currently at the top of the stack. A push operation pre-decrements the stack pointer before storing an item on the stack. Hence the program initializes the stack pointer to point one item beyond the highest numbered element in the array that makes up the stack.


2 Answers

Distinguishing between malloc/free and new/delete is generally not possible, at least not in a reliable and/or portable way. Even more so as new simply wrapps malloc anyway in many implementations.

None of the following alternatives to distinguish heap/stack have been tested, but they should all work.

Linux:

  1. Solution proposed by Luca Tettananti, parse /proc/self/maps to get the address range of the stack.
  2. As the first thing at startup, clone your process, this implies supplying a stack. Since you supply it, you automatically know where it is.
  3. Call GCC's __builtin_frame_address function with increasing level parameter until it returns 0. You then know the depth. Now call __builtin_frame_address again with the maximum level, and once with a level of 0. Anything that lives on the stack must necessarily be between these two addresses.
  4. sbrk(0) as the first thing at startup, and remember the value. Whenever you want to know if something is on the heap, sbrk(0) again -- something that's on the heap must be between the two values. Note that this will not work reliably with allocators that use memory mapping for large allocations.

Knowing the location and size of the stack (alternatives 1 and 2), it's trivial to find out if an address is within that range. If it's not, is necessarily "heap" (unless someone tries to be super smart-ass and gives you a pointer to a static global, or a function pointer, or such...).

Windows:

  1. Use CaptureStackBackTrace, anything living on the stack must be between the returned pointer array's first and last element.
  2. Use GCC-MinGW (and __builtin_frame_address, which should just work) as above.
  3. Use GetProcessHeaps and HeapWalk to check every allocated block for a match. If none match for none of the heaps, it's consequently allocated on the stack (... or a memory mapping, if someone tries to be super-smart with you).
  4. Use HeapReAlloc with HEAP_REALLOC_IN_PLACE_ONLY and with exactly the same size. If this fails, the memory block starting at the given address is not allocated on the heap. If it "succeeds", it is a no-op.
  5. Use GetCurrentThreadStackLimits (Windows 8 / 2012 only)
  6. Call NtCurrentTeb() (or read fs:[18h]) and use the fields StackBase and StackLimit of the returned TEB.
like image 128
Damon Avatar answered Sep 27 '22 01:09

Damon


I did the same question a couple of years ago on comp.lang.c, I liked the response of James Kuyper:

Yes. Keep track of it when you allocate it.

The way to do this is to use the concept of ownership of memory. At all times during the lifetime of a block of allocated memory, you should always have one and only one pointer that "owns" that block. Other pointers may point into that block, but only the owning pointer should ever be passed to free().

If at all possible, an owning pointer should be reserved for the purpose of owning pointers; it should not be used to store pointers to memory it does not own. I generally try to arrange that an owning pointer is initialized with a call to malloc(); if that's not feasible, it should be set to NULL sometime before first use. I also try to make sure that the lifetime of an owning pointer ends immediately after I free() the memory it owns. However, when that's not possible, set it to NULL immediately after free()ing that memory. With those precautions in place, you should not let the lifetime of a non-null owning pointer end without first passing it to free().

If you have trouble keeping track of which pointers are 'owning' pointers, put a comment about that fact next to their declaration. If you have lots of trouble, use a naming convention to keep track of this feature.

If, for any reason, it is not possible to reserve an owning pointer variable exclusively for ownership of the memory it points at, you should set aside a separate flag variable to keep track of whether or not that pointer currently owns the memory it points at. Creating a struct that contains both the pointer and the ownership flag is a very natural way to handle this - it ensures that they don't get separated.

If you have a rather complicated program, it may be necessary to transfer ownership of memory from one owning pointer variable to another. If so, make sure that any memory owned by target pointer is free()d before the transfer, and unless the lifetime of the source pointer ends immediately after the transfer, set the source pointer to NULL. If you're using ownership flags, reset them accordingly.

like image 34
David Ranieri Avatar answered Sep 24 '22 01:09

David Ranieri