Operating Systems usualy handle memory as following:
The OS allocates the memory for global and static variables when it
loads the program into memory and doesn't deallocate it until the
program terminates. ... The other two regions, the stack and the heap,
are more dynamic - the memory is allocated and deallocated by the
program as needed. (Reference needed)
The "program" - in this case the CLR - allocates/requests memory on the stack or heap from the os when needed.
How does CLR manage this?
Managed Heap
The CLR implements an Managed Heap. This in fact is an abstraction over the native heap provided by the os:
- The Managed Heap is used to divide the Heap in further segments (Large Object Heap, ObjectHeap for example).
- When you allocate memory on the Managed Heap you won't get back a
real pointer as a return value. What you get instead is a Handle, which is a indirection to the "real" pointer. This is because the GC Collector can
compact your heap and move around the objects (allocated on native heap) and thus changing the pointer address)
- If you want to allocate memory in the unmanaged heap you need to use
Marshal.AllocHGlobal
or Marshal.AllocCoTaskMem
- If code is blittable or you use the unsafe keyword you need to
pin the object so it doesent get moved around by GC. Because in both cases your referring to the object via the pointer directly and not the handle...
Managed Stack
This is used for stackwalking
Ref Counting, so the GC knows when he can move an object to the
next generation and so on...
So you see theres a level of abstraction over the native concepts provided by the os so it can be managed by the clr but the data at the end resides in the native stack/heap.
But what if the device/os doesn't provide a heap/stack (as stated per your question)?
CLR on a device/os without stack/heap
If the CLR would be made available on such an os without such memory segmentation, the CLR could be build seperately for that os (so it accepts the same IL and manages memory in a way it is efficient without the stack/heap).
In this case the documentation would change for such systems.
-OR-
It also could be possible that the clr creates their own datastructuers within the memory and manage its own stack and heap just to virtually comform the spezifications.
Remember the CLR is always Build separately for its destination (you dont use the same CLR Sourcecode and compile it once for linux and once for windows). Heck theres even an own CLR built in Javascript (.net blazor).
But since theres no such implementation nobody really can tell (what if questions are always tricky... What if I die tomorrow, what would I do today?). There are virtually no OS around that does not split their processes in Stack/heap.
Btw you stated following:
whereas member variables are always allocated in the heap
Thats not completely true if the object itself resides on the stack (eg. struct) then the members are also allocated on the stack.
A reference type always stores the pointer on the stack/heap (whereever the object resides of which it is member of) and points to an object in the heap. Local variables, eg. variables in methods, are always on the stack (value for valuetypes and pointer/handle for reference types)