Microsoft MVP성태의 닷넷 이야기
.NET Framework: 774. C# - blittable 타입이란? [링크 복사], [링크+제목 복사]
조회: 13854
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 4개 있습니다.)

C# - blittable 타입이란?

닷넷에서 blittable 타입에 대한 공식 문서는 다음에서 찾아볼 수 있습니다.

Blittable and Non-Blittable Types
; https://docs.microsoft.com/en-us/dotnet/framework/interop/blittable-and-non-blittable-types

위의 문서에 따르면 기존 타입들 중 다음의 것들이 blittable 타입입니다.

System.Byte (byte)
System.SByte (sbyte)
System.Int16 (short)
System.UInt16 (ushort)
System.Int32 (int)
System.UInt32 (uint)
System.Int64 (long)
System.UInt64 (ulong)
System.IntPtr
System.UIntPtr
System.Single (float)
System.Double (double)

이와 함께 다음의 것들도 blittable 타입입니다.

  • One-dimensional arrays of blittable types, such as an array of integers. However, a type that contains a variable array of blittable types is not itself blittable.
  • Formatted value types that contain only blittable types (and classes if they are marshaled as formatted types). For more information about formatted value types, see Default Marshaling for Value Types.

첫 번째 조건을 풀어보면, blittable types의 1차원 배열도 blittable 타입이지만, 반면 배열을 포함한 타입은 blittable 타입이 아니라는 것입니다. 예를 들어 다음의 arr은 blittable 타입의 인스턴스이지만,

void Main()
{
    int [] arr = new int [100];
}

다음의 TestStruct 타입은 배열을 포함하고 있으므로 blittable 타입이 아닙니다.

struct TestStruct
{
    public int Age;       // int만 담고 있다면 TestStruct도 blittable 타입이겠지만.
    public int [] Scores; // 배열을 담고 있으므로 TestStruct는 blittable 타입이 아님.
}

두 번째 조건은 복합 타입(complex type)인 경우에 대해 blittable 조건을 명시하고 있습니다. 멤버로 blittable 타입, 또는 formatted 타입으로 마샬링 가능한 클래스만을 필드로 포함하는 "Formatted value types"라면 그 타입은 blittable이라는 것입니다. "Formatted" + "value types"이기 때문에 기본적으로 class 타입은 무조건 blittable이 아닙니다. 그리고 나머지 "Formatted 타입"이라는 것은 다음의 문서에서 설명하고 있습니다.

Default Marshaling for Value Types
; https://docs.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/0t2cwe11(v=vs.100)

A formatted type is a complex type that contains information that explicitly controls the layout of its members in memory.

결국 타입이 담고 있는 필드들에 대한 메모리 상의 정보를 가지고 있다면 "A formatted type"입니다. 그리고 그 정보라는 것은 StructLayout 특성과 연결됩니다.

눈으로 확인하는 LayoutKind 옵션 효과
; https://www.sysnet.pe.kr/2/0/1558

정리해 보면, "Formatted value types"는 값 형식 중에서 LayoutKind.Auto가 아닌 형식을 의미합니다. 모든 타입은 LayoutKind가 명시되어 있지 않으면 Auto가 되지만, 특별히 C# 컴파일러는 struct 타입에 한해 (사용자가 지정하지 않았다면) 자동으로 LayoutKind.Sequential을 지정해 줍니다. 따라서 사용자가 굳이 Auto로 지정하지 않은 struct 타입에 대해 오직 blittable 타입만을 필드로 포함하고 있다면 그 formatted value type도 blittable 타입이 됩니다.

따라서 struct인 경우 다음은 blittable이지만,

struct BlittableStructType
{
    public int Age;
}

다음의 struct들은 blittable이 아닙니다.

[StructLayout(LayoutKind.Auto)] // Auto Layout이므로.
struct AutoLayoutStructType
{
    public int Age;
}

struct NonBlittableStructType_HasString
{
    public int Age;
    public string Name; // 참조 타입인 string을 포함
}

struct NonBlittableStructType_HasArray
{
    public int Age;
    public int [] data; // 참조 타입인 배열을 포함
}




일단 문서는 그렇다 치고, 개인적으로 한 가지 혼란스러운 문구가 하나 있습니다.

Formatted value types that contain only blittable types (and classes if they are marshaled as formatted types). For more information about formatted value types, see Default Marshaling for Value Types.

위에서 말하는 "classes"에 속한 타입은 다음과 같은 것들을 의미합니다.

[StructLayout(LayoutKind.Explicit)]
class ExplicitClassType
{
    [FieldOffset(0)]
    public int Age;
}

그리고 문서에 의하면 저런 "classes"들을 포함하는 "Formatted value types"가 blittable하다고 합니다. 예를 들어 다음과 같은 struct가 blittable일 수 있습니다.

struct StructType
{
    public int Age;
    public ExplicitClassType data1;
}

blittable이 되기 위한 기본 조건은 문서의 처음에 언급한 대로,

Most data types have a common representation in both managed and unmanaged memory and do not require special handling by the interop marshaler. These types are called blittable types because they do not require conversion when they are passed between managed and unmanaged code.

해당 타입의 필드들이 managed/unmanaged 메모리에서 동일한 구성을 갖고 있어 "interop marshaler"에 의한 별도 처리가 필요하지 않은 것입니다. 좀 더 간단하게 표현하면, 그 인스턴스가 존재하는 메모리를 포인터로 가리킬 때 그 주소 영역에 모든 필드의 값들이 포함되어 있어야 합니다. 하지만 위의 StructType은 ExplicitClassType이 "marshaled as formatted types"에 속하고 따라서 그것의 메모리 크기까지 모두 계산이 되어 할당은 되지만 그래도 managed <-> unmanaged 간의 마샬링 절차가 필요합니다. 즉, 아래에서 설명할 interop marshaler를 고려해 보면 ExplicitClassType을 포함한 StructType 타입이 blittable 타입이라고 볼 수 없는 것입니다.




비록, 인스턴스의 필드들이 메모리의 연속된 공간에 펼쳐져 있지만 "do not require special handling by the interop marshaler" 조건을 만족시키지 못해 blittable이 아닌 타입도 있습니다. "Blittable and Non-Blittable Types" 문서에도 나오지만,

System.Array
System.Boolean
System.Char
System.Class
System.Object
System.Mdarray
System.String
System.Valuetype
System.Szarray

위의 것들은 Non-Blittable입니다. 이상한 것은 이 중에서 3개(System.Class, System.Mdarray, System.Szarray)는 원래 .NET BCL에 존재하지 않는 타입이고, 또 다른 3개(System.Array, System.Object, System.ValueType)는 딱히 자료형으로 쓰지 않는 타입입니다. 따라서 남은 자료형은 3개인데,

System.Boolean
System.Char
System.String

이게 왜 Non-Blittable이 아닌지 살짝 이해가 안 될 수도 있습니다. 이유는, 문서 상으로 보면 위의 것들은 interop marshaler가 그 중간에서 작업을 해줘야 하기 때문입니다.

우선 System.Boolean의 경우, 1바이트인데 unmanaged 공간에서는 BOOL 타입이 C/C++라면 1 바이트이거나 COM VARIANT에서는 short이기 때문에 중간에 interop marshaler가 변환 처리를 해야 합니다. (심지어 4바이트가 될 때도 있다고 합니다.)

System.Char는 managed 공간에서는 2바이트지만 unmanaged 공간으로 갈 때는 ANSI/UNICODE 2가지 버전으로 나뉠 수 있기 때문에 역시 중간에서 interop marshaler가 관여를 합니다.

마지막으로 System.String은 결국 System.Char의 연속 공간이기 때문에 마찬가지로 interop marshaler가 관여를 합니다. 재미있는 것은 System.String의 경우 설령 interop marshaler가 관여를 하지 않는다 해도 blittable 타입이 될 수 없습니다. 확인을 위해 .NET Reflector로 보면,

[Serializable, ComVisible(true), __DynamicallyInvokable]
public sealed class String : IComparable, ICloneable, IConvertible, IEnumerable, IComparable<string>, IEnumerable<char>, IEquatable<string>
{
    // Fields
    // ...[생략]...
    [NonSerialized]
    private char m_firstChar;
    [NonSerialized]
    private int m_stringLength;
    // ...[생략]...
}

System.String 타입이 포함하고 있는 필드는 char m_firstChar, int m_stringLength일 뿐 문자 배열 자체는 CLR 내부에 의해 숨겨져 있기 때문입니다.

그 이외의 타입들에 대해서는 interop marshaler는 대상 타입이 참조 객체를 포함하지 않고 LayoutKind.Auto만 아니라면 관여하지 않을 수 있습니다. 하지만, 그 이외의 경우라면 관여를 하게 됩니다.

우선 대상 타입이 Sequential이고, 그것이 ("classes if they are marshaled as formatted types" 조건에 해당하는) 참조 객체를 포함하고 있는 경우 managed와 unmanaged 간의 메모리 크기도 상이할뿐더러 필드 배치의 순서까지도 달라질 수 있으므로 당연히 interop marshaler가 중재할 수밖에 없습니다.

또한 대상 타입이 Explicit 일지라도 managed와 unmanaged 간의 메모리 구조가 동일하다는 것만 빼고는 여전히 managed일 때 참조 타입인 필드에 대해서는 힙 주소만을 포함하고 있다가 unmanaged로 마샬링이 되어야 할 때 interop marshaler의 중재로 메모리가 동일하게 바뀌게 됩니다. 이 때문에 "Formatted value types"가 blittable이 되려면 어찌 되었건 필드로 참조 타입을 가져서는 안됩니다.




그건 그렇고, 혹시 해당 타입이 blittable 타입인지 코드로 알 수 있을까요? 아쉽게도 닷넷 차원에서는 이것을 알 수 있는 직접적인 정보를 Type 클래스에서 제공하지 않습니다. 예전에도 ^^ 값 형식인지 참조 형식인지에 대한 구분도 메타데이터에서 제공하지 않는다고 했었는데요.

닷넷 메타데이터에 struct/class(값/참조 형식)의 구분이 있을까요?
; https://www.sysnet.pe.kr/2/0/10993

검색해 보면, 적당하게 다음의 코드가 눈에 띕니다.

The fastest way to check if a type is blittable?
; https://stackoverflow.com/questions/10574645/the-fastest-way-to-check-if-a-type-is-blittable

BlittableStructs/BlittableStructs/BlittableHelper.cs
; https://github.com/AndreyAkinshin/BlittableStructs/blob/master/BlittableStructs/BlittableHelper.cs

그런데, 제가 정리한 이 글에서의 요구를 만족시키진 않습니다. 왜냐하면 참조 형식(class)도 blittable이라고 반환하는 경우가 있기 때문입니다. 따라서 이 글에서 정리한 조건을 만족하려면 다음과 같이 if 문 하나를 더 추가해야 합니다.

public static class IsBlittableCache<T>
{
    public static bool IsBlittable()
    {
        return IsBlittableCache<T>.Value;
    }

    public static bool IsBlittable(Type type)
    {
        if (type.IsArray)
        {
            var elem = type.GetElementType();
            return elem.IsValueType && IsBlittable(elem);
        }

        if (type.IsValueType == false)
        {
            return false;
        }

        try
        {
            object instance = FormatterServices.GetUninitializedObject(type);
            GCHandle.Alloc(instance, GCHandleType.Pinned).Free();
            return true;
        }
        catch
        {
            return false;
        }
    }

    public static readonly bool Value = IsBlittable(typeof(T));
}

위의 코드로 몇몇 타입을 테스트해 보면 다음과 같은 결과를 얻을 수 있습니다. (아래의 출력에 사용된 타입의 구체적인 정의는 첨부 파일에서 확인할 수 있습니다.)

ClassType: False
SequentialClassType: False
SequentialClassType_HasString: False
ExplicitClassType: False
ExplicitClassType_HasString: False
AutoLayoutStructType: False
BlittableStructType: True
NonBlittableStructType_HasClassWithLayoutInfo: False
NonBlittableStructType_HasClassWithLayoutInfo2: False
NonBlittableStructType_HasClassWithLayoutInfo3: False
NonBlittableStructType_HasString: False
NonBlittableStructType_HasArray: False
System.Boolean: False
System.Char: False
System.Int32[]: True
System.Boolean[]: False
BlittableStructType[]: True
System.String: False

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 7/13/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 16  17  18  19  20  21  22  23  24  25  26  27  28  29  [30]  ...
NoWriterDateCnt.TitleFile(s)
12869정성태12/8/20218255개발 환경 구성: 613. git clone 실행 시 fingerprint 묻는 단계를 생략하는 방법
12868정성태12/7/20216827오류 유형: 770. twine 업로드 시 "HTTPError: 400 Bad Request ..." 오류 [1]
12867정성태12/7/20216539개발 환경 구성: 612. 파이썬 - PyPI 패키지 만들기 (3) entry_points 옵션
12866정성태12/7/202113911오류 유형: 769. "docker build ..." 시 "failed to solve with frontend dockerfile.v0: failed to read dockerfile ..." 오류
12865정성태12/6/20216606개발 환경 구성: 611. 파이썬 - PyPI 패키지 만들기 (2) long_description, cmdclass 옵션
12864정성태12/6/20215084Linux: 46. WSL 환경에서 find 명령을 사용해 파일을 찾는 방법
12863정성태12/4/20216966개발 환경 구성: 610. 파이썬 - PyPI 패키지 만들기
12862정성태12/3/20215716오류 유형: 768. Golang - 빌드 시 "cmd/go: unsupported GOOS/GOARCH pair linux /amd64" 오류
12861정성태12/3/20217941개발 환경 구성: 609. 파이썬 - "Windows embeddable package"로 개발 환경 구성하는 방법
12860정성태12/1/20216048오류 유형: 767. SQL Server - 127.0.0.1로 접속하는 경우 "Access is denied"가 발생한다면?
12859정성태12/1/202112193개발 환경 구성: 608. Hyper-V 가상 머신에 Console 모드로 로그인하는 방법
12858정성태11/30/20219450개발 환경 구성: 607. 로컬의 USB 장치를 원격 머신에 제공하는 방법 - usbip-win
12857정성태11/24/20216938개발 환경 구성: 606. WSL Ubuntu 20.04에서 파이썬을 위한 uwsgi 설치 방법
12856정성태11/23/20218696.NET Framework: 1121. C# - 동일한 IP:Port로 바인딩 가능한 서버 소켓 [2]
12855정성태11/13/20216091개발 환경 구성: 605. Azure App Service - Kudu SSH 환경에서 FTP를 이용한 파일 전송
12854정성태11/13/20217628개발 환경 구성: 604. Azure - 윈도우 VM에서 FTP 여는 방법
12853정성태11/10/20216004오류 유형: 766. Azure App Service - JBoss 호스팅 생성 시 "This region has quota of 0 PremiumV3 instances for your subscription. Try selecting different region or SKU."
12851정성태11/1/20217360스크립트: 34. 파이썬 - MySQLdb 기본 예제 코드
12850정성태10/27/20218511오류 유형: 765. 우분투에서 pip install mysqlclient 실행 시 "OSError: mysql_config not found" 오류
12849정성태10/17/20217647스크립트: 33. JavaScript와 C#의 시간 변환 [1]
12848정성태10/17/20218616스크립트: 32. 파이썬 - sqlite3 기본 예제 코드 [1]
12847정성태10/14/20218469스크립트: 31. 파이썬 gunicorn - WORKER TIMEOUT 오류 발생
12846정성태10/7/20218246스크립트: 30. 파이썬 __debug__ 플래그 변수에 따른 코드 실행 제어
12845정성태10/6/20218087.NET Framework: 1120. C# - BufferBlock<T> 사용 예제 [5]파일 다운로드1
12844정성태10/3/20216126오류 유형: 764. MSI 설치 시 "... is accessible and not read-only." 오류 메시지
12843정성태10/3/20216582스크립트: 29. 파이썬 - fork 시 기존 클라이언트 소켓 및 스레드의 동작파일 다운로드1
... 16  17  18  19  20  21  22  23  24  25  26  27  28  29  [30]  ...