IT_Study/C++

[C++] 프로그램 컴파일 과정 - 선행처리기(preprocessor), 컴파일러(compiler), 링커(linker)

__Vivacé__ 2023. 3. 1. 23:35

 

C++ 프로그램 컴파일 과정

 

  1. 1. 소스 파일 작성
  2. 2. 선행처리기(preprocessor)에 의한 선행처리
  3. 3. 컴파일러(compiler)에 의한 컴파일
  4. 4. 링커(linker)에 의한 링크

 

 

C++에서 소스 파일에서 실행 파일을 생성하는 순서

 

 


 

1. 소스 파일 작성

소스 파일(.cpp) : C++ 문법에 맞게 논리적으로 작성된 프로그램

 


2. 선행처리기(preprocessor)에 의한 선행처리

  1. 전처리 지시자 처리 : 소스 코드 내에서 사용되는 매크로, 헤더 파일, 조건부 컴파일 등을 처리
  2. 주석 제거 : 소스 코드 내의 주석을 제거하여 컴파일 시 영향을 주지 않도록 처리합니다.
  3. 난독화 : 코드의 가독성을 낮추어 코드 분석을 어렵게 하는 난독화 기능을 제공합니다.

 

*전처리 지시자(Preprocessor directive)란?

    # 기호로 시작하는 특별한 명령어를 의미

 

  • #include: 다른 소스 파일현재 소스 파일에 포함시키는 역할
#include <iostream>
// iostream : C++에서 입력(input)과 출력(output)을 수행하기 위해 
//           사용하는 표준 입출력 라이브러리를 정의하고 있는 **헤더 파일**

// **헤더 파일(Header file)** vs **소스 파일(Source file)**
// **헤더 파일** : 소스 파일에서 사용되는 선언을 포함, 보통 .h 또는 .hpp 확장자를 가짐
// **소스 파일** : 프로그램의 실제 코드를 포함, 보통 .cpp 또는 .cc 확장자를 가짐

 

  • #define: 프로그램 내에서 사용되는 상수나 매크로를 정의하는 역할
    • 코드의 재사용성을 높이는 역할
#define MAX 100000

// #define **지시자(Directive)**로,
// 상수 100000을 MAX라는 **식별자(Identifier)**로 정의 

// **식별자(Identifier)** vs **변수(Variable)**
// **식별자** : 변수, 함수, 클래스, 구조체, 열거형 상수 등에 부여되는 이름을 의미
// **변수** : 프로그램에서 데이터를 저장하는 메모리 공간을 의미

 

  • #ifdef, #ifndef, #else, #endif: 조건부 컴파일을 수행하는 역할
#include <iostream>

#define OPTION_A 1 // OPTION_A를 정의
#define OPTION_B 2 // OPTION_B를 정의

using namespace std;

// 1. ifdef 사용법

// OPTION_A가 정의되어 있을 때만 코드 블록을 컴파일함
#ifdef OPTION_A
void functionA() {
    cout << "Option A is enabled." << endl;
}
#endif

// 2. ifndef, else 사용법

// OPTION_B가 정의되어 있지 않을 때만 코드 블록을 컴파일함
#ifndef OPTION_B
void functionB() {
    cout << "Option B is not enabled." << endl;
}
#else
void functionB() {
    cout << "Option B is enabled." << endl;
}
#endif

// 둘 다 정의되어 있지 않을 때만 코드 블록을 컴파일함
#ifndef OPTION_A
#ifndef OPTION_B
void functionC() {
    cout << "Neither option A nor option B is enabled." << endl;
}
#endif
#endif

int main() {
#ifdef OPTION_A
    functionA();
#endif

#ifdef OPTION_B
    functionB();
#else
    cout << "Option B is not enabled." << endl;
#endif

    functionC(); // OPTION_A 와 OPTION_B가 정의되어 있지 않으면 실행 가능

    return 0;
}

 

  • #error: 컴파일 중에 오류 메시지를 출력하는 역할
#include <iostream>

#define MAX_NUMBER 1000

using namespace std;

int main() {
    int number;

    cin >> number;

    // 입력된 값이 MAX_NUMBER보다 크면 에러 메시지 출력 후 컴파일 중단
    #if (number > MAX_NUMBER)
        #error "The number is too big."
    #endif

    cout << "The number is " << number << endl;

    return 0;
}

 

  • #pragma: 컴파일러에게 특별한 지시를 제공하는 역할
// 컴파일러에서 발생하는 4996 경고를 무시하라는 뜻
#pragma warning(disable: 4996)

// GCC 컴파일러에서 코드 최적화를 수행할 때, 최적화 수준을 3으로 지정
#pragma GCC optimize(3)

// VC++ 컴파일러에서 mylib.lib라는 라이브러리 파일을 링커에 추가
#pragma comment(lib, "mylib.lib")

// 구조체의 크기를 1로 지정
#pragma pack(push, 1)
struct MyStruct {
    char a;
    int b;
};
#pragma pack(pop)

 


3. 컴파일러(compiler)에 의한 컴파일

소스 코드(high level language)를 기계어 코드(low level language)로 변환하는 과정

 

과정

// 덧셈 예시

int add(int a, int b) {
    return a + b;
}

int main() {
    int result = add(1, 2);
    return 0;
}

 

 

  1. 어휘 분석(Lexical analysis): 소스 코드를 의미 있는 작은 단위인 토큰(Token)으로 분해
[int] [add]([int] [a], [int] [b]) { [return] [a] + [b]; }
[int] [main] () { [int] [result] = [add]([1], [2]); [return] [0]; }

// 대괄호 안의 문자열 : 토큰을 의미
// 대괄호 밖의 문자열 : 구분자

 

  1. 구문 분석(Parsing): 생성된 토큰들이 문법적으로 올바른 문장인지 확인
    • 추상 구문 트리(Abstract Syntax Tree, AST)라는 중간 표현을 생성
           PROGRAM
              |
           FUNCTION           FUNCTION
              |                  |
             ADD                MAIN
              |                  |
       ARGUMENTS              STATEMENTS
         /   \\                    |
      INT     INT         VARIABLE_ASSIGNMENT
        |       |                 |
        a       b                result
                                 |
                              FUNCTION_CALL
                                 |
                                ADD
                               /   \\
                              1     2

 

  1. 의미 분석(Semantic analysis): 추상 구문 트리를 분석하여 프로그램의 의미를 파악
    • 변수의 타입 검사, 함수의 인자 검사, 변수와 함수의 정의 여부 검사 등이 수행
    • 오류가 나면, 해당 오류를 보고하고 컴파일 중단

 

  1. 중간 코드 생성(Intermediate code generation): 기계어 코드 생성 전, 코드 최적화(Optimization) 작업을 수행하고, 기계어 코드를 생성하기 위한 준비를 함
// x86 아키텍처를 기반으로 한 어셈블리어 코드 예시

int add(int a, int b) {
    return a + b;
}

int main() {
    int result;
    push 2
    push 1
    call add
    mov result, eax
    xor eax, eax
    ret
}

add:
    mov eax, [esp+4]
    add eax, [esp+8]
    ret

 

  1. 기계어 코드 생성(Code generation)
    • 여러 최적화 기술이 적용하여 중간 코드를 0과 1로 이루어진 기계어 코드로 변환
    • 오브젝트 파일(확장자 .o 또는 .obj) 생성

 


 

4. 링커(linker)에 의한 링크

링커(linker) : 컴파일 과정에서 생성된 오브젝트 파일(Object File)이나 라이브러리 파일(Library File)을 연결하여 하나의 실행 파일(Executable File) 또는 라이브러리 파일로 만들어주는 도구