Here we have several different examples, given that we even have several languages ​​for discussion.
Let's start with a simple example, a simple array in C ++:
int array[512];
What happens in terms of memory allocation here? On the stack, 512 words of memory are allocated for the array. No heap of memory is allocated. There is no overhead; there are no pointers to the array, there is nothing, only 512 words of memory.
Here is an alternative method to create an array in C ++:
int * array = new int[512];
Here we create an array on the heap. It will allocate 512 words of memory without the extra memory allocated on the heap. Then, as soon as this is done, the address at the beginning of this array will be placed on a variable in the stack, taking up an additional memory word. If you look at the total memory for the entire application, yes, it will be 513, but it is worth noting that it is on the stack and the rest is on the heap (stack memory is much cheaper to allocate and does not cause fragmentation, but if you abuse it or misuse it, you can easily deal with it.
Now in C #. In C #, we don’t have two different syntaxes, all you have is:
int[] array = new int[512];
This will create a new array object on the heap. It will contain 512 words of memory for the data in the array, as well as some additional memory for the overhead of the array object. He will need 4 bytes to hold an array counter, a synchronization object, and several other bits of overhead that we really don't need to think about. These overheads are small and independent of array size.
There will also be a pointer (or "link", as it would be more appropriate to use in C #), to this array, which is pushed onto the stack, which will occupy the memory word. Like C ++, stack memory can be allocated / deallocated very quickly and without memory fragmentation, so when considering the memory size of your program it often makes sense to separate it.