구조체 정렬이 필드 유형이 기본인지 사용자 정의인지에 따라 달라지는 이유는 무엇입니까?


121

에서 노다 시간 V2, 우리는 나노초 해상도로 이동하고 있습니다. 이는 우리가 관심있는 전체 시간 범위를 나타 내기 위해 더 이상 8 바이트 정수를 사용할 수 없음을 의미합니다. 이로 인해 Noda Time의 (많은) 구조체의 메모리 사용량을 조사하게되었고 결과적으로 저를 이끌었습니다. CLR의 정렬 결정에서 약간의 이상한 점을 발견했습니다.

첫째, 나는 이것이 실현 이다 기본 동작은 언제든지 변경 될 수 있습니다 구현 결정하고있다. 나는 것을 깨닫게 수 사용하여 수정 [StructLayout]하고 [FieldOffset],하지만 난 오히려 가능하면이 필요하지 않은 솔루션을 가지고 올 것입니다.

내 핵심 시나리오는 struct참조 유형 필드와 두 개의 다른 값 유형 필드를 포함하는이 필드에 대한 간단한 래퍼입니다 int. 나는 그것이 64 비트 CLR에서 16 바이트로 표현 되기를 바랐 지만 (참조 용으로 8 바이트, 나머지 용으로 4 바이트) 어떤 이유로 24 바이트를 사용하고 있습니다. 그런데 저는 배열을 사용하여 공간을 측정하고 있습니다. 상황에 따라 레이아웃이 다를 수 있다는 것을 이해하지만 이것은 합리적인 시작점처럼 느껴졌습니다.

다음은 문제를 보여주는 샘플 프로그램입니다.

using System;
using System.Runtime.InteropServices;

#pragma warning disable 0169

struct Int32Wrapper
{
    int x;
}

struct TwoInt32s
{
    int x, y;
}

struct TwoInt32Wrappers
{
    Int32Wrapper x, y;
}

struct RefAndTwoInt32s
{
    string text;
    int x, y;
}

struct RefAndTwoInt32Wrappers
{
    string text;
    Int32Wrapper x, y;
}    

class Test
{
    static void Main()
    {
        Console.WriteLine("Environment: CLR {0} on {1} ({2})",
            Environment.Version,
            Environment.OSVersion,
            Environment.Is64BitProcess ? "64 bit" : "32 bit");
        ShowSize<Int32Wrapper>();
        ShowSize<TwoInt32s>();
        ShowSize<TwoInt32Wrappers>();
        ShowSize<RefAndTwoInt32s>();
        ShowSize<RefAndTwoInt32Wrappers>();
    }

    static void ShowSize<T>()
    {
        long before = GC.GetTotalMemory(true);
        T[] array = new T[100000];
        long after  = GC.GetTotalMemory(true);        
        Console.WriteLine("{0}: {1}", typeof(T),
                          (after - before) / array.Length);
    }
}

그리고 내 랩톱의 컴파일 및 출력 :

c:\Users\Jon\Test>csc /debug- /o+ ShowMemory.cs
Microsoft (R) Visual C# Compiler version 12.0.30501.0
for C# 5
Copyright (C) Microsoft Corporation. All rights reserved.


c:\Users\Jon\Test>ShowMemory.exe
Environment: CLR 4.0.30319.34014 on Microsoft Windows NT 6.2.9200.0 (64 bit)
Int32Wrapper: 4
TwoInt32s: 8
TwoInt32Wrappers: 8
RefAndTwoInt32s: 16
RefAndTwoInt32Wrappers: 24

그래서:

  • 참조 유형 필드가없는 경우 CLR은 Int32Wrapper필드를 함께 압축 할 수 있습니다 ( TwoInt32Wrappers크기는 8).
  • 참조 유형 필드가 있어도 CLR은 여전히 int필드를 함께 압축 할 수 있습니다 ( RefAndTwoInt32s크기는 16).
  • 두 가지를 결합하면 각 Int32Wrapper필드가 8 바이트로 채워지거나 정렬 된 것처럼 보입니다. ( RefAndTwoInt32Wrappers크기는 24입니다.)
  • 디버거에서 동일한 코드를 실행하면 (하지만 여전히 릴리스 빌드) 크기가 12로 표시됩니다.

몇 가지 다른 실험에서도 비슷한 결과가 나왔습니다.

  • 값 유형 필드 뒤에 참조 유형 필드를 두는 것은 도움이되지 않습니다.
  • object대신 사용 하는 string것은 도움 이 되지 않습니다 ( "모든 참조 유형"일 것으로 예상).
  • 참조 주위의 "래퍼"로 다른 구조체를 사용하는 것은 도움이되지 않습니다.
  • 참조 주위의 래퍼로 일반 구조체를 사용하는 것은 도움이되지 않습니다.
  • 필드를 계속 추가하면 (단순성을 위해 쌍으로) int필드는 여전히 4 바이트로 Int32Wrapper계산 되고 필드는 8 바이트로 계산됩니다.
  • [StructLayout(LayoutKind.Sequential, Pack = 4)]시야에있는 모든 구조체에 추가해도 결과가 변경되지는 않습니다.

누구든지 이에 대한 설명 (이상적으로는 참조 문서 포함)이 있거나 상수 필드 오프셋 을 지정 하지 않고 필드를 압축하고 싶다는 CLR에 대한 힌트를 얻을 수있는 방법에 대한 제안이 있습니까?


1
실제로 사용하는 것 같지 Ref<T>않지만 string대신 사용 하고 있습니다.
— tvanfosson

2
두 개를 넣어 두 개의 구조체를 만들 TwoInt32Wrappers거나 an Int64and a TwoInt32Wrappers? 어떻게 일반적인를 만들 경우에 대한 Pair<T1,T2> {public T1 f1; public T2 f2;}다음 생성 Pair<string,Pair<int,int>>및 Pair<string,Pair<Int32Wrapper,Int32Wrapper>>? 어떤 조합이 JITter가 물건을 채우도록 강요합니까?
— supercat 2014-07-14

7
@supercat : 당신이 자신의 코드와 실험을 복사하는 것은 아마 최고 -하지만 Pair<string, TwoInt32Wrappers> 않습니다 단지 16 바이트를주고 그 문제를 해결 할 수 있도록. 매혹적인.
— Jon Skeet

9
@SLaks : 때때로 구조가 네이티브 코드로 전달 될 때 런타임은 모든 데이터를 다른 레이아웃의 구조로 복사합니다. Marshal.SizeOf.NET 코드의 구조 크기와 관련이없는 네이티브 코드로 전달되는 구조의 크기를 반환합니다.
— supercat 2014-07-14

5
흥미로운 관찰 : Mono는 올바른 결과를 제공합니다. 환경 : Unix 3.13.0.24 (64 비트)의 CLR 4.0.30319.17020 Int32Wrapper : 4 TwoInt32s : 8 TwoInt32Wrappers : 8 RefAndTwoInt32s : 16 RefAndTwoInt32Wrappers : 16
— AndreyAkinshin

답변:


85

나는 이것이 버그라고 생각합니다. 자동 레이아웃의 부작용이 있습니다. 64 비트 모드에서 8 바이트의 배수 인 주소에 중요하지 않은 필드를 정렬하는 것을 좋아합니다. [StructLayout(LayoutKind.Sequential)]속성 을 명시 적으로 적용한 경우에도 발생 합니다. 그것은 일어나지 않아야합니다.

구조체 멤버를 공개하고 다음과 같이 테스트 코드를 추가하여 확인할 수 있습니다.

    var test = new RefAndTwoInt32Wrappers();
    test.text = "adsf";
    test.x.x = 0x11111111;
    test.y.x = 0x22222222;
    Console.ReadLine();      // <=== Breakpoint here

중단 점에 도달하면 Debug + Windows + Memory + Memory 1을 사용합니다. 4 바이트 정수로 전환 &test하고 Address 필드에 입력합니다.

 0x000000E928B5DE98  0ed750e0 000000e9 11111111 00000000 22222222 00000000 

0xe90ed750e0내 컴퓨터의 문자열 포인터입니다 (당신의 것이 아닙니다). 쉽게 볼 수 있습니다Int32Wrappers크기를 24 바이트로 바꾸는 추가 4 바이트의 패딩으로을 . 구조체로 돌아가서 문자열을 마지막에 넣으십시오. 반복하면 문자열 포인터가 여전히 첫 번째 임을 알 수 있습니다 . 위반 LayoutKind.Sequential, 당신은 LayoutKind.Auto.

마이크로 소프트가이 문제를 고치도록 설득하는 것은 어려울 것입니다. 너무 오랫동안 이런 식으로 작동했기 때문에 어떤 변화도 깨질 것입니다. 무언가를 뜨리게 것 입니다. CLR [StructLayout]은 struct의 관리 버전 을 존중 하고 blittable로 만들 려고 시도 할 뿐이며 일반적으로 빠르게 포기합니다. DateTime을 포함하는 모든 구조체에 대해 유명합니다. 구조체를 마샬링 할 때만 진정한 LayoutKind 보장을받습니다. 마샬링 된 버전은 확실히 16 바이트 Marshal.SizeOf()입니다.

LayoutKind.Explicit당신이 듣고 싶었던 것이 아니라 그것을 고쳐 사용 하십시오.


7
"마이크로 소프트가이 문제를 고치도록 설득하는 것은 어려울 것입니다. 너무 오랫동안 이런 식으로 작동했기 때문에 어떤 변화로 인해 무언가를 깨뜨릴 것입니다." 이것이 32 비트 또는 모노에서 분명히 나타나지 않는다는 사실이 도움이 될 수 있습니다 (다른 의견에 따라).
— NPSF3000

StructLayoutAttribute의 문서는 매우 흥미 롭습니다. 기본적으로 blittable 유형 만 관리되는 메모리의 StructLayout을 통해 제어됩니다. 흥미 롭 네요.
— 마이클 방부제로 발효를 막다

@Soner 아니 그것은 그것을 고치지 않습니다. 두 필드의 레이아웃을 오프셋 8로 설정 했습니까? 그렇다면 x와 y는 동일하고 하나를 변경하면 다른 하나가 변경됩니다. Jon이 추구하는 것이 분명하지 않습니다.
— BartoszAdamczewski

적용한 string다른 새 참조 유형 ( class)으로 대체 [StructLayout(LayoutKind.Sequential)]해도 아무것도 변경되지 않는 것으로 보입니다. 반대 방향으로에 적용 [StructLayout(LayoutKind.Auto)]하면 struct Int32Wrapper에서 메모리 사용량 이 변경됩니다 TwoInt32Wrappers.
— Jeppe의 Stig 닐슨

1
"마이크로 소프트가이 문제를 고치도록 설득하는 것은 어려울 것입니다. 너무 오랫동안 이런 식으로 작동했기 때문에 어떤 변화로 인해 무언가를 깨뜨릴 것입니다." xkcd.com/1172
— iCodeSometime

19

EDIT2

struct RefAndTwoInt32Wrappers
{
    public int x;
    public string s;
}

이 코드는 8 바이트로 정렬되므로 구조체는 16 바이트를 갖게됩니다. 비교하면 다음과 같습니다.

struct RefAndTwoInt32Wrappers
{
    public int x,y;
    public string s;
}

4 바이트로 정렬되므로이 구조체도 16 바이트를 갖게됩니다. 따라서 여기의 이론적 근거는 CLR의 구조체 결합이 가장 많이 정렬 된 필드의 수에 의해 결정된다는 것입니다. clases는 분명히 그렇게 할 수 없으므로 8 바이트 정렬 상태로 유지됩니다.

이제 모든 것을 결합하고 구조체를 생성하면 :

struct RefAndTwoInt32Wrappers
{
    public int x,y;
    public Int32Wrapper z;
    public string s;
}

24 바이트 {x, y}는 각각 4 바이트, {z, s}는 8 바이트입니다. 구조체에 ref 유형을 도입하면 CLR은 항상 사용자 지정 구조체를 클래스 정렬과 일치하도록 정렬합니다.

struct RefAndTwoInt32Wrappers
{
    public Int32Wrapper z;
    public long l;
    public int x,y;  
}

Int32Wrapper는 길이와 동일하게 정렬되므로이 코드는 24 바이트를 갖게됩니다. 따라서 사용자 지정 구조체 래퍼는 항상 구조에서 가장 높은 / 가장 정렬 된 필드 또는 자체 내부 가장 중요한 필드에 정렬됩니다. 따라서 8 바이트 정렬 된 참조 문자열의 경우 구조체 래퍼가 이에 정렬됩니다.

구조체 내부의 최종 사용자 지정 구조체 필드는 항상 구조체에서 가장 높게 정렬 된 인스턴스 필드에 정렬됩니다. 이제 이것이 버그인지 확실하지 않지만 증거가 없으면 이것이 의식적인 결정일 수 있다는 내 의견을 고수 할 것입니다.


편집하다

크기는 실제로 힙에 할당 된 경우에만 정확하지만 구조체 자체의 크기는 더 작습니다 (필드의 정확한 크기). 추가 분석을 통해 이것이 CLR 코드의 버그 일 수 있지만 증거로 백업해야 함을 제안합니다.

나는 cli 코드를 검사하고 유용한 것이 발견되면 추가 업데이트를 게시 할 것입니다.


이것은 .NET mem 할당 자에서 사용하는 정렬 전략입니다.

public static RefAndTwoInt32s[] test = new RefAndTwoInt32s[1];

static void Main()
{
    test[0].text = "a";
    test[0].x = 1;
    test[0].x = 1;

    Console.ReadKey();
}

x64에서 .net40으로 컴파일 된이 코드는 WinDbg에서 다음을 수행 할 수 있습니다.

먼저 힙에서 유형을 찾을 수 있습니다.

    0:004> !dumpheap -type Ref
       Address               MT     Size
0000000003e72c78 000007fe61e8fb58       56    
0000000003e72d08 000007fe039d3b78       40    

Statistics:
              MT    Count    TotalSize Class Name
000007fe039d3b78        1           40 RefAndTwoInt32s[]
000007fe61e8fb58        1           56 System.Reflection.RuntimeAssembly
Total 2 objects

일단 우리가 그 주소 아래에 무엇이 있는지 볼 수 있습니다.

    0:004> !do 0000000003e72d08
Name:        RefAndTwoInt32s[]
MethodTable: 000007fe039d3b78
EEClass:     000007fe039d3ad0
Size:        40(0x28) bytes
Array:       Rank 1, Number of elements 1, Type VALUETYPE
Fields:
None

이것이 ValueType이고 우리가 생성 한 것임을 알 수 있습니다. 이것이 배열이기 때문에 배열에있는 단일 요소의 ValueType def를 가져와야합니다.

    0:004> !dumparray -details 0000000003e72d08
Name:        RefAndTwoInt32s[]
MethodTable: 000007fe039d3b78
EEClass:     000007fe039d3ad0
Size:        40(0x28) bytes
Array:       Rank 1, Number of elements 1, Type VALUETYPE
Element Methodtable: 000007fe039d3a58
[0] 0000000003e72d18
    Name:        RefAndTwoInt32s
    MethodTable: 000007fe039d3a58
    EEClass:     000007fe03ae2338
    Size:        32(0x20) bytes
    File:        C:\ConsoleApplication8\bin\Release\ConsoleApplication8.exe
    Fields:
                      MT    Field   Offset                 Type VT     Attr            Value Name
        000007fe61e8c358  4000006        0            System.String      0     instance     0000000003e72d30     text
        000007fe61e8f108  4000007        8             System.Int32      1     instance                    1     x
        000007fe61e8f108  4000008        c             System.Int32      1     instance                    0     y

16 바이트가 패딩 용으로 예약되어 있으므로 구조는 실제로 32 바이트이므로 실제로 모든 구조는 처음부터 최소 16 바이트 크기입니다.

int와 문자열 참조에서 16 바이트를 추가하면 0000000003e72d18 + 8 바이트 EE / 패딩이됩니다. 0000000003e72d30이되고 이것은 문자열 참조의 시작 지점이며 모든 참조는 첫 번째 실제 데이터 필드에서 8 바이트가 채워 지므로 이것은이 구조의 32 바이트를 차지합니다.

문자열이 실제로 그렇게 채워져 있는지 살펴 보겠습니다.

0:004> !do 0000000003e72d30    
Name:        System.String
MethodTable: 000007fe61e8c358
EEClass:     000007fe617f3720
Size:        28(0x1c) bytes
File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String:      a
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fe61e8f108  40000aa        8         System.Int32  1 instance                1 m_stringLength
000007fe61e8d640  40000ab        c          System.Char  1 instance               61 m_firstChar
000007fe61e8c358  40000ac       18        System.String  0   shared           static Empty
                                 >> Domain:Value  0000000001577e90:NotInit  <<

이제 위의 프로그램을 같은 방식으로 분석해 보겠습니다.

public static RefAndTwoInt32Wrappers[] test = new RefAndTwoInt32Wrappers[1];

static void Main()
{
    test[0].text = "a";
    test[0].x.x = 1;
    test[0].y.x = 1;

    Console.ReadKey();
}

0:004> !dumpheap -type Ref
     Address               MT     Size
0000000003c22c78 000007fe61e8fb58       56    
0000000003c22d08 000007fe039d3c00       48    

Statistics:
              MT    Count    TotalSize Class Name
000007fe039d3c00        1           48 RefAndTwoInt32Wrappers[]
000007fe61e8fb58        1           56 System.Reflection.RuntimeAssembly
Total 2 objects

이제 구조체는 48 바이트입니다.

0:004> !dumparray -details 0000000003c22d08
Name:        RefAndTwoInt32Wrappers[]
MethodTable: 000007fe039d3c00
EEClass:     000007fe039d3b58
Size:        48(0x30) bytes
Array:       Rank 1, Number of elements 1, Type VALUETYPE
Element Methodtable: 000007fe039d3ae0
[0] 0000000003c22d18
    Name:        RefAndTwoInt32Wrappers
    MethodTable: 000007fe039d3ae0
    EEClass:     000007fe03ae2338
    Size:        40(0x28) bytes
    File:        C:\ConsoleApplication8\bin\Release\ConsoleApplication8.exe
    Fields:
                      MT    Field   Offset                 Type VT     Attr            Value Name
        000007fe61e8c358  4000009        0            System.String      0     instance     0000000003c22d38     text
        000007fe039d3a20  400000a        8             Int32Wrapper      1     instance     0000000003c22d20     x
        000007fe039d3a20  400000b       10             Int32Wrapper      1     instance     0000000003c22d28     y

여기서 상황은 동일합니다. 0000000003c22d18 + 8 바이트의 문자열 참조를 추가하면 값이 실제로 우리가있는 주소를 가리키는 첫 번째 Int 래퍼의 시작 부분에서 끝납니다.

이제 각 값이 객체 참조임을 다시 확인할 수 있습니다. 0000000003c22d20을 살펴보면서 확인할 수 있습니다.

0:004> !do 0000000003c22d20
<Note: this object has an invalid CLASS field>
Invalid object

실제로 그것이 obj 또는 vt이면 주소가 우리에게 아무것도 알려주지 않는 구조체이기 때문에 정확합니다.

0:004> !dumpvc 000007fe039d3a20   0000000003c22d20    
Name:        Int32Wrapper
MethodTable: 000007fe039d3a20
EEClass:     000007fe03ae23c8
Size:        24(0x18) bytes
File:        C:\ConsoleApplication8\bin\Release\ConsoleApplication8.exe
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fe61e8f108  4000001        0         System.Int32  1 instance                1 x

따라서 실제로 이것은 이번에 8 바이트 정렬되는 Union 유형과 더 비슷합니다 (모든 패딩은 부모 구조체와 정렬 됨). 그렇지 않다면 20 바이트로 끝날 것이고 그것은 최적이 아니므로 mem 할당자는 결코 그것을 허용하지 않을 것입니다. 다시 수학을하면 구조체의 크기가 실제로 40 바이트임을 알 수 있습니다.

따라서 메모리를 좀 더 보수적으로 사용하려면 구조체 사용자 지정 구조체 형식으로 압축하지 말고 대신 간단한 배열을 사용하십시오. 또 다른 방법은 힙 (예 : VirtualAllocEx)에서 메모리를 할당하는 것입니다. 이렇게하면 고유 한 메모리 블록이 제공되고 원하는 방식으로 관리 할 수 ​​있습니다.

여기서 마지막 질문은 왜 갑자기 우리가 그런 레이아웃을 얻을 수 있는지입니다. int [] 증분의 지 티드 코드와 성능을 struct []와 카운터 필드 증분과 비교하면 두 번째는 유니온이되는 8 바이트 정렬 주소를 생성하지만 지팅하면 더 최적화 된 어셈블리 코드로 변환됩니다 (단일 LEA 대 다중 MOV). 그러나 여기에 설명 된 경우 성능이 실제로 더 나빠질 것이므로 여러 필드를 가질 수있는 사용자 지정 형식이므로 기본 CLR 구현과 일치하므로 시작 주소 대신 시작 주소를 입력하는 것이 더 쉬울 수 있습니다. value (불가능하기 때문에) 그리고 거기에서 구조체 패딩을 수행하여 더 큰 바이트 크기를 만듭니다.


1
이것을 직접 보면 크기 RefAndTwoInt32Wrappers 는 32 바이트 가 아니라 24 바이트입니다. 이는 제 코드에서보고 한 것과 같습니다. 를 사용하는 대신 메모리 뷰를 dumparray보고 구별 가능한 값이있는 3 개의 요소가있는 배열의 메모리를 살펴보면 각 요소가 8 바이트 문자열 참조와 두 개의 8 바이트 정수로 구성되어 있음을 분명히 알 수 있습니다. . dumparray값을 표시하는 방법을 모르기 때문에 값을 참조로 표시하고 있다고 생각 Int32Wrapper합니다. 이러한 "참조"는 자신을 가리 킵니다. 그들은 별도의 값이 아닙니다.
— 존 소총

1
"16 바이트 패딩"을 어디서 얻을 수 있는지 잘 모르겠지만 "16 바이트 + 개수 * 요소 크기"가 될 배열 객체의 크기를보고 있기 때문일 수 있습니다. 따라서 개수가 2 인 배열의 크기는 72 (16 + 2 * 24) dumparray입니다.
— Jon Skeet

@jon 구조체를 dumpheap하고 힙에서 얼마나 많은 공간을 차지하는지 확인 했습니까? 일반적으로 어레이 크기는 어레이의 시작 부분에 유지되며이를 확인할 수도 있습니다.
— BartoszAdamczewski

@jon보고 된 크기에는 8에서 시작하는 문자열의 오프셋도 포함되어 있습니다. 언급 된 추가 8 바이트가 배열에서 나온 것이라고 생각하지 않습니다. 대부분의 배열 항목이 첫 번째 요소 주소 앞에 있기 때문입니다. 그것에 대해 언급하십시오.
— BartoszAdamczewski

1
아니요, ThreeInt32Wrappers는 12 바이트, FourInt32Wrappers는 16, FiveInt32Wrappers는 20입니다. 레이아웃을 크게 변경하는 참조 유형 필드 추가에 대해 논리적으로 볼 수 없습니다. 필드가 유형일 때 8 바이트 정렬을 무시하는 것이 매우 기쁩니다 Int32. 솔직히 말해서 스택에서하는 일에 대해 너무 신경 쓰지 않지만 확인하지 않았습니다.
— Jon Skeet 2014

9

요약은 아마도 위의 @Hans Passant의 답변을 참조하십시오. Layout Sequential이 작동하지 않습니다.


일부 테스트 :

그것은 확실히 64 비트에만 있고 객체 참조는 구조체를 "독"시킵니다. 32 비트는 당신이 기대하는 것을합니다 :

Environment: CLR 4.0.30319.34209 on Microsoft Windows NT 6.2.9200.0 (32 bit)
ConsoleApplication1.Int32Wrapper: 4
ConsoleApplication1.TwoInt32s: 8
ConsoleApplication1.TwoInt32Wrappers: 8
ConsoleApplication1.ThreeInt32Wrappers: 12
ConsoleApplication1.Ref: 4
ConsoleApplication1.RefAndTwoInt32s: 12
ConsoleApplication1.RefAndTwoInt32Wrappers: 12
ConsoleApplication1.RefAndThreeInt32s: 16
ConsoleApplication1.RefAndThreeInt32Wrappers: 16

객체 참조가 추가 되 자마자 모든 구조체는 4 바이트 크기가 아닌 8 바이트로 확장됩니다. 테스트 확장 :

Environment: CLR 4.0.30319.34209 on Microsoft Windows NT 6.2.9200.0 (64 bit)
ConsoleApplication1.Int32Wrapper: 4
ConsoleApplication1.TwoInt32s: 8
ConsoleApplication1.TwoInt32Wrappers: 8
ConsoleApplication1.ThreeInt32Wrappers: 12
ConsoleApplication1.Ref: 8
ConsoleApplication1.RefAndTwoInt32s: 16
ConsoleApplication1.RefAndTwoInt32sSequential: 16
ConsoleApplication1.RefAndTwoInt32Wrappers: 24
ConsoleApplication1.RefAndThreeInt32s: 24
ConsoleApplication1.RefAndThreeInt32Wrappers: 32
ConsoleApplication1.RefAndFourInt32s: 24
ConsoleApplication1.RefAndFourInt32Wrappers: 40

참조가 추가 되 자마자 모든 Int32Wrapper가 8 바이트가되므로 단순한 정렬이 아닙니다. LoH 할당이 다르게 정렬 된 경우 배열 할당을 줄였습니다.


4

믹스에 데이터를 추가하기 위해 보유한 유형에서 하나 이상의 유형을 만들었습니다.

struct RefAndTwoInt32Wrappers2
{
    string text;
    TwoInt32Wrappers z;
}

프로그램은 다음을 기록합니다.

RefAndTwoInt32Wrappers2: 16

따라서 TwoInt32Wrappers구조체가 새 RefAndTwoInt32Wrappers2구조체 에서 제대로 정렬 된 것처럼 보입니다 .


64 비트를 실행하고 있습니까? 32 비트에서 정렬이
— Ben Adams

내 발견은 다양한 환경에서 다른 사람들과 동일합니다.
— Jesse C. Slicer
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.