[F-Lab 66해빗 페이백 챌린지 ]

[F-Lab 페이백 모각코 64일차] 자바 컴파일 과정

everydeveloper 2023. 9. 9. 23:48

자바 컴파일 과정

  • 소스 작성
  • 컴파일
    • 문법검사
      • 어휘 분석
        • 토근
          • 예약어
            • 자바에서 미리 정의된 키워드들입니다 (예: class, if, while 등)
          • 식별자
            • 변수, 메소드, 클래스 등의 이름입니다.
          • 리터럴
            • 상수 값들입니다 (예: 정수, 실수, 문자열, 불린 값 등)
          • 연산자
            • 수학, 관계, 논리 등의 연산자입니다 (예: +, -, *, /, ==, !=, &&, || 등).
          • 구분자
            • 코드의 구조를 나타내는 문자들입니다 (예: 괄호, 중괄호, 세미콜론 등).
            자바 코드 예제:
            
            java
            Copy code
            public class HelloWorld {
                public static void main(String[] args) {
                    System.out.println("Hello, World!");
                }
            }
            이 코드에서 추출되는 토큰들 중 일부는 다음과 같습니다:
            
            public: 예약어(reserved word)로 접근 제어자(access modifier)를 나타냅니다. 여기서는 클래스와 메소드에 대한 접근 제어자로 사용됩니다.
            class: 예약어로 클래스를 정의할 때 사용됩니다.
            HelloWorld: 식별자(identifier)로 클래스 이름을 나타냅니다.
            static: 예약어로 메소드가 클래스 메소드임을 나타냅니다.
            void: 예약어로 메소드의 반환 타입이 없음을 나타냅니다.
            main: 식별자로 메소드 이름을 나타냅니다.
            String: 예약어로 문자열 데이터 타입을 나타냅니다.
            args: 식별자로 메소드의 매개변수 이름을 나타냅니다.
            System: 식별자로 자바의 내장 클래스를 나타냅니다.
            out: 식별자로 System 클래스의 정적 변수를 나타냅니다.
            println: 식별자로 out 변수의 메소드 이름을 나타냅니다.
            "Hello, World!": 리터럴(literal)로 문자열 상수 값을 나타냅니다.
            (, ), {, }: 구분자(separators)로 코드의 구조를 나타내는 괄호와 중괄호입니다.
            ;: 구분자로 문장의 끝을 나타내는 세미콜론입니다.
            이렇게 추출된 토큰들은 구문 분석기에 전달되어 코드의 문법 구조를 검사하는 데 사용됩니다.
            
      • 구문 분석
        • 구문 분석(syntax analysis)은 어휘 분석에서 생성된 토큰들을 사용하여 소스 코드의 문법 구조를 검사하는 과정
        • 구문 분석기(parser)
          • 구문 분석기(parser)는 이 작업을 수행하며, 이 과정에서 문법 오류가 발견되면 컴파일 오류를 발생시킵니다. 구문 분석의 결과로 추상 구문 트리(Abstract Syntax Tree, AST)가 생성
          자바 코드 예제:
          
          java
          Copy code
          int sum(int a, int b) {
              return a + b;
          }
          이 코드의 추상 구문 트리는 다음과 같습니다:
          
          markdown
          Copy code
          FunctionDeclaration
              - Type: int
              - Identifier: sum
              - Parameters
                  - Parameter
                      - Type: int
                      - Identifier: a
                  - Parameter
                      - Type: int
                      - Identifier: b
              - Body
                  - ReturnStatement
                      - Expression
                          - BinaryExpression
                              - Left: Identifier (a)
                              - Operator: +
                              - Right: Identifier (b)
          
          
    • 위에서 보여드린 추상 구문 트리는 각 코드 요소와 관계를 계층 구조로 표현하고 있습니다. 이 트리는 구문 분석기가 소스 코드의 문법 구조를 이해하고, 이를 바탕으로 타입 검사, 바인딩, 최적화 등의 작업을 수행할 수 있게 합니다.
    구문 분석에서 문법 오류가 발견되면 컴파일러는 오류를 발생시키고 프로세스가 중단됩니다. 예를 들어, 다음 코드는 구문 오류가 발생하는 경우입니다
    • 이 경우 구문 분석기는 누락된 세미콜론을 발견하고 컴파일 오류를 발생시킵니다. 이렇게 구문 분석은 소스 코드가 문법 규칙을 따르는지 검사하는 핵심 컴파일 과정
    • 상위-하위 구문 분석
      • 구문 분석기가 상위 요소(루트 노드)에서 시작하여 하위 요소(리프 노드)로 이동하는 방식입니다.
        • 알기 쉽게 비유로 들면
          • 상위-하위 구문 분석을 이해하기 쉽게 비유를 들어 설명해 드리겠습니다. 상위-하위 구문 분석은 마치 문장을 구성하는 단어와 구절을 이해하는 것과 비슷합니다. 예를 들어, 다음 문장을 생각해보세요.
          The dog barks at the cat.
          
          
          • 이 문장은 명사, 동사, 전치사 등 다양한 품사로 구성되어 있습니다. 문장을 이해하기 위해서는 각 단어가 어떤 역할을 하는지 파악해야 합니다. 이를 위해 상위-하위 구문 분석을 사용할 수 있습니다. 상위-하위 구문 분석은 문장의 전체 구조를 이해하기 위해 상위 수준의 문법 요소(구, 절 등)부터 시작해서 하위 수준의 요소(단어, 품사 등)로 내려가며 분석합니다.
          • 이제 다시 상위-하위 구문 분석에 대한 상세한 설명을 들어가겠습니다.
            1. 상위-하위 구문 분석은 가장 높은 수준의 문법 요소, 즉 문법의 시작점에서 시작합니다.
            2. 시작점에서 가능한 모든 문법 규칙을 시도하여 입력 문자열과 일치하는 규칙을 찾습니다.
            3. 일치하는 규칙이 발견되면, 그 규칙에 따라 하위 수준의 문법 요소로 내려갑니다.
            4. 하위 수준의 문법 요소에서 다시 가능한 모든 규칙을 시도하여 일치하는 규칙을 찾습니다.
            5. 이 과정을 계속 반복하며, 모든 입력 문자열이 처리되거나 더 이상 일치하는 규칙이 없을 때까지 내려갑니다.
          • 이 방법으로, 상위 수준의 구조부터 차례대로 하위 수준의 구조로 내려가며 문법 요소를 분석하고 구문 트리를 구성할 수 있습니다. 이 과정에서 문법 오류가 발견되면 구문 분석이 실패하고 오류 메시지가 출력됩니다.
          • Q(나) 상위에서 하위로 까지 여러 단계의 구문의 단계가 있는데 맨 처음 최상위 수준의 구문을 찾고 그 다음 단계의 구문을 전부 찾고 없으면 그다음 단계 수준의 구문.... 끝까지 최하위 단계의 구문을 찾고 이에서 필수 구문이 빠지거나 순서다르면 에러다?
          • A(CHATGTP)네, 맞습니다! 상위-하위 구문 분석은 최상위 수준의 구문부터 시작하여 하위 수준의 구문으로 내려가며 전체 문법 구조를 이해하고 검증합니다. 각 단계에서 입력 문자열과 일치하는 문법 규칙을 찾고, 모든 규칙이 적용되어야만 문법적으로 올바른 코드라고 판단할 수 있습니다. 만약 필수 구문이 빠지거나 순서가 잘못되면 구문 분석이 실패하고 에러 메시지가 출력됩니다. 이를 통해 프로그래머가 문법 오류를 수정할 수 있게 도와줍니다.
      • 대표적인 상위-하위 구문 분석 방법으로는 재귀 하향 구문 분석(Recursive Descent Parsing)가 있다.
      • 재귀적 함수 호출: 재귀 하향 구문 분석은 각 문법 규칙에 대응하는 파서 함수를 호출하며, 이 함수들은 필요에 따라 서로를 재귀적으로 호출합니다. 이렇게 하여 문법 구조를 따라 파싱이 진행됩니다.
      • 순차적 검사: 재귀 하향 구문 분석에서는 가능한 문법 규칙을 순차적으로 검사하며, 일치하는 규칙을 찾으면 해당 규칙을 따라 파싱을 진행합니다.
      • 백트래킹: 재귀 하향 구문 분석은 일치하는 문법 규칙을 찾지 못하면 이전 단계로 돌아가서 다른 규칙을 시도하는 백트래킹을 사용할 수 있습니다. 하지만 백트래킹은 성능에 부정적인 영향을 줄 수 있기 때문에, 대부분의 경우 예측 파서(predictive parser)를 사용하여 백트래킹이 필요 없도록 문법을 설계합니다.
      • LL(1) 문법: 재귀 하향 구문 분석은 주로 LL(1) 문법을 사용합니다. LL(1) 문법은 왼쪽에서 오른쪽으로 파싱하고, 왼쪽 재귀를 허용하지 않으며, 한 번의 룩어헤드(lookahead)로 어떤 규칙을 적용할지 결정할 수 있는 문법입니다.
        • LL(1): LL(1) 문법은 왼쪽에서 오른쪽(Left-to-right)으로 스캔하며, 왼쪽에서 오른쪽으로 파싱하는 구문 분석 알고리즘입니다. 약어 'LL'에서 첫 번째 'L'은 스캔 방향을 나타내고, 두 번째 'L'은 파싱 방향을 나타냅니다. LL(1) 문법은 재귀 하향 구문 분석 알고리즘과 함께 사용되며, 왼쪽 재귀를 허용하지 않습니다. 즉, 한 번의 룩어헤드로 어떤 문법 규칙을 적용할지 결정할 수 있어야 합니다.
          • 룩어헤드(lookahead)는 구문 분석 과정에서 파서가 미리 다음에 나올 토큰(혹은 토큰들)을 살펴보는 기법입니다. 룩어헤드를 사용하면 파서가 현재 처리 중인 토큰 이후의 입력을 고려하여 적절한 문법 규칙을 결정할 수 있습니다.
          • 룩어헤드는 일반적으로 LL(k)와 같은 문법에서 사용됩니다. 여기서 'k'는 룩어헤드하는 토큰 수를 나타냅니다. 예를 들어, LL(1) 문법에서는 현재 토큰을 처리한 후 바로 다음 토큰만 미리 보고(lookahead) 문법 규칙을 결정합니다. 이를 통해 파서는 여러 가지 문법 규칙 중 어떤 것을 적용할지 결정할 수 있습니다.
          • 룩어헤드를 사용함으로써 파서는 문법 규칙 간의 충돌을 피하고, 처리 과정에서 더 나은 성능과 정확도를 달성할 수 있습니다. 하지만 룩어헤드 값이 클수록 구문 분석기의 복잡성이 증가하므로, 적절한 룩어헤드 값을 선택하는 것이 중요합니다. 대부분의 경우 LL(1)이나 LR(1) 문법이 충분한 성능과 간결함을 제공합니다.
            • LL(1) or LR(1) → 한 글자씩 미리 예측
              • 대부분 이 경우를 채택함
              • 대개의 경우 효율적
            • LL(2) or LR(2) → 두 글자씩 미리 예측
      • 직관적이고 구현이 간단: 재귀 하향 구문 분석은 직관적인 방식으로 문법 구조를 따라 파싱을 진행하며, 구현이 상대적으로 간단하기 때문에 손쉽게 작성할 수 있습니다. 그러나 복잡한 문법이나 큰 입력에 대해서는 성능이 떨어질 수 있습니다.
    • 하위-상위 구문 분석(상향식 구문 분석)
    • 위에서 아래로 왼쪽에서 오른쪽으로 스캔하면서 구문 분석을 한다
    • 파싱은 오른쪽에서 왼쪽으로 진행한다.
      • 주어진 소스 코드나 데이터를 읽고 이해하는 과정
    • 입력을 스캔하면서 토큰을 스택에 넣습니다.
    • 스택의 상태와 룩어헤드 토큰을 기반으로 문법 규칙을 적용할지 결정합니다.
    • 문법 규칙을 적용할 때 스택에서 토큰을 꺼내고 새로운 구문 구조를 스택에 쌓습니다. 이 과정을 "reduce"라고 합니다.
    • 이 과정을 반복하여 최종적으로 문법 규칙을 만족하는 하나의 구조를 얻습니다
    • 알기 쉽게 설명하자면
      • Q
        • 하향식 구문 분석은 이해했는데 상향식 구문 분석도 바로 이해가 안되네... 비유나 알기 쉽게 설명해준뒤 다시 설명해 줄 수 있겠어?
      • A
          • 상향식 구문 분석을 수행하는 것은 퍼즐 맞추기와 비슷합니다. 퍼즐 조각들이 입력 토큰이라고 가정해봅시다. 퍼즐을 맞추는 과정에서 조각들을 하나씩 확인하고, 서로 연결되는 조각들을 찾아 하나의 큰 퍼즐 조각으로 합칩니다. 이렇게 조각들을 계속 연결해 나가다 보면, 최종적으로 완성된 퍼즐을 얻게 됩니다.
          • -상향식 구문 분석도 마찬가지로, 입력 토큰들을 하나씩 처리하면서 서로 연결되는 부분 구조를 찾습니다. 부분 구조들이 문법 규칙을 만족하면, 이를 하나의 더 큰 구조로 합칩니다(reduce). 이 과정을 반복하여 최종적으로 주어진 문법을 만족하는 하나의 구조를 얻습니다.
        • 상향식 구문 분석도 문법검사가 목적이므로 결국 미리 정해진 경우의 수에 해당 되는 CASE가 아니라면 문법에러 - 컴파일 에러가 난다.
          • 문법의 경우의 수가 많긴 하지만 경우의 수로 나누지 못할 정도로 불가능한 수는 아니기 때문
    • 타입 검사
    • 바인딩
    • 최적화
  • java Copy code int sum(int a, int b) { return a + b } // 세미콜론이 누락되어 문법 오류 발생
  • JVM실행
    • 인터프리팅
    • JIT(Just In Time)컴파일러