성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# 7.3 - 구조체의 고정 크기를 갖는 fixed 배열 필드에 대한 직접 접근 가능</h1> <p> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# 7.3 (1) - 개선된 문법 4개(Support == and != for tuples, Ref Reassignment, Constraints, Stackalloc initializers) ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11552'>http://www.sysnet.pe.kr/2/0/11552</a> C# 7.3 (2) - 개선된 메서드 선택 규칙 3가지(Improved overload candidates) ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11553'>http://www.sysnet.pe.kr/2/0/11553</a> C# 7.3 (3) - 자동 구현 속성에 특성 적용 가능(Attribute on backing field) ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11554'>http://www.sysnet.pe.kr/2/0/11554</a> C# 7.3 (4) - 사용자 정의 타입에 fixed 적용 가능(Custom fixed) ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11555'>http://www.sysnet.pe.kr/2/0/11555</a> C# 7.3 (5) - 구조체의 고정 크기를 갖는 fixed 배열 필드에 대한 직접 접근 가능(Indexing movable fixed buffers) ; http://www.sysnet.pe.kr/2/0/11556 C# 7.3 (6) - blittable 제네릭 제약(blittable) ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11558'>http://www.sysnet.pe.kr/2/0/11558</a> C# 7.3 (7) - 초기화 식에서 변수 사용 가능(expression variables in initializers) ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11560'>http://www.sysnet.pe.kr/2/0/11560</a> </pre> <br /> <hr style='width: 50%' /><br /> <br /> fixed 예약어는 지난 글에서 설명한 내용에 따라,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# 7.3 - 사용자 정의 타입에 fixed 적용 가능(Custom fixed) ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11555'>http://www.sysnet.pe.kr/2/0/11555</a> </pre> <br /> 관리 힙에 놓인 객체를 이동하지 않도록 고정시키는데 사용할 수 있는가 하면, 또 다른 용도로 고정 크기(fixed size)를 갖는 배열을 정의하는 문법으로도 쓰입니다. 이번 글에서 사용하는 fixed는 그 2가지 경우를 모두 다루기 때문에 다소 혼란스러울 수 있습니다. 그러니까... 대충... 결론부터 내면 이런 식입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > fixed 배열 필드에 대한 인덱스 접근을 C# 7.2까지는 fixed 구문에서 사용해야 했는데 C# 7.3부터 fixed 구문이 필요 없도록 바뀌었다. </pre> <br /> <hr style='width: 50%' /><br /> <br /> C#에서 배열은 "관리 힙"에 할당하도록 되어 있습니다. 그래서 값 형식인 구조체에 배열(및 참조 형식)이 정의되어 있으면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > struct StructType { public int [] fields; } </pre> <br /> fields 배열을 가리키는 포인터만 스택에 있게 되고 배열의 내용 자체는 관리 힙에 할당이 됩니다. 이것이 문제가 되는 경우가 바로 C/C++ 모듈과의 interop을 할 때입니다. 예를 들어 C/C++에서 다음과 같은 식으로 구조체와 그것을 정의하는 함수를 정의한 경우,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // C/C++의 구조체 struct CppStructType { public: <span style='color: blue; font-weight: bold'>int fields[10]; __int64 dummy[20];</span> }; __declspec(dllexport) void __stdcall ProcessItem(CppStructType *value) { for (int i = 0; i < 10; i++) { value->fields[i] = (i + 1) * 2; } for (int i = 0; i < 20; i++) { value->dummy[i] = (i + 1) * 20; } } </pre> <br /> C#에서 위의 ProcessItem이라는 함수를 호출하기 위해 다음과 같은 식으로 구조체를 만들어 시도하려는 경우가 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // C#의 struct struct CSharpStructType { <span style='color: blue; font-weight: bold'>public int[] fields; public long[] dummy;</span> } static void Main(string[] args) { CSharpStructType item = new CSharpStructType(); item.fields = <span style='color: blue; font-weight: bold'>new int[10];</span> item.dummy = <span style='color: blue; font-weight: bold'>new long[20];</span> } </pre> <br /> 물론, 당연히 이렇게 하면 안 됩니다. CSharpStructType은 다음과 같은 식으로 메모리를 소유하고 있기 때문입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > CSharpStructType 인스턴스 { // 관리 힙에 있는 fields를 가리키는 4 bytes (or 8 bytes in x64) 포인터 변수 // 관리 힙에 있는 dummy를 가리키는 4 bytes (or 8 bytes in x64) 포인터 변수 } // 관리 힙에 있는 fields fields { // sizeof(int) * 10 bytes } // 관리 힙에 있는 dummy dummy { // sizeof(long) * 20 bytes } </pre> <br /> 따라서 C/C++의 ProcessItem은 겨우 8바이트(또는 16바이트)에 불과한 CSharpStructType 인스턴스를 다루게 되고 이로 인해 AV(Access Violation) 같은 오류가 발생하게 됩니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 바로 이럴 때, CppStructType과 동일한 메모리를 확보할 수 있도록 지원하는 구문이 바로 fixed입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > unsafe struct CppStructType { public <span style='color: blue; font-weight: bold'>fixed</span> int fields<span style='color: blue; font-weight: bold'>[10]</span>; public <span style='color: blue; font-weight: bold'>fixed</span> long dummy<span style='color: blue; font-weight: bold'>[20]</span>; } </pre> <br /> 위와 같이 정의하고 CppStructType 인스턴스를 생성하면 다음과 같은 식으로 메모리가 할당이 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > CppStructType 인스턴스 { // sizeof(int) * 10 bytes // sizeof(long) * 20 bytes } </pre> <br /> 이는 C/C++에서의 struct와 동일한 크기의 메모리 구조를 갖게 되므로 C#에서 다음과 같이 안전하게 호출할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Runtime.InteropServices; unsafe struct CppStructType { <span style='color: blue; font-weight: bold'>public fixed int fields[10]; public fixed long dummy[20];</span> } class Program { [DllImport("Win32Project1.dll", SetLastError = true)] <span style='color: blue; font-weight: bold'>internal static unsafe extern int ProcessItem(CppStructType* value);</span> static unsafe void Main(string[] args) { CppStructType item = new CppStructType(); <span style='color: blue; font-weight: bold'>CppStructType* ptItem = &item; ProcessItem(ptItem);</span> for (int i = 0; i < 10; i++) { Console.WriteLine(item.fields[i]); } Console.WriteLine(); for (int i = 0; i < 20; i++) { Console.WriteLine(item.dummy[i]); } } } </pre> <br /> <hr style='width: 50%' /><br /> <br /> 자... 이 정도면 fixed에 대한 사전 설명은 대충 끝이 났으니 이제 C# 7.3에서 추가된 부분을 살펴보겠습니다. <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Indexing fixed fields should not require pinning regardless of the movable/unmovable context. ; <a target='tab' href='https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/indexing-movable-fixed-fields.md'>https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/indexing-movable-fixed-fields.md</a> </pre> <br /> 일단, C#에서는 fixed 고정 크기 배열을 소유한 구조체를 다음과 같이 메서드 내에서 사용할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static unsafe void Main(string[] args) { CppStructType item = new CppStructType(); <span style='color: blue; font-weight: bold'>item.fields[0] = 5; int n = item.fields[2];</span> } </pre> <br /> 위의 구조체는 (unmovable context의 하나인) 스택에 생성되어 있으므로 GC의 수행에 따라 메모리 이동이 발생하지 않습니다. 하지만, 다음과 같이 CppStructType을 참조 형식 내의 필드로 선언하는 경우에는 (달리 말해 movable context) 상황이 달라집니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; unsafe struct CppStructType { public fixed int fields[10]; public fixed long dummy[20]; } <span style='color: blue; font-weight: bold'>class UserType</span> { <span style='color: blue; font-weight: bold'>CppStructType _item</span>; // 값 형식이지만, // 참조 형식(class)의 필드로 정의되었으므로 // 관리 힙에 할당이 됨 public UserType() { <span style='color: blue; font-weight: bold'>_item = new CppStructType();</span> } public unsafe void ProcessItem() { // 컴파일 에러 - Error CS1666 You cannot use fixed size buffers contained in unfixed expressions. Try using the fixed statement. <span style='color: blue; font-weight: bold'>_item.fields[0]</span> = 5; // 컴파일 에러 - Error CS1666 You cannot use fixed size buffers contained in unfixed expressions. Try using the fixed statement. int n = <span style='color: blue; font-weight: bold'>_item.fields[2]</span>; } } </pre> <br /> C# 7.2까지 fixed 배열의 인덱스 접근은 항상 비관리 포인터를 사용하도록 코드 생성이 되므로 그로 인해 위와 같은 movable context 하에서는 반드시 fixed를 이용해 다음과 같이 고정시킨 후 사용해야 했습니다. <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public unsafe void ProcessItem() { // fixed 배열 필드의 인덱스를 접근하기 위해 fixed 문에서 메모리를 고정한 후 사용 // 왜냐하면 fixed 배열 필드의 인덱스 접근 코드가 비관리 포인터를 이용하는 방식이었기 때문에. <span style='color: blue; font-weight: bold'>fixed</span> (int* ptr = _item.fields) { <span style='color: blue; font-weight: bold'>ptr[0]</span> = 5; int n = <span style='color: blue; font-weight: bold'>ptr[2]</span>; } } </pre> <br /> 하지만, 가만 보니 fixed 배열의 인덱스 접근에 비관리 포인터 대신 관리 포인터를 쓸 수 있었던 것입니다. 이를 감안해 C# 7.3부터 다음과 같이 fixed 없이 컴파일이 되도록 바뀌었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > unsafe struct CppStructType { public fixed int fields[10]; public fixed long dummy[20]; } class UserType { CppStructType _item; public UserType() { _item = new CppStructType(); } public unsafe void ProcessItem() { // C# 7.3부터 컴파일 가능 <span style='color: blue; font-weight: bold'>_item.fields[0] = 5;</span> // C# 7.3부터 컴파일 가능 <span style='color: blue; font-weight: bold'>int n = _item.fields[2];</span> // 하지만 (movable context에서) 포인터로써 접근하겠다고 하면 여전히 fixed가 필요. <span style='color: blue; font-weight: bold'>fixed (int* ptr = _item.fields)</span> { } // 당연히 unmovable context에서는 포인터로써 접근해도 fixed가 필요치 않음. { CppStructType localItem; <span style='color: blue; font-weight: bold'>int* ptr = localItem.fields</span>; } } } </pre> <br /> 실제로 (C# 7.3부터) 위의 코드에서 사용된 인덱스 접근 코드는 다음의 IL 코드에서 보는 바와 같이 <a target='tab' href='https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes.ldflda'>ldflda 코드</a>로 해결하고 있습니다. 즉, 관리 포인터를 쓰는 것만으로 fixed 배열의 인덱싱이 가능했던 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .method public hidebysig instance void ProcessItem() cil managed { .maxstack 3 .locals init ( [0] int32 num) L_0000: nop L_0001: ldarg.0 L_0002: ldflda valuetype CppStructType UserType::_item L_0007: ldflda valuetype CppStructType/<fields>e__FixedBuffer CppStructType::fields L_000c: <span style='color: blue; font-weight: bold'>ldflda int32 CppStructType/<fields>e__FixedBuffer::FixedElementField</span> L_0011: ldc.i4.5 L_0012: stind.i4 L_0013: ldarg.0 L_0014: ldflda valuetype CppStructType UserType::_item L_0019: ldflda valuetype CppStructType/<fields>e__FixedBuffer CppStructType::fields L_001e: <span style='color: blue; font-weight: bold'>ldflda int32 CppStructType/<fields>e__FixedBuffer::FixedElementField</span> L_0023: ldc.i4.2 L_0024: conv.i L_0025: ldc.i4.4 L_0026: mul L_0027: add L_0028: ldind.i4 L_0029: stloc.0 L_002a: ret } </pre> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1273&boardid=331301885'>첨부 파일은 이 글의 예제 코드 - C/C++, C# 프로젝트를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
3517
(왼쪽의 숫자를 입력해야 합니다.)