컴파일 / 링크 프로세스는 어떻게 작동합니까?


416

컴파일 및 링크 프로세스는 어떻게 작동합니까?

(참고 : 이것은 Stack Overflow의 C ++ FAQ에 대한 항목 입니다.이 양식으로 FAQ를 제공한다는 아이디어를 비판하려면이 모든 것을 시작한 메타에 게시 하면됩니다. 이 질문은 C ++ 대화방 에서 모니터링되며 여기서 FAQ 아이디어는 처음부터 시작되었으므로 아이디어를 얻은 사람들이 귀하의 답변을 읽을 가능성이 큽니다.)

답변:


554

C ++ 프로그램 컴파일에는 세 단계가 포함됩니다.

  1. 전처리 : 전처리 기는 C ++ 소스 코드 파일을 사용하여 #includes, #defines 및 기타 전 처리기 지시문을 처리합니다. 이 단계의 결과는 사전 프로세서 지시문이없는 "순수한"C ++ 파일입니다.

  2. 컴파일 : 컴파일러는 전 처리기의 출력을 가져 와서 그로부터 객체 파일을 생성합니다.

  3. 연결 : 링커는 컴파일러가 생성 한 객체 파일을 가져와 라이브러리 또는 실행 파일을 생성합니다.

전처리

전처리 기는 및과 같은 전 처리기 지시문을 처리합니다 . C ++의 구문과 무관하므로주의해서 사용해야합니다.#include#define

그것은 대체하여 한 번에 하나 개의 C ++ 소스 파일에서 작동 #include, (대개 선언입니다) 각각의 파일의 내용과 지시를 매크로 (교체 일 #define), 그리고 다른 텍스트의 부분의 따라 선택 #if, #ifdef#ifndef지시를.

전처리 기는 전처리 토큰 스트림에서 작동합니다. 매크로 대체는 토큰을 다른 토큰으로 대체하는 것으로 정의됩니다 (연산자 ##가 의미가있는 경우 두 토큰을 병합 할 수 있음).

이 후, 전처리 기는 위에서 설명한 변환으로 인해 토큰 스트림 인 단일 출력을 생성합니다. 또한 컴파일러에 각 줄의 출처를 알려주는 특별한 마커를 추가하여 합리적인 오류 메시지를 생성하는 데 사용할 수 있도록합니다.

#ifand #error지시문 을 영리하게 사용하면이 단계에서 일부 오류가 발생할 수 있습니다 .

편집

컴파일 단계는 전 처리기의 각 출력에서 ​​수행됩니다. 컴파일러는 순수한 C ++ 소스 코드 (이전 프로세서 지시문이 없음)를 구문 분석하여 어셈블리 코드로 변환합니다. 그런 다음 해당 코드를 기계 코드로 어셈블하는 기본 백엔드 (툴체인의 어셈블러)를 호출하여 일부 형식 (ELF, COFF, a.out, ...)으로 실제 바이너리 파일을 생성합니다. 이 오브젝트 파일에는 입력에 정의 된 기호의 컴파일 된 코드 (2 진 형식)가 포함되어 있습니다. 객체 파일의 기호는 이름으로 참조됩니다.

객체 파일은 정의되지 않은 심볼을 참조 할 수 있습니다. 선언을 사용할 때, 정의를 제공하지 않는 경우입니다. 컴파일러는 이것을 신경 쓰지 않으며 소스 코드가 올바르게 구성되어 있으면 행복하게 객체 파일을 생성합니다.

일반적으로 컴파일러를 사용하면이 시점에서 컴파일을 중지 할 수 있습니다. 각 소스 코드 파일을 개별적으로 컴파일 할 수 있기 때문에 매우 유용합니다. 이것이 제공하는 이점 은 단일 파일 만 변경하는 경우 모든 것을 다시 컴파일 할 필요가 없다는 입니다.

생성 된 오브젝트 파일은 나중에 쉽게 재사용 할 수 있도록 정적 라이브러리라는 특수 아카이브에 넣을 수 있습니다.

이 단계에서 구문 오류 또는 실패한 과부하 해결 오류와 같은 "일반적인"컴파일러 오류가보고됩니다.

연결

링커는 컴파일러가 생성 한 객체 파일에서 최종 컴파일 출력을 생성하는 것입니다. 이 출력은 공유 (또는 동적) 라이브러리 (이름은 비슷하지만 앞에서 언급 한 정적 라이브러리와 공통점이 많지 않음) 또는 실행 파일 일 수 있습니다.

정의되지 않은 기호에 대한 참조를 올바른 주소로 대체하여 모든 오브젝트 파일을 링크합니다. 이러한 각 심볼은 다른 객체 파일이나 라이브러리에서 정의 할 수 있습니다. 그것들이 표준 라이브러리가 아닌 다른 라이브러리에 정의되어 있다면, 링커에 그들에 대해 알려 주어야합니다.

이 단계에서 가장 일반적인 오류는 정의가 누락되었거나 정의가 중복 된 것입니다. 전자는 정의가 존재하지 않거나 (즉, 작성되지 않음) 오브젝트 파일 또는 라이브러리가있는 링커에 제공되지 않았 음을 의미합니다. 후자는 명백하다 : 동일한 기호가 두 개의 다른 객체 파일 또는 라이브러리에 정의되었다.


39
컴파일 단계는 또한 객체 파일로 변환하기 전에 어셈블러를 호출합니다.
manav mn 2019

3
최적화는 어디에 적용됩니까? 언뜻보기에는 컴파일 단계에서 수행되는 것처럼 보이지만 다른 한편으로는 적절한 최적화가 링크 후에 만 ​​수행 될 수 있다고 상상할 수 있습니다.
바트 반 Heukelom

6
@BartvanHeukelom 전통적으로 컴파일 과정에서 수행되었지만 현대의 컴파일러는 소위 "링크 시간 최적화"를 지원하므로 번역 단위에서 최적화 할 수있는 이점이 있습니다.
R. Martinho Fernandes

3
C에는 같은 단계가 있습니까?
케빈 주

6
링커가 라이브러리의 클래스 / 방법을 참조하는 기호를 주소로 변환하는 경우, 라이브러리 바이너리가 OS가 일정하게 유지하는 메모리 주소에 저장되어 있습니까? 링커가 모든 대상 시스템의 stdio 바이너리의 정확한 주소를 어떻게 알 수 있는지에 대해서는 혼란 스럽습니다. 파일 경로는 항상 동일하지만 정확한 주소는 변경 될 수 있습니다.
Dan Carter

42

이 주제는 CProgramming.com에서 논의됩니다 :
https://www.cprogramming.com/compilingandlinking.html

저자가 쓴 내용은 다음과 같습니다.

컴파일은 실행 파일을 만드는 것과 완전히 다릅니다! 대신 실행 파일을 만드는 것은 컴파일과 링크라는 두 가지 구성 요소로 나누어 진 다단계 프로세스입니다. 실제로, 프로그램이 "정상적으로 컴파일"되더라도 연결 단계 동안 오류로 인해 실제로 작동하지 않을 수 있습니다. 소스 코드 파일에서 실행 파일로 이동하는 전체 프로세스를 빌드라고하는 것이 좋습니다.

편집

컴파일은 소스 코드 파일 (.c, .cc 또는 .cpp)의 처리 및 '객체'파일의 생성을 말합니다. 이 단계는 사용자가 실제로 실행할 수있는 것을 만들지 않습니다. 대신, 컴파일러는 컴파일 된 소스 코드 파일에 해당하는 기계어 명령어 만 생성합니다. 예를 들어, 세 개의 개별 파일을 컴파일 (링크하지는 않음)하면 각각 .o 또는 .obj라는 이름을 가진 세 개의 객체 파일이 출력으로 생성됩니다 (확장자는 컴파일러에 따라 다름). 이러한 각 파일에는 소스 코드 파일을 기계 언어 파일로 변환 한 파일이 포함되어 있지만 아직 실행할 수는 없습니다! 운영 체제에서 사용할 수있는 실행 파일로 바꿔야합니다. 그것이 링커가 들어오는 곳입니다.

연결

연결은 여러 개체 파일에서 단일 실행 파일을 만드는 것을 말합니다. 이 단계에서는 링커가 정의되지 않은 함수 (일반적으로 주 자체)에 대해 불평하는 것이 일반적입니다. 컴파일하는 동안 컴파일러가 특정 함수에 대한 정의를 찾을 수 없으면 함수가 다른 파일에 정의되어 있다고 가정합니다. 그렇지 않은 경우 컴파일러가 알 수있는 방법이 없습니다. 한 번에 두 개 이상의 파일 내용을 보지 않습니다. 반면 링커는 여러 파일을보고 언급되지 않은 함수에 대한 참조를 찾으려고 시도 할 수 있습니다.

별도의 컴파일 및 연결 단계가있는 이유를 물을 수 있습니다. 첫째, 그런 식으로 구현하는 것이 더 쉽습니다. 컴파일러는 기능을 수행하고 링커는 기능을 분리하여 프로그램의 복잡성을 줄입니다. 또 다른 (더 명백한) 장점은 파일을 변경할 때마다 컴파일 단계를 다시 수행 할 필요없이 큰 프로그램을 만들 수 있다는 것입니다. 대신 "조건부 컴파일"이라고하는 변경된 소스 파일 만 컴파일하면됩니다. 나머지의 경우, 오브젝트 파일은 링커에 충분한 입력입니다. 마지막으로 사전 컴파일 된 코드 라이브러리를 간단하게 구현할 수 있습니다. 객체 파일을 만들고 다른 객체 파일과 같이 링크하면됩니다.

조건 컴파일의 이점을 최대한 활용하려면 마지막 컴파일 이후에 변경된 파일을 시도하고 기억하는 것보다 도움이되는 프로그램을 얻는 것이 더 쉽습니다. 물론 해당 오브젝트 파일의 시간 소인보다 시간 소인이 큰 모든 파일을 재 컴파일 할 수 있습니다. IDE (Integrated Development Environment)로 작업중인 경우 이미이를 처리 할 수 ​​있습니다. 명령 행 도구를 사용하는 경우 대부분의 * nix 배포판과 함께 제공되는 make라는 멋진 유틸리티가 있습니다. 조건부 컴파일과 함께, 프로그래밍을위한 다양한 컴파일 기능 (예 : 디버깅을위한 자세한 출력을 생성하는 버전이있는 경우)과 같은 프로그래밍을위한 몇 가지 다른 멋진 기능이 있습니다.

컴파일 단계와 링크 단계의 차이점을 알면 버그를 쉽게 찾을 수 있습니다. 컴파일러 오류는 일반적으로 구문 상으로 세미콜론이없고 괄호가 없습니다. 연결 오류는 일반적으로 누락되거나 여러 정의와 관련이 있습니다. 링커에서 함수 또는 변수가 여러 번 정의되었다는 오류가 발생하면 소스 코드 파일 중 두 개가 동일한 함수 또는 변수를 가지고 있다는 오류입니다.


1
내가 이해하지 못하는 것은 전처리 기가 하나의 슈퍼 파일을 만들기 위해 #includes와 같은 것을 관리하면 그 후에 링크 할 것이 없다는 것입니다.
binarysmacker

@binarysmacer 아래에 쓴 내용이 당신에게 의미가 있는지보십시오. 나는 내부에서 문제를 설명하려고 노력했다.
타원형보기

3
@binarysmacker 이것에 대해 언급하기에는 너무 늦었지만, 다른 사람들은 이것이 유용하다고 생각할 것입니다. youtu.be/D0TazQIkc8Q 기본적으로 헤더 파일을 포함하고 이러한 헤더 파일에는 일반적으로 변수 / 함수 선언 만 포함되며 정의가없고 별도의 소스 파일에 정의가있을 수 있으므로 전처리 기는 선언 만 포함하고 정의는 포함하지 않습니다. 변수 / 함수를 사용하는 소스 파일과이를 정의하는 소스 파일을 링크합니다.
Karan Joisher 2016 년

24

표준 전선에서 :

  • 변환 유닛은 소스 파일에 포함 헤더 및 소스 파일 이하 조건부 포함 처리기 지시문 스킵 모든 소스 라인의 조합이다.

  • 표준은 번역에서 9 단계를 정의합니다. 처음 4 개는 전처리에 해당하고, 다음 3 개는 컴파일이며, 다음 4 개는 템플릿의 인스턴스화 ( 인스턴스화 단위 생성 )이고 마지막 하나는 연결입니다.

실제로 8 단계 (템플릿 인스턴스화)는 종종 컴파일 프로세스 중에 수행되지만 일부 컴파일러는이를 링크 단계로 지연시키고 일부는 2 단계로 확산시킵니다.


14
9 단계를 모두 나열 할 수 있습니까? 그것은 대답에 좋은 추가 일 것입니다. :)
jalf


@ jalf, @sbi가 가리키는 대답의 마지막 단계 직전에 템플릿 인스턴스화를 추가하십시오. IIRC는 넓은 문자를 처리 할 때 정확한 단어 표현에 미묘한 차이가 있지만 다이어그램 레이블에 표시되지 않는다고 생각합니다.
AProgrammer

2
@ sbi 예, 그러나 이것은 FAQ 질문 일 것입니다. 이 정보를 여기서 사용할 수 없습니까? ;)
jalf

3
@AProgrammmer : 단순히 이름으로 나열하면 도움이됩니다. 그런 다음 사람들은 더 자세한 정보를 원하면 무엇을 검색해야하는지 알고 있습니다. 어쨌든, 어떤 경우에도 답변을 +1했습니다 :)
jalf

14

스키니는 CPU가 메모리 주소에서 데이터를로드하고, 데이터를 메모리 주소로 저장하며, 처리 된 명령 시퀀스에서 조건부로 점프하면서 메모리 주소에서 순차적으로 명령을 실행한다는 것입니다. 이들 3 가지 카테고리의 명령어 각각은 머신 명령어에 사용될 메모리 셀에 대한 어드레스를 계산하는 것을 포함한다. 기계 명령어는 관련된 특정 명령어에 따라 길이가 가변적이기 때문에 기계 코드를 작성할 때 변수 길이를 함께 묶기 때문에 주소를 계산하고 빌드하는 과정에는 두 단계가 있습니다.

먼저 각 셀에 정확히 무엇이 들어가는 지 알기 전에 가능한 한 최선의 메모리 할당을 배치합니다. 우리는 바이트, 단어 또는 명령어와 리터럴 및 데이터를 구성하는 모든 것을 알아냅니다. 우리는 메모리 할당을 시작하고 우리가 갈 때 프로그램을 만들 값을 만들고, 돌아가서 주소를 수정해야하는 곳을 적어 둡니다. 그 장소에서 우리는 메모리 크기를 계속 계산할 수 있도록 위치를 채우는 더미를 넣었습니다. 예를 들어 첫 번째 기계 코드는 하나의 셀을 사용할 수 있습니다. 다음 기계 코드는 하나의 기계 코드 셀과 두 개의 주소 셀을 포함하는 3 개의 셀을 사용할 수 있습니다. 이제 주소 포인터는 4입니다. 우리는 op 코드 인 machine cell에 무엇이 들어가는 지 알고 있지만, 데이터가 어디에 위치하는지 알 때까지 address cell에 무엇이 들어가는 지 계산해야합니다.

소스 파일이 하나만 있으면 컴파일러는 이론적으로 링커없이 완전히 실행 가능한 머신 코드를 생성 할 수 있습니다. 2 패스 프로세스에서는 머신로드 또는 저장 명령어가 참조하는 모든 데이터 셀에 대한 모든 실제 주소를 계산할 수 있습니다. 그리고 절대 점프 명령이 참조하는 모든 절대 주소를 계산할 수 있습니다. 이것은 링커없이 Forth와 같은 간단한 컴파일러가 작동하는 방식입니다.

링커는 코드 블록을 개별적으로 컴파일 할 수있게하는 것입니다. 이렇게하면 코드를 작성하는 전체 프로세스 속도가 빨라지고 나중에 블록이 사용되는 방식에 따라 유연성이 향상 될 수 있습니다. 즉, 모든 주소에 1000을 추가하여 블록을 1000 개의 주소 셀로 스쿠 트하는 등 메모리에 재배치 할 수 있습니다.

따라서 컴파일러가 출력하는 것은 아직 완전히 빌드되지 않은 대략적인 머신 코드이지만 모든 크기를 알 수 있습니다. 다시 말해서 모든 절대 주소의 위치를 ​​계산할 수 있습니다. 컴파일러는 이름 / 주소 쌍인 심볼 목록도 출력합니다. 기호는 이름이있는 모듈의 기계 코드에서 메모리 오프셋을 나타냅니다. 오프셋은 모듈에서 심볼의 메모리 위치까지의 절대 거리입니다.

그것이 우리가 링커에 도착하는 곳입니다. 링커는 먼저이 모든 머신 코드 블록을 끝에서 끝까지 때리고 각 블록이 시작되는 위치를 기록합니다. 그런 다음 더 큰 레이아웃에서 모듈 내 상대 오프셋과 모듈의 절대 위치를 더하여 고정 할 주소를 계산합니다.

분명히 나는 ​​이것을 지나치게 단순화하여 그것을 이해하려고 시도 할 수 있으며, 나에게 혼란의 일부인 객체 파일, 심볼 테이블 등의 전문 용어를 의도적으로 사용하지 않았습니다.


13

GCC는 C / C ++ 프로그램을 4 단계로 실행 파일로 컴파일합니다.

예를 들어 gcc -o hello hello.c다음과 같이 수행됩니다.

1. 전처리

GNU C 전 처리기 ( cpp.exe) 를 통한 전처리 . 헤더 ( #include) 를 포함 하고 매크로 ( #define)를 확장합니다 .

cpp hello.c > hello.i

결과 중간 파일 "hello.i"에는 확장 된 소스 코드가 포함됩니다.

2. 편집

컴파일러는 사전 처리 된 소스 코드를 특정 프로세서의 어셈블리 코드로 컴파일합니다.

gcc -S hello.i

-S 옵션은 객체 코드 대신 어셈블리 코드를 생성하도록 지정합니다. 결과 어셈블리 파일은 "hello.s"입니다.

3. 조립

어셈블러 ( as.exe)는 오브젝트 파일 "hello.o"에서 어셈블리 코드를 기계 코드로 변환합니다.

as -o hello.o hello.s

4. 링커

마지막으로 링커 ( ld.exe)는 객체 코드를 라이브러리 코드와 연결하여 실행 파일 "hello"를 생성합니다.

    ld -o hello hello.o ... 라이브러리 ...

9

URL을보십시오 : http://faculty.cs.niu.edu/~mcmahon/CS241/Notes/compile.html
C ++의 완전한 결합 프로세스가이 URL에 명확하게 소개되어 있습니다.


2
공유해 주셔서 감사합니다. 이해하기가 너무 간단하고 간단합니다.
Mark

좋은 리소스, 여기에 프로세스에 대한 기본 설명을 넣을 수 있습니까? 답변은 알고리즘에 의해 낮고 URL이 짧고 품질이 낮은 b / c로 표시됩니다.
JasonB

내가 찾은 좋은 짧은 튜토리얼 : calleerlandsson.com/the-four-stages-of-compiling-ac-program
Guy Avraham
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.