ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 컴파일과정 정리 / cpp와 h를 나누는 이유?
    @ 16. 1 ~ 17. 1/면접관련 2017. 1. 4. 23:35

    c언어등은 고급언어로 프로그래머가 작성하기는 쉽지만 cpu가 그대로 이해할 수 없다. 일련의 컴파일 과정을 거쳐서 cpu가 이해할 수 있는

    기계어로 번역이 되어야 한다. 그 과정을 컴파일 과정이라고 하고, 그 과정을 이해해야지 ㅇ최적화 등에 이용할 수 있다.


    컴퓨터 프로그램의 개발은 코드 작성(프로그래밍)부터 시작된다.

    이렇게 작성된 코드는 사용자(개발자)가 컴퓨터가 수행해 주기를 원하는 내용을 기술한 것이지만, 컴퓨터가 이해할 수 있는 문법(언어)이 아닌 사용자가 이해할 수 있는 문법이다.

    따라서 작성된 코드는 '컴파일(Compile)' 과정을 거쳐 컴퓨터가 이해할 수 있는 언어로 변환되며, 컴파일된 파일을 '오브젝트 파일(Object File)'이라고 부른다. 

    일반적으로 하나의 프로그램은 여러 개의 오브젝트 파일과 공용 라이브러리로 조합이 되며, 하나의 컴퓨터가 실행할 수 있는 프로그램을 완성하기 위한 작업을 '링킹(Linking)'이라고 부른다. 결국 코드를 컴파일 과정과 링킹 과정을 거치면 사용자가 실행할 수 있는 '실행 파일(ex. EXE파일)'이 생성된다.

    완성된 실행파일을 사용자가 실행(Execute)하게 되면 컴퓨터는 해당 프로그램의 내용을 메모리에 적재(Load)시키고 내용에 따라 프로그램을 수행하게 된다. 이러한 일을 수행하는 프로그램을 '로더(Loader)'라고 한다.



    출처: http://seohs.tistory.com/259 [Digital Recipe]


    컴파일 되는 과정

    1. 전처리기 과정

       i 파일이 생성됨

      #define #include #if와 같은 전처리 지시자 해석

    2. 컴파일러 과정

      하드웨어 종속적인 어셈블리코드 s가 

    3. 어셈블러

      어셈블러 오브젝트 파일이 생성(hello.o)

    4. 링킹과 재배치 과정

    (컴파일한 각각의 결과물에서 최종적인 실행 파일을 만드는데 필요한 부분들을 찾아 연결하는 작업)

    * 여기서 나오는 정적 링크, 동적 링크가 나오는데..

    경우에 따라서는 필요한 모든 목적 코드를 연결해서 완전히 완성된 실행파일을 만들어내지 않고, 프로그램이 실행되는 중간에 프로그램 외부에 존재하는 필요한 목적 코드를 찾아서 연결하는 경우도 있다. 전자를 정적, 후자를 동적링크라고 부른다.

    * dll 인 파일들이 동적 링크를 위한 라이브러리들이다. 

      실행파일이 생성됨




    C++ 컴파일 과정

    C++을 컴파일하는 과정은 크게 2가지로 나눌 수 있습니다.

    첫 번째는 source text 파일을 binary object 파일(.OBJ/.O)로 컴파일하는 것이고, 두 번째는 모든 object파일을 link해 실행 가능한 binary파일을 만드는 것입니다.

    첫 번째 과정에서는

    • 헤더 파일을 include하지 않은 경우: CPP파일만 컴파일합니다.
    • 헤더 파일을 include한 경우는 관련: CPP파일이나 library가 같이 컴파일됩니다.

    CPP파일들은 서로서로 독립적으로 컴파일됩니다.

    그래서 다음과 같이 A.CPP에 B.CPP파일에서 정의한 symbol을 쓰면 A.CPP는 B.CPP파일에 해당 symbol이 있는지 알 수 없으므로 컴파일되지 않습니다.

    // A.CPP
    void fncA()
    {
       fncInB(); // B.CPP에 정의된 함수
    }
    
    /**************파일구분****************/
    
    // B.CPP
    void fncInB()
    {
       // 어떤 코드
    }
    

    이 문제를 해결하려면 A.CPP에 해당 symbol을 선언해 줘야 합니다.

    // A.CPP
    void fncInB() ; // B.CPP의 fncInB()선언
    
    /**************파일구분****************/
    
    void fncA()
    {
       fncInB() ; // B.CPP에 정의되어 있음
    }
    

    C.CPP에서도 fncInB()를 쓰려면 어떻게 해야 할까요? 이 역시 마찬가지로 C.CPP에 fncInB()를 선언해주면 됩니다.

    CPP파일이 아주 많은 경우엔?

    서로 다른 10개의 CPP파일이 모두 fncInB()를 쓴다고 하면 10개의 CPP파일에 한 번씩 총 10번 선언해야 할까요? fncInB1()fncInB2()fncInB3(), ... 같이 함수가 더 많아지면 어떻게 해야 할까요?

    이런 코드는 유지/보수하기 힘들기 때문에 헤더 파일안에 fncInB1()fncInB2()fncInB3()를 한 번씩 선언한 뒤 각 CPP파일에서 헤더 파일을 include하는 방식을 씁니다.

    컴파일러가 include를 처리할 때 해당 파일의 내용이 그 위치에 대신 들어가기 때문에 프로그래머가 직접 복사-붙여넣기 하지 않고 컴파일러에게 그 일을 대신시키는 것이지요.

    // B.HPP - 헤더파일
    void fncInB() ; //함수를 선언
    
    /**************파일구분****************/
    
    // A.CPP
    #include "B.HPP"
    
    void fncA()
    {
       fncInB() ; // B.CPP에 정의된 함수
    }
    
    /**************파일구분****************/
    
    // B.CPP
    #include "B.HPP"
    
    void fncInB()
    {
       // 어떤 코드
    }
    
    /**************파일구분****************/
    
    // C.CPP
    #include "B.HPP"
    
    void fncC()
    {
       fncInB() ; // B.CPP에 정의된 함수
    }
    


    헤더파일을 만드는 이유에 대해 먼저 말씀 드리겠습니다.

    .cpp 파일로 만들어진 오브젝트 파일에 있는 함수들의 내용을 다른 소스 파일에서 사용할 수 있도록 하기 위함입니다.

    A.cpp 파일에서 B.cpp 파일에 들어있는 함수나 클래스를 사용하기 위해서는 함수의 프로토 타입이나 클래스 선언 등의 정보가 필요합니다.
    (그래야 어떤 함수(또는 메소드)를 호출할때 인자값이 필요하고, 안필요하고와 리턴 타입을 알 수 있으니까요.)

    그런 정보들을 파악하기 위해서 헤더 파일을 만들어서 관리합니다.

    그리고, 헤더파일의 사용에 대해서 질문하셨는데.. 이는 라이브러리를 생각하시면 간단합니다.
    라이브러리와 같은 것들은 cpp 파일을 제공하지 않는 경우가 많기 때문입니다.

    그리고, 무엇보다 관리와 공유가 편하다는 장점이 있습니다. :)



Designed by Tistory.