이상한 질문 일 수도 있습니다.
C ++ 컴파일러 (또는 비 VM 언어)를 작성하는 사람 : 원시 기계 언어를 읽거나 쓸 수 있어야합니까? 어떻게 작동합니까?
편집 : 나는 다른 프로그래밍 언어가 아닌 머신 코드로 컴파일하는 컴파일러를 구체적으로 언급하고 있습니다.
이상한 질문 일 수도 있습니다.
C ++ 컴파일러 (또는 비 VM 언어)를 작성하는 사람 : 원시 기계 언어를 읽거나 쓸 수 있어야합니까? 어떻게 작동합니까?
편집 : 나는 다른 프로그래밍 언어가 아닌 머신 코드로 컴파일하는 컴파일러를 구체적으로 언급하고 있습니다.
답변:
아뇨, 전혀 아닙니다. 컴파일러가 대신 어셈블리 코드를 생성하는 것이 가능합니다. 그런 다음 어셈블러는 실제 기계 코드 작성을 처리합니다.
그건 그렇고, 비 VM 구현과 VM 구현을 구별하는 것은 유용하지 않습니다.
우선 머신 코드에 VM 또는 사전 컴파일을 사용하는 것은 언어를 구현하는 다른 방법 일뿐입니다. 대부분의 경우 하나의 전략을 사용하여 언어를 구현할 수 있습니다. 실제로 C ++ 인터프리터를 한 번 사용해야했습니다 .
또한 JVM과 같은 많은 VM에는 일반 아키텍처와 마찬가지로 이진 기계 코드와 일부 어셈블러가 있습니다.
Clang 컴파일러에서 사용하는 LLVM은 여기에서 특별히 언급 할 가치가 있습니다. 명령어는 바이트 코드, 텍스트 어셈블리 또는 컴파일러에서 쉽게 방출 할 수있는 데이터 구조로 표시 될 수있는 VM을 정의합니다. 따라서 디버깅에 유용하고 수행중인 작업을 이해하는 데 유용하지만 어셈블리 언어에 대해서는 알 필요가 없으며 LLVM API에 대해서만 알 수 있습니다.
LLVM의 좋은 점은 VM이 단지 추상화이며 바이트 코드가 일반적으로 해석되지 않고 대신 투명하게 JIT된다는 것입니다. 따라서 CPU의 명령어 세트에 대해 알 필요없이 효과적으로 컴파일 된 언어를 작성할 수 있습니다.
아닙니다. 질문의 핵심은 컴파일이 매우 광범위하다는 것입니다. 컴파일은 모든 언어에서 모든 언어로 발생할 수 있습니다. 그리고 어셈블리 / 머신 코드는 컴파일 대상을위한 많은 언어 중 하나 일뿐입니다. 예를 들어 C #, F # 및 VB.NET과 같은 Java 및 .NET 언어는 모두 머신 별 코드 대신 일종의 중간 코드로 컴파일됩니다. VM에서 실행되는지 여부는 중요하지 않으며 언어는 여전히 컴파일됩니다. C와 같은 다른 언어로 컴파일하는 옵션도 있습니다. C는 실제로 매우 인기있는 컴파일 대상이며 많은 도구가이를 수행합니다. 마지막으로 일부 도구 또는 라이브러리를 사용하여 기계 코드를 생성하는 데 어려움을 겪을 수 있습니다. 예를 들어 독립 컴파일러를 만드는 데 필요한 노력을 줄일 수있는 LLVM 이 있습니다.
또한 편집 내용이 의미가 없습니다. "모든 엔지니어가 엔진의 작동 방식을 이해해야합니까? 그리고 엔진을 사용하는 엔지니어에 대해 묻고 있습니다." 기계 코드를 생성하는 프로그램이나 라이브러리에서 작업하는 경우이를 이해해야합니다. 요점은 컴파일러를 작성할 때 그런 일을 할 필요가 없다는 것입니다. 많은 사람들이 당신보다 먼저 했으므로 다시해야 할 심각한 이유가 있어야합니다.
일반적으로 컴파일러는 어휘 분석, 구문 분석 및 코드 생성의 세 부분으로 구성됩니다. 어휘 분석은 프로그램의 텍스트를 언어 키워드, 이름 및 값으로 나눕니다. 구문 분석은 어휘 분석에서 나온 토큰이 구문 적으로 올바른 언어 문장으로 결합되는 방법을 보여줍니다. 코드 생성은 파서가 생성 한 데이터 구조를 가져 와서 기계 코드 또는 다른 표현으로 변환합니다. 오늘날 어휘 분석 및 구문 분석은 단일 단계로 결합 될 수 있습니다.
분명히 코드 생성기를 작성하는 사람은 명령 세트, 프로세서 파이프 라인 및 캐시 동작을 포함하여 매우 깊은 수준에서 대상 머신 코드를 이해해야합니다. 그렇지 않으면 컴파일러가 생성 한 프로그램이 느리고 비효율적입니다. 8 진수 또는 16 진수로 표시되는 기계어 코드를 읽고 쓸 수 있을지 모르지만 일반적으로 기계어 명령어 테이블을 내부적으로 참조하여 기계어 코드를 생성하는 함수를 작성합니다. 이론적으로 어휘 분석기와 파서를 작성하는 사람들은 머신 코드 생성에 대해 아무것도 알지 못할 수도 있습니다. 실제로 일부 최신 컴파일러를 사용하면 렉서 및 파서 작성자가 들어 본 적이없는 일부 CPU의 기계 코드를 생성 할 수있는 자체 코드 생성 루틴을 연결할 수 있습니다.
그러나 실제로 각 단계의 컴파일러 작성자는 서로 다른 프로세서 아키텍처에 대해 많은 것을 알고 있으며이를 통해 코드 생성 단계에 필요한 데이터 구조를 설계 할 수 있습니다.
입력 및 출력 언어의 의미에 대한 자세한 지식 으로 시작할 필요는 없지만 둘 다에 대한 정교하게 자세한 지식으로 마무리 하는 것이 좋습니다 . 그렇지 않으면 컴파일러가 사용할 수 없을 정도로 버그가 있습니다. 따라서 입력이 C ++이고 출력이 특정 기계 언어 인 경우 결국 두 가지의 의미를 모두 알아야합니다.
다음은 C ++을 기계 코드로 컴파일하는 데있어 미묘한 부분입니다. (머리 꼭대기에서 잊어 버릴 것이 더 있다고 확신합니다.)
어떤 크기입니까 int
? 여기서 "정확한"선택은 기계의 자연적인 포인터 크기, 다양한 크기의 산술 연산에 대한 ALU의 성능 및 기계의 기존 컴파일러가 선택한 선택을 기반으로하는 기술입니다. 기계에도 64 비트 산술이 있습니까? 그렇지 않은 경우 32 비트 정수를 추가하면 명령어로 변환되고 64 비트 정수를 추가하면 64 비트 추가를 수행하는 함수 호출로 변환해야합니다. 머신에 8 비트 및 16 비트 추가 작업이 있습니까? 아니면 32 비트 ops 및 마스킹 (예 : DEC Alpha 21064)을 사용하여 시뮬레이션해야합니까?
머신의 다른 컴파일러, 라이브러리 및 언어에서 사용되는 호출 규칙은 무엇입니까? 매개 변수가 스택에서 오른쪽에서 왼쪽으로 또는 왼쪽에서 오른쪽으로 푸시됩니까? 일부 매개 변수는 레지스터에 들어가고 다른 매개 변수는 스택에 있습니까? 정수와 부동 소수점이 다른 레지스터 공간에 있습니까? 레지스터 할당 매개 변수는 varargs 호출에서 특별히 처리되어야합니까? 발신자가 저장되는 레지스터와 수신자가 저장되는 레지스터는 무엇입니까? 리프 호출 최적화를 수행 할 수 있습니까?
기계의 각 변속 명령은 무엇을합니까? 64 비트 정수를 65 비트로 이동하도록 요청하면 결과는 무엇입니까? (많은 기계에서 결과는 1 비트만큼 이동하는 것과 같고 다른 기계에서는 결과는 "0"입니다.)
머신의 메모리 일관성 의미는 무엇입니까? C ++ 11에는 매우 최적화 된 메모리 의미론이있어 어떤 경우에는 일부 최적화에 제한을 두지 만 다른 경우에는 최적화를 허용합니다. 메모리 의미론이 잘 정의 되지 않은 언어 (C ++ 11 이전의 모든 C / C ++ 버전 및 다른 많은 명령형 언어)를 컴파일하는 경우 에는 메모리 의미론을 개발해야합니다. 머신 시맨틱과 가장 일치하는 메모리 시맨틱을 발명하려고합니다.