C# 컴파일은 헤더 파일이 필요한 상황에서 어떻게 해결됩니까?
저는 C# 개발자로서 제 직업적인 삶을 보냈습니다.저는 학생으로서 가끔 C를 사용했지만 그것의 컴필레이션 모델을 깊이 연구하지는 않았습니다.최근 저는 시류에 편승하여 Objective-C를 공부하기 시작했습니다.저의 첫 걸음은 제가 기존에 가지고 있던 지식에 구멍이 있다는 것을 알게 했을 뿐입니다.
제가 조사한 바로는 C/C++/ObjC 컴파일은 만나는 모든 심볼을 사전에 선언해야 합니다.건축은 2단계로 진행되는 것으로 알고 있습니다.먼저 각 소스 파일을 개별 개체 파일로 컴파일합니다.이러한 개체 파일에는 정의되지 않은 "기호"(일반적으로 헤더 파일에 선언된 식별자에 해당)가 있을 수 있습니다.두 번째로 개체 파일을 연결하여 최종 출력을 구성합니다.이것은 꽤 높은 수준의 설명이지만 저의 호기심을 충분히 만족시킵니다.하지만 C# 빌드 프로세스에 대해서도 비슷한 수준의 이해를 하고 싶습니다.
Q: C# 빌드 프로세스는 헤더 파일의 필요성을 어떻게 극복합니까?나는 아마도 컴필레이션 단계가 투 패스가 되는 것을 상상할 수 있을 겁니다.
(편집: 여기서 후속 질문 C/C++/Objective-C는 C#과 라이브러리를 사용할 때 어떻게 비교됩니까?)
업데이트: 이 질문은 2010년 2월 4일 블로그의 주제였습니다.좋은 질문 감사합니다!
제가 한 번 설명해 드릴게요.가장 기본적인 의미에서 컴파일러는 "투 패스 컴파일러"입니다. 왜냐하면 컴파일러가 거치는 단계는 다음과 같습니다.
- 메타데이터 생성.
- IL의 생성.
메타데이터는 코드의 구조를 설명하는 모든 "최상위" 항목입니다.네임스페이스, 클래스, 구조체, 열거형, 인터페이스, 위임자, 메서드, 형식 매개변수, 생성자, 이벤트, 특성 등입니다.기본적으로 메소드 바디를 제외한 모든 것.
IL은 코드가 어떻게 구성되는지에 대한 메타데이터가 아니라 실제 명령 코드인 메소드 바디에 들어가는 모든 것입니다.
첫 번째 단계는 실제로 소스에 대한 많은 패스를 통해 구현됩니다.두 개보다 훨씬 많습니다.
우리가 가장 먼저 하는 일은 출처의 텍스트를 가져와서 토큰의 흐름으로 나누는 것입니다.즉, 우리는 다음과 같은 것을 결정하기 위해 어휘 분석을 합니다.
class c : b { }
클래스, 식별자, 콜론, 식별자, 왼쪽 컬리, 오른쪽 컬리입니다.
그런 다음 토큰 스트림이 문법적으로 올바른 C# 프로그램을 정의하는지 확인하는 "상위 수준 구문 분석"을 수행합니다.그러나 메서드 본문 파싱은 생략합니다.메소드 바디에 부딪히면 일치하는 근접 컬리에 도달할 때까지 토큰을 통해 불을 뿜어냅니다.이 시점에서 메타데이터를 생성하기에 충분한 정보를 얻는 것에만 관심을 두고 있기 때문에 나중에 다시 이 문제로 돌아가겠습니다.
그런 다음 프로그램의 모든 네임스페이스와 유형 선언의 위치를 메모하는 "선언" 패스를 수행합니다.
그런 다음 선언된 모든 유형이 기본 유형에 사이클이 없음을 확인하는 패스를 수행합니다.이후 패스할 때마다 사이클을 처리할 필요 없이 계층 유형을 상향 조정할 수 있어야 하기 때문에 먼저 이 작업을 수행해야 합니다.
그런 다음 제네릭 유형의 모든 제네릭 매개 변수 제약 조건이 비순환적인지 확인하는 패스를 수행합니다.
그런 다음 클래스 방법, 구조체 필드, 열거 값 등 모든 유형의 모든 구성원이 일치하는지 확인하는 패스를 수행합니다.모든 재정의 방법이 실제로 가상화된 것을 재정의하는 등 열거된 주기가 없습니다.이 시점에서 모든 인터페이스의 "vtable" 레이아웃, 가상 메서드가 포함된 클래스 등을 계산할 수 있습니다.
그런 다음 모든 "const" 필드의 값을 계산하는 패스를 수행합니다.
이 시점에서 우리는 이 어셈블리의 거의 모든 메타데이터를 전송할 수 있는 충분한 정보를 가지고 있습니다.우리는 여전히 반복자/익명 함수 폐쇄 또는 익명 유형에 대한 메타데이터에 대한 정보가 없습니다. 우리는 이 작업을 늦게 합니다.
이제 IL 생성을 시작할 수 있습니다.각 방법 본체(및 속성, 인덱서, 생성자 등)에 대해 방법 본체가 시작된 지점까지 렉서를 되감기하고 방법 본체를 파싱합니다.
메서드 본문이 파싱되면 초기 "구속" 패스를 수행하여 모든 문장에 있는 모든 식의 유형을 결정하려고 시도합니다.그런 다음 각 방법체에 대해 전체 패스 더미를 수행합니다.
먼저 루프를 고토와 라벨로 변환하기 위해 패스를 실행합니다.
(다음 몇 번의 패스는 나쁜 것을 찾습니다.)
그런 다음 경고를 위해 사용하지 않는 유형의 사용 여부를 확인하는 패스를 실행합니다.
그런 다음 아직 메타데이터를 전송하지 않은 익명 유형의 용도를 검색하는 패스를 실행하여 전송합니다.
그런 다음 표현 트리의 잘못된 사용을 검색하는 패스를 실행합니다.예를 들어 식 트리에서 ++ 연산자를 사용합니다.
그런 다음 정의되었지만 사용되지 않은 본문의 모든 지역 변수를 찾아 경고를 보고하는 패스를 실행합니다.
그런 다음 반복자 블록 안에서 불법 패턴을 찾는 패스를 실행합니다.
그런 다음 도달 가능성 검사기를 실행하여 도달할 수 없는 코드에 대한 경고를 제공하고 무효가 아닌 방법이 끝날 때 반환을 잊어버린 경우와 같은 작업을 수행했습니다.
그런 다음 모든 이동 경로가 합리적인 레이블을 대상으로 하며 모든 레이블이 도달 가능한 이동 경로를 대상으로 하는지 확인하는 패스를 실행합니다.
그런 다음 모든 로컬이 사용 전에 확실히 할당되어 있는지 확인하고, 어떤 로컬 변수가 익명 함수 또는 반복기의 닫힌 외부 변수인지, 어떤 익명 함수가 도달 가능한 코드인지 확인하는 패스를 실행합니다.(이 패스는 너무 많아요.저는 예전부터 그것을 재검토하려고 했습니다.)
이 시점에서 우리는 나쁜 것들을 찾는 것은 끝났지만, 잠자기 전에 갈 수 있는 패스가 훨씬 더 많습니다.
다음으로 COM 개체의 호출에 대한 참조 인수가 누락된 것을 탐지하여 수정하는 패스를 실행합니다. (이것은 C# 4의 새로운 기능입니다.)
그런 다음 "새로운 MyDelegate(Foo)" 형식의 내용을 찾는 패스를 실행하여 CreateDelegate 호출에 다시 씁니다.
그런 다음 런타임에 표현 트리를 만드는 데 필요한 공장 메서드 호출 순서로 표현 트리를 변환하는 패스를 실행합니다.
그런 다음 모든 nullable 산술을 HasValue 등을 테스트하는 코드로 다시 쓰는 패스를 실행합니다.
그런 다음 양식 베이스의 모든 참조를 찾는 패스를 실행합니다.Blah()를 기본 클래스 메소드에 비가상 호출을 수행하는 코드로 다시 씁니다.
그런 다음 개체 및 수집 초기화자를 찾아 적절한 속성 집합으로 변환하는 패스를 실행합니다.
그런 다음 동적 호출(C# 4)을 찾는 패스를 실행하여 DLR을 사용하는 동적 호출 사이트에 다시 씁니다.
그런 다음 제거된 메서드에 대한 호출을 찾는 패스를 실행합니다. (즉, 실제 구현이 없는 부분 메서드 또는 조건부 컴파일 기호가 정의되지 않은 조건부 메서드)그런 것들은 노옵스로 바뀝니다.
그런 다음 도달할 수 없는 코드를 찾아서 트리에서 제거합니다.IL을 코드 생성하는 것은 의미가 없습니다.
그런 다음 사소한 "is" 연산자와 "as" 연산자를 다시 쓰는 최적화 패스를 실행합니다.
그런 다음 스위치(상수)를 찾는 최적화 패스를 실행하여 올바른 경우에 직접 분기로 다시 씁니다.
그런 다음 문자열 연결을 문자열의 올바른 오버로드로 호출하는 패스를 실행합니다.콩캣.
(아, 추억.이 마지막 두 번의 합격은 제가 컴파일러 팀에 합류했을 때 처음으로 작업한 것들입니다.)
그런 다음 이름이 지정된 파라미터와 선택적인 파라미터의 사용을 호출에 다시 쓰는 패스를 실행합니다. 여기서 부작용은 모두 올바른 순서로 발생합니다.
그런 다음 산술을 최적화하는 패스를 실행합니다. 예를 들어 M()이 int를 반환하고 1 * M()을 가진 경우 M()으로 변환합니다.
그런 다음 이 방법으로 처음 사용된 익명 유형에 대한 코드를 생성합니다.
그런 다음 이 신체의 익명 기능을 폐쇄 수업의 방법으로 변환합니다.
마지막으로, 우리는 중계기 블록을 스위치 기반 상태 기계로 변환합니다.
그리고 방금 계산한 변형된 나무에 대한 IL을 방출합니다.
아주 쉬워요!
질문에 대한 해석이 여러 가지인 것 같습니다.솔루션 내 해석에 대해서는 답변을 드렸지만, 제가 알고 있는 모든 정보를 기재하도록 하겠습니다.
컴파일된 어셈블리에 "헤더 파일 메타데이터"가 있으므로 참조를 추가하는 어셈블리는 컴파일러가 해당 어셈블리에서 메타데이터를 가져올 수 있습니다.
아직 컴파일되지 않은 것들에 대해서는, 현재 해결책의 일부로서, 그것은 투 패스 컴파일, 첫 읽기 네임스페이스, 타입 네임, 멤버 네임, 즉 코드를 제외한 모든 것을 할 것입니다.그런 다음 이것이 체크아웃되면 코드를 읽고 컴파일합니다.
이것은 컴파일러가 존재하는 것과 존재하지 않는 것을 알 수 있게 해줍니다.
2-패스 컴파일러의 유효성을 확인하려면 3개의 문제, 2개의 선언 관련 문제, 1개의 코드 문제가 있는 다음 코드를 테스트합니다.
using System;
namespace ConsoleApplication11
{
class Program
{
public static Stringg ReturnsTheWrongType()
{
return null;
}
static void Main(string[] args)
{
CallSomeMethodThatDoesntExist();
}
public static Stringg AlsoReturnsTheWrongType()
{
return null;
}
}
}
컴파일러는 이 두 가지에 대해서만 불평할 것임을 유의합니다.Stringg
찾을 수 없는 유형입니다.이를 수정하면 기본 메서드에서 호출된 메서드 이름을 찾을 수 없다고 불만을 제기합니다.
참조 어셈블리의 메타데이터를 사용합니다.그것은 헤더 파일에서 찾을 수 있는 것과 같은 전체 형식의 선언을 포함합니다.
2패스 컴파일러가 되는 것은 또 다른 것을 가져다 줍니다: 다른 소스 코드 파일에 선언되기 전에 한 소스 파일에 타입을 사용할 수 있다는 것입니다.
2패스 컴파일러입니다.http://en.wikipedia.org/wiki/Multi-pass_compiler
필요한 모든 정보는 참조된 어셈블리에서 얻을 수 있습니다.
따라서 헤더 파일은 없지만 컴파일러는 사용 중인 DLL에 접근해야 합니다.
네, 2패스 컴파일러이지만 라이브러리 유형에 대한 정보를 얻는 방법은 설명되지 않습니다.
언급URL : https://stackoverflow.com/questions/1917935/how-does-c-sharp-compilation-get-around-needing-header-files
'programing' 카테고리의 다른 글
주소 ()를 취하고 값을 주는 함수 (0) | 2023.10.01 |
---|---|
IN 절의 MySQL 여러 열 (0) | 2023.09.26 |
MySQL에서 피벗 테이블 출력을 반환하려면 어떻게 해야 합니까? (0) | 2023.09.26 |
화면이 1024px보다 좁을 경우 ADIV 숨기기 (0) | 2023.09.26 |
GUID/UUID 데이터베이스 키의 장단점 (0) | 2023.09.26 |