성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
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# - 닷넷에서 허용하는 메서드의 매개변수와 호출 인자의 최대 수</h1> <p> 재미있는 읽을거리가 있군요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > What’s the maximum number of arguments for method in C# and in .NET? ; <a target='tab' href='https://www.tabsoverspaces.com/233892-whats-the-maximum-number-of-arguments-for-method-in-csharp-and-in-net'>https://www.tabsoverspaces.com/233892-whats-the-maximum-number-of-arguments-for-method-in-csharp-and-in-net</a> </pre> <br /> 귀찮은 매개변수/인자 테스트를 T4 템플릿을 이용해,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 하나의 T4 템플릿으로 여러 개의 소스코드 파일을 자동으로 생성하는 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/1674'>https://www.sysnet.pe.kr/2/0/1674</a> T4를 이용한 INotifyPropertyChanged 코드 자동 생성 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/1619'>https://www.sysnet.pe.kr/2/0/1619</a> </pre> <br /> 해결하고 있는데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <#@ template debug="false" hostspecific="false" language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ output extension=".cs" #> <# const int Max = 65537; #> class Test { public static void Huge(<#= string.Join(", ", Enumerable.Range(0, Max).Select(x => $"byte arg{x}")) #>) { } public static void CallHuge() { Huge(<#= string.Join(", ", Enumerable.Range(0, Max).Select(_ => "0")) #>); Console.WriteLine("It works!"); } } </pre> <br /> 위의 T4 템플릿으로 생성되는 메서드는 65537개의 매개변수를 갖지만 빌드해 보면 이런 오류가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Unhandled Exception: System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values. </pre> <br /> 반면, 16비트 범위인 65536으로 낮추면 컴파일은 잘 됩니다. 그러니까, 메타데이터로는 일단 65536개의 매개변수를 가진 메서드는 허용을 하는 것입니다.<br /> <br /> 하지만, 해당 메서드를 호출해 보면 실행 시에 다음과 같은 오류가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C:\temp\Console1> <span style='color: blue; font-weight: bold'>.\bin\Debug\net6.0\Console1.exe</span> Unhandled exception. <span style='color: blue; font-weight: bold'>System.InvalidProgramException: Common Language Runtime detected an invalid program.</span> at Test.CallHuge() at Program.Main(String[] args) in C:\temp\Console1\Program.cs:line 7 </pre> <br /> "<a target='tab' href='https://www.tabsoverspaces.com/233892-whats-the-maximum-number-of-arguments-for-method-in-csharp-and-in-net'>What’s the maximum number of arguments for method in C# and in .NET?</a>" 글에서 이에 대해 설명하는데요, 그러니까, 매개변수는 65536개까지 가능하지만 정작 호출하는 측의 인수 전달은 8192개까지만 가능한 것입니다. 그런데 8192라는 숫자가 나온 것도 재미있는데, 2의 13승으로 65536/8개에 해당합니다.<br /> <br /> 정리하면, 8193 ~ 65536개의 매개변수는 실행 시 System.InvalidProgramException 예외가 발생하고, 65536을 초과하면 컴파일 시 System.ArgumentOutOfRangeException 예외가 발생하는 것입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그런데, 저런 제약이 어디서 오는 것일까요? 우선 메서드 정의와 관련된 signature 해석을 살펴보겠습니다. 예전에, method의 signature 분석에 대해 적은 글이 있는데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .NET 메서드의 Signature 바이트 코드 분석 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12379'>https://www.sysnet.pe.kr/2/0/12379</a> </pre> <br /> 위의 글에 소개한 코드 하나를 예로 들면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > COR_SIGNATURE sigFunctionProbe[] = { IMAGE_CEE_CS_CALLCONV_DEFAULT, // default calling convention (쉽게 말해, static 멤버) <span style='color: blue; font-weight: bold'>0x0, // # of arguments == 0</span> ELEMENT_TYPE_VOID, // return type == void }; </pre> <br /> 1바이트로 인자 수를 표현했지만, 원래는 저 코드 영역을 cor.h 헤더 파일에 정의된 CorSigUncompressData 함수를 이용해 해석해 냅니다. 해당 코드를 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > inline HRESULT CorSigUncompressData(// return S_OK or E_BADIMAGEFORMAT if the signature is bad PCCOR_SIGNATURE pData, // [IN] compressed data DWORD len, // [IN] length of the signature ULONG * pDataOut, // [OUT] the expanded *pData ULONG * pDataLen) // [OUT] length of the expanded *pData { HRESULT hr = S_OK; BYTE const *pBytes = reinterpret_cast<BYTE const*>(pData); // Smallest. if ((*pBytes & 0x80) == 0x00) <span style='color: blue; font-weight: bold'>// 0??? ????</span> { if (len < 1) { *pDataOut = 0; *pDataLen = 0; hr = META_E_BAD_SIGNATURE; } else { *pDataOut = *pBytes; *pDataLen = 1; } } // Medium. else if ((*pBytes & 0xC0) == 0x80) <span style='color: blue; font-weight: bold'>// 10?? ????</span> { if (len < 2) { *pDataOut = 0; *pDataLen = 0; hr = META_E_BAD_SIGNATURE; } else { *pDataOut = (ULONG)(((*pBytes & 0x3f) << 8 | *(pBytes+1))); *pDataLen = 2; } } else if ((*pBytes & 0xE0) == 0xC0) <span style='color: blue; font-weight: bold'>// 110? ????</span> { if (len < 4) { *pDataOut = 0; *pDataLen = 0; hr = META_E_BAD_SIGNATURE; } else { *pDataOut = (ULONG)(((*pBytes & 0x1f) << 24 | *(pBytes+1) << 16 | *(pBytes+2) << 8 | *(pBytes+3))); *pDataLen = 4; } } else // We don't recognize this encoding { *pDataOut = 0; *pDataLen = 0; hr = META_E_BAD_SIGNATURE; } return hr; } </pre> <br /> 상위 비트가 0, 10, 110인지 판단해 1~4바이트 가변 크기를 지원하는 형식입니다. 따라서, 8192개의 매개변수를 가졌다면 16진수로는 0x2000이지만 정작 인코딩 상태에서는 0xa000으로 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0xa0_00 1010_0000 </pre> <br /> 따라서, 두 번째 if 문으로 진입하고 0b0011_1111(0x3f)와 AND 연산 및 8비트 Left shift 시킨 후 두 번째 바이트와 OR 연산을 하기 때문에 0b0010_0000_0000_0000(0x2000) 값이 나와 8192가 됩니다.<br /> <br /> 저런 의미에서 봤을 때, 사실 메타데이터에서 지원하는 signature 포맷으로는 2의 (32 - 3) 승까지는 표현할 수 있는데 어느 부분에서 제약을 하고 있는지 모르겠군요. ^^<br /> <br /> 그다음 호출 측을 볼까요?<br /> <br /> 호출과 관련해서는 IL 코드로 표현이 되는데, ldarg의 경우,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > OpCodes.Ldarg Field ; <a target='tab' href='https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes.ldarg'>https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes.ldarg</a> </pre> <br /> 16비트 값을 받기 때문에,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > FE 09 < unsigned int16 > </pre> <br /> 온전히 65536개의 인자를 처리할 수 있습니다. 그러니까, 메타데이터 관점에서 보면 매개변수의 수는 2의 29승, 인자의 수는 2의 16승까지 지원은 합니다. (혹시, 매개변수가 65536, 인자가 8192개까지만 처리되는 내부 코드를 알고 계신 분이 있다면 덧글 부탁드립니다. ^^)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
8389
(왼쪽의 숫자를 입력해야 합니다.)