in your case - string allocates on heap.
but reference types can be allocated on stack.
for exemplar, arrays can be allocated as:
private static void Main(string[] args)
{
unsafe
{
const int size = 123;
var mgmtArray = new long[size];
mgmtArray[0] = 12345;
void* refAddr = Unsafe.AsPointer(ref mgmtArray);
long mgmtArrAddr = *(long*)refAddr;
long mtAddr = *(long*)mgmtArrAddr;
long arrSize = ((int*)mgmtArrAddr)[2];
Console.WriteLine("Addr ref: {0:x}", (long)refAddr);
Console.WriteLine("Mgmt array addr: {0:x}", mgmtArrAddr);
Console.WriteLine("MT: {0:X}", mtAddr);
Console.WriteLine($"{nameof(arrSize)}: {arrSize}");
long* stackPtr = stackalloc long[size * 2];
stackPtr[0] = 0; //sync block
stackPtr[1] = mtAddr;
((int*)stackPtr)[4] = size;
stackPtr++;
long** stackPtrRef = &stackPtr;
var pointer = new MgmtPointer<long[]>(Unsafe.Read<long[]>(stackPtrRef));
long[] arrayOnStack = Unsafe.Read<long[]>(stackPtrRef);
arrayOnStack[0] = 54321;
Console.WriteLine("-----");
Console.WriteLine($"In heap: {mgmtArray[0]}"); //12345
Console.WriteLine($"In stack: {arrayOnStack[0]}"); //54321
Console.WriteLine($"arrayOnStack len: {arrayOnStack.Length}");
}
}
for other reference types also, but with some changes.