windbg - SOS DumpMT의 "BaseSize", "ComponentSize" 값에 대한 의미
일례로 DumpMT를 통해 object [] 타입을 검사하면 다음과 같은 내용을 보게 됩니다.
0:000> !DumpMT 00007ff9624f6fc0
EEClass: 00007ff961ebaa00
Module: 00007ff961df1000
Name: System.Object[]
mdToken: 0000000002000000
File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
BaseSize: 0x18
ComponentSize: 0x8
Slots in VTable: 28
Number of IFaces in IFaceMap: 6
가만 보니 대부분의 타입은 ComponentSize가 8로 나옵니다. 검색으로 찾아봐도 딱히 설명이 없군요. 그래서 이번에도 지난번에 했던 것처럼,
windbg - SOS DumpClass/DumpMT의 "Vtable Slots", "Total Method Slots", "Slots in VTable" 값에 대한 의미
; https://www.sysnet.pe.kr/2/0/11337
제 나름대로 규칙을 정리해 봤습니다. ^^
우선, ComponentSize는 해당 객체를 표현하는 메모리 구조에서 "요소의 수"가 있는 경우에만 1 이상의 값을 갖습니다. 즉, "요소"가 하나 늘어날 때마다 증가하는 메모리의 바이트를 나타내는 것입니다. 이런 유형의 타입으로 바로 배열이 있습니다.
.NET Array는 왜 12bytes의 기본 메모리를 점유할까?
; https://www.sysnet.pe.kr/2/0/1173
* 위의 경우는 x86에서 테스트했기 때문에 12(0xc)바이트 기본 점유이지만, x64에서는 24(0x18)바이트입니다.
그리고 BaseSize는 요소를 하나도 갖고 있지 않은 경우에도 기본적으로 차지하는 메모리를 의미합니다. 따라서 다음과 같은 공식으로 정리할 수 있습니다.
(배열) 객체의 크기 = BaseSize + (# of elements * ComponentSize)
확인을 해볼까요? ^^
우선, char 배열로 그 수가 0, 1, 2인 배열에 대해서 보겠습니다.
new char[0]
위의 객체를 dumpobj로 출력하면 그 크기가 나옵니다.
0:000> !DumpObj /d 000001d312232e60
Name: System.Char[]
MethodTable: 00007ff9624f7a98
EEClass: 00007ff961ebabc0
Size: 24(0x18) bytes
Array: Rank 1, Number of elements 0, Type Char (Print Array)
Content:
Fields:
None
요소의 수가 하나도 없는데도 0x18 바이트를 차지하는 것을 보니 아마도 char[] 타입의 BaseSize는 0x18일 것입니다. DumpMT로 확인해 보면 예상대로입니다.
0:000> !DumpMT /d 00007ff9624f7a98
EEClass: 00007ff961ebabc0
Module: 00007ff961df1000
Name: System.Char[]
mdToken: 0000000002000000
File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
BaseSize: 0x18
ComponentSize: 0x2
Slots in VTable: 28
Number of IFaces in IFaceMap: 6
이에 기반을 둬서 char[1], char[2] 배열의 값을 자동으로 다음과 같이 계산할 수 있습니다.
char[1] = BaseSize 0x18 + (# of elements * ComponentSize 0x2) = 0x1a
char[2] = BaseSize 0x18 + (# of elements * ComponentSize 0x2) = 0x1c
(실제 크기는 DumpObj로 확인할 수 있고 위의 값이 맞습니다.)
byte [] 배열에 대해서도 마찬가지입니다. byte 요소의 크기가 1이므로 이럴 때는 다음과 같이 크기가 계산됩니다.
byte[0] = BaseSize 0x18 + 0 * 1 = 0x18
byte[1] = BaseSize 0x18 + 1 * 1 = 0x19
byte[2] = BaseSize 0x18 + 2 * 1 = 0x1a
그렇다면 값 형식의 경우 다음과 같은 공식으로 일반화할 수 있습니다.
[요소가 값 형식의 경우]
(배열) 객체의 크기 = BaseSize + (# of elements * sizeof(element))
실제로 다음과 같이 struct로 새로운 값 형식을 만들어,
public struct ST
{
int _i;
int _j;
int _k;
}
공식을 확인할 수 있습니다.
ComponentSize = sizeof(ST) = 0xc
ST[0] = BaseSize 0x18 + 0 * 0xc = 0x18
ST[1] = BaseSize 0x18 + 1 * 0xc = 0x24
ST[2] = BaseSize 0x18 + 2 * 0xc = 0x30
그렇다면 참조 형식은 어떨까요? 참조 형식의 경우 배열 내에 참조 값에 대한 주소만 보관하고 있기 때문에 ComponentSize는 언제나 포인터 크기가 됩니다.
[요소가 참조 형식의 경우]
(배열) 객체의 크기 = BaseSize + (# of elements * 8) // 64비트인 경우
(배열) 객체의 크기 = BaseSize + (# of elements * 4) // 32비트인 경우
확인을 위해 다음과 같은 간단한 class를 만들고,
public class CT
{
int _i = 1;
int _j = 2;
int _k = 3;
}
공식을 확인할 수 있습니다.
ComponentSize = x64 포인터 크기 0x8 (x86 포인터 크기 0x4)
CT[0] = BaseSize 0x18 + 0 * 0x8 = 0x18
CT[1] = BaseSize 0x18 + 1 * 0x8 = 0x20
CT[2] = BaseSize 0x18 + 2 * 0x8 = 0x38
여기서 예외가 하나 있는 것이 바로 System.String입니다.
windbg - .NET string의 x86/x64 메모리 할당 구조
; https://www.sysnet.pe.kr/2/0/11336
System.String은 배열은 아니지만 내부 메모리 구조는 char 배열과 유사합니다. 단, null 처리로 인한 2바이트 추가로 인해 다음과 같이 공식이 바뀝니다.
string 객체의 크기 = BaseSize + (# of elements * ComponentSize) + 2 (null)
= BaseSize + (# of elements * sizeof(char)) + 2 (null)
이 공식을 사용하면 크기를 다음과 같이 자동으로 구할 수 있습니다.
new string('a', 0) = 0x18 + 0 * 2 + 2 = 0x1a
new string('a', 1) = 0x18 + 1 * 2 + 2 = 0x1c
new string('a', 2) = 0x18 + 2 * 2 + 2 = 0x1e
new string('a', 3) = 0x18 + 3 * 2 + 2 = 0x20
정리해 보면, x86/x64에 따라 다음과 같이 일반화할 수 있습니다.
[x86]
BaseSize = 0xc (0n12)
값 형식: 객체의 크기 = 12 + (# of elements * sizeof(element))
참조 형식: 객체의 크기 = 12 + (# of elements * 4)
System.String 크기 = 12 + (# of elements * 2) + 2
[x64]
BaseSize = 0x18 (0n24)
값 형식: 객체의 크기 = 24 + (# of elements * sizeof(element))
참조 형식: 객체의 크기 = 24 + (# of elements * 8)
System.String 크기 = 24 + (# of elements * 2) + 2
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]