본문 바로가기
오래된 글

C/C++ 컴파일 과정 | gcc/g++ 명령어

by pagehit 2019. 10. 24.
반응형

소스 코드는 아래의 그림과 같은 컴파일 과정을 거쳐 실행 파일로 만들어 집니다.

전처리기preprocessor: 소스 코드의 주석 제거, define을 치환하는 기능을 합니다.
컴파일러compiler: 어셈블리 파일로 변환합니다. 어셈블리 코드는 CPU 명령어 조합이며, 어셈블리어는 CPU에 의존적입니다.
어셈블러assembler: 오브젝트 코드 파일로 변환합니다. 오브젝트 코드는 0과 1로 이루어진 이진 코드입니다. 그리고 아직 주소 정보가 확정되지 않은 상태입니다.
링커linker: 오브젝트 파일들을 묶어서 실행 코드 파일로 변환합니다. 운영체제가 로딩할 수 있도록 주소 정보를 할당한 파일을 만들어 냅니다. 따라서 링커는 운영체제에 의존적입니다.

컴파일 과정을 조금 더 자세하게 살펴보겠습니다.

전처리 과정

전처리기는 헤더 파일을 삽입하고, 메크로를 치환해 적용합니다. 전처리기가 #include 문장을 만나면 해당 헤더 파일을 찾아서 헤더 파일의 내용을 삽입합니다. 그리고 #define이나 #ifdef과 같은 매크로를 만나면 심볼 테이블symbol table에 저장한 뒤, 심볼 테이블에 있는 문자열과 같은 문자열을 소스코드에서 만나면 매크로를 치환합니다.

컴파일 과정

컴파일러가 하는 역할은 꽤 많기 때문에 front-end, middle-end, back-end로 나누어 볼 수 있습니다.
front-end에서는 프로그래밍 언어를 처리합니다. 이 단계에서 소스 코드가 문법에 맞게 작성되었는지 분석합니다. 어휘 분석과 구문 분석, 의미 분석, 중간 표현(중간 코드) 생성을 합니다.

어휘 분석lexical analysis: 소스 코드를 의미 있는 최소 단위 토큰token으로 나눕니다.
구문 분석syntax analysis: 토큰으로 파스 트리parse tree를 생성하여 문법 오류를 검출합니다.
의미 분석semantic analysis: 파스 트리를 이용해 의미상 오류가 있는지 검사합니다(함수의 매개변수, 변수의 자료형, 값을 0으로 나누는지, 정수와 문자열을 더하는지 등).
중간 코드 생성code generation: 트리 형태의 중간 표현 GIMPLE tree를 생성합니다.

middle-end에서는 최적화를 수행합니다. 최적화는 대부분의 상용 컴파일러에서 이루어지고 있습니다.

back-end에서도 최적화가 이루어지며 어셈블리 코드를 생성합니다.

어셈블 과정

어셈블리 코드를 0과 1의 기계어로 변환합니다. 최종적으로 생성되는 목적 코드object file은 바이너리 포맷 구조를 갖습니다.

링킹 과정

어셈블러에 의해 생성된 목적 코드 파일들과 이 프로그램에서 사용된 라이브러리들을 연결시키는 작업을 합니다. 이 과정에서 printf()와 같은 함수에 대한 라이브러리가 연결됩니다.
최종적으로 정리해보면 아래와 같은 그림을 생각해 볼 수 있습니다.


그러면 실제 코드는 어떻게 만들어지는지 우분투에서 GCC 명령어를 사용하여 살펴봅시다.

먼저 아래와 같이 Hello world를 출력하는 간단한 프로그램을 작성해봅시다.

그 다음 코드에 전처리기를 적용해 생성되는 filename.i 코드를 열어보면 아래와 같습니다. 표준 출력을 위해 stdio.h 파일만 포함시켰을 뿐인데 약 800줄 가량이 더 늘어났습니다.

그리고 gcc에 -S 옵션을 주어 어셈블리어 코드로 만들어 봅시다. 생성되는 filename.s 파일을 열어 보면 아래와 같은 어셈블리어 코드가 보입니다.

오브젝트 파일은 0과 1로된 기계어 코드이므로 이를 보려면 헥사코드를 지원하는 UltraEdit 같은 에디터가 필요합니다.
$ gcc --save-temps -o test test.c
참고로 위의 명령어를 이용하면 소스 코드가 실행파일로 변하면서 생성되는 중간 파일들을 모두 저장할 수 있습니다.


간단하게 자주 사용하는 GCC 옵션도 한 번 살펴 보겠습니다.

  --help : 간단한 옵션을 출력
  --version 또는 -v : gcc의 버젼을 출력
  -o : 출력 파일명을 지정
  -c : 링킹 과정을 진행 하지 않고 .o 파일인 오브젝트 파일까지만 생성
  -S : 어셈블러까지 진행 하지 않고, 컴파일러까지의 출력인 .S 어셈블러 파일을 생성
  -E : 컴파일러까지 진행 하지 않고, 전처리까지의 출력인 .i 파일을 생성
  -O1 ~ -O3 : 최적화 수준을 지정. 숫자가 클수록 높은 수준의 최적화.
  -g : 디버깅을 위한 정보를 컴파일 하면서 생성
  -D : define 을 할수 있는 옵션
  -l (엘) : 라이브러리 이름을 지정
  -L : 추가 라이브러리 디렉토리를 지정
  -W : 모든 에러 메시지 출력
  -w : 모든 에러 메시지를 출력 하지 않음
  -I(아이) : 추가 헤더 파일이 있는 디렉토리를 지정
  

반응형

댓글