25

There is a question:

Given:

struct Point {int x; int y;}
var p = new Point[3]

how many bytes of memory will be allocated in stack and in heap if we use a 64-bit processor?

The correct answer for .Net is 44. Can somebody explain how did this number appear?

As far as I understand, p will occupy 8 bytes in stack for x64.

And we have two values of Int32 per structure, thus p.Length * sizeof(Point) 3 * 8 = 24 bytes in heap for an array.

That will be 32 bytes. What is the rest 12 bytes for in this case?

16
  • you mean 2 * 8 right? Commented Aug 29, 2016 at 11:41
  • 15
    You are asking about the internals of .NET which can be hard to answer because they are... well... internals. Jon Skeet did some research and concludes that the overhead of a value type array is 12 bytes: stackoverflow.com/a/1589806/98607 Commented Aug 29, 2016 at 11:46
  • 3
    Who says p is on/in the stack? Commented Aug 29, 2016 at 12:22
  • 1
    It could be captured (closed over) by a lambda or part of an async method. Commented Aug 29, 2016 at 15:09
  • 1
    I don't know about the deep workings of .net but is this actually defined in the language? It seems rather limiting that it would define the exact amount of heap space it can allocate for objects. Commented Aug 29, 2016 at 19:45

3 Answers 3

42

Your answer of 44 bytes is probably a confusion referring to an array of 32 bit architecture.

In .Net (32 bit):

  • Every object contains 4 bytes for synchronization (lock (obj)).
  • Every object contains 4 bytes of its type token.
  • Every array contains 4 bytes of its length.

The pointer is 8 bytes as you said.

This with the 24 bytes of the array itself gives you 44 bytes.


However, this is the header layout for 32 bit.

As you can see, the memory layout of the following code:

var p = new Point[3];
p[0] = new Point { x = 1, y = 2 };
p[1] = new Point { x = 3, y = 4 };
p[2] = new Point { x = 5, y = 6 };

var p2 = new Point[3];
p2[0] = new Point { x = 8, y = 8 };
p2[1] = new Point { x = 8, y = 8 };
p2[2] = new Point { x = 8, y = 8 };

Will be:

Memory layout

You can see the number values in the memory layout as well.


In 64 bit, each field of the header with its takes 8 byte so that the header length is 24 bytes therefore the length of the entire array is 48 bytes and with the variable pointing to the array: 56 bytes.

64 bit architecture memory layout:

64 bit memory layout


Notes:

  • If your array wasn't rounded up to an 8 byte multiple alignment would take place, but it is so alignment is not required. Example (two 1 sized int arrays):

    Alignment

  • Even though the length field of the header is 8 bytes in 64 bit, it's larger than the maximum array size .NET allows therefore only 4 may be used.

Keep in mind that this is an implementation detail and it might change between implementations/versions of the CLR.

Sign up to request clarification or add additional context in comments.

34 Comments

so you are limited to 2^32 array members even on 64bit arch?
@Paladin The indexer is also limited to int so its int.MaxValue which is 2^31.
@TamirVered: Contents of a large file? Scientific/numerical computing where array is obvious type for the situation? ...
@Paladin probably to avoid the huge number of gotchas associated with math involving signed and unsigned values. (In general .net tries to avoid using unsigned values whenever possible.) IIRC C# requires a lot of extra casts in those cases to (in theory) make the developer acknowledge that something could go wrong.
@R.. Split the file while loading it... To lines/deserialized data/grouped data. Arrange the data in some more complex structure before doing a numerical/scientific computing. I agree that in some cases people would want to do so but there probably will be a better alternative for them.
|
13

Most of this is purely an implementation detail and could change with the next version of the CLR.

Run the following program as x86 or x64 and you can empirically determine the size of your structure:

struct Point { int x; int y; }

class Program
{
    const int Size = 100000;

    private static void Main(string[] args)
    {
        object[] array = new object[Size];
        long initialMemory = GC.GetTotalMemory(true);
        for (int i = 0; i < Size; i++)
        {
            array[i] = new Point[3];
        }
        long finalMemory = GC.GetTotalMemory(true);
        GC.KeepAlive(array);

        long total = finalMemory - initialMemory;
        Console.WriteLine("Size of each element: {0:0.000} bytes",
                          ((double)total) / Size);

    }
}

The code is pretty simple but was shamelessly stolen from Jon Skeet.

If you do run this you will get the following results:

x86: 36 byte
x64: 48 byte

In the current implementation the size of every object is aligned to the pointer size, this means every object in x86 is 4 byte aligned, and under x64 8 byte (this is absolutely possible to change - HotSpot in Java for example aligns everything to 8 byte even under x86).

Arrays in C# are somewhat special with their length: While they do have a 4 byte length field, under x64 they also include 4 byte additional padding (vm/object.h:766 contains the interesting part). This is most likely done to guarantee that the start of the actual fields is always 8 byte aligned under x64 which is required to get good performance when accessing longs/doubles/pointers (the alternative would be to only add the padding for these types and specialize the length computation - unlikely to be worth the additional complexity).

On x86 the object header is 8 byte and the array overhead 4 byte, which gives us 36 byte.

On x64 the object header is 16 byte and the array overhead 8 byte. This gives us 24 + 24 = 48 byte.

For anyone wanting actual proof instead of empirical tests about the header size and alignment you can just go to the actual source: Here is the object definition of the coreclr. Check out the comment starting at line 178 which states:

// The only fields mandated by all objects are
// 
//     * a pointer to the code:MethodTable at offset 0
//     * a poiner to a code:ObjHeader at a negative offset. This is often zero.  It holds information that
//         any addition information that we might need to attach to arbitrary objects. 

You can also look at the actual code to see that those pointers are actual pointers and not DWORDs or anything else.

The code for aligning the object sizes is also in the same file:

#define PTRALIGNCONST (DATA_ALIGNMENT-1)

#ifndef PtrAlign
#define PtrAlign(size) \
    ((size + PTRALIGNCONST) & (~PTRALIGNCONST))
#endif //!PtrAlign

DATA_ALIGNMENT is defined as 4 for x86 (vm/i386/cgencpu.h) and ARM (vm/arm/cgencpu.h) and 8 for x64 (vm/amd64/cgencpu.h). The code itself is nothing but a standard optimized "round to next multiple of DATA_ALIGNMENT" assuming data alignment is a power of 2 method.

13 Comments

@hatchet I also added some references to the coreclr source code that support the claim as well. Never looked at that before but I'm surprised (probably shouldn't be there's only so much sensible hierarchies for a VM I guess) how similar the structure is to HotSpot - easy to find your way around.
As a result of its being an implementation detail it's useful to note this answer is completely inapplicable to Mono, where I get 56 bytes on x64.
@cat Including the variable pointing to the array?, That's what you should get in .NET as well... Read my answer.
Good answer, but you should link to the Jon Skeet post from which you stole the code sample, shamelessly or otherwise.
|
1

Speaking about x86 architecture, the answer of 44 bytes is not correct, because the object reference size in x86 is 4 bytes, not 8 bytes, thus the object length of 36 bytes + 4 bytes of reference to the object gives 40 bytes. Correct me if I'm wrong.

4 Comments

the answer 44 bytes is for 64-bit architecture. Please look through the question
@JohnSmith I'm refering to the answer from Tamir Vered, that states that 44 bytes is correct for 32-bit, which is not in my opinion
My bad, didn't get it from your comment.
@John Considering that the size for x64 is 48 byte without considering the pointer to the structure that's rather unlikely.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.