전처리 과정 with cc1 -E
전처리과정은 cc1 -E
에 의해 수행되는데, 이는 크게 두 부분으로 나눌 수 있다.
- 헤더 파일의 삽입
- 시스템 헤더파일 디렉토리로 등록된,
/usr/local/include
/usr/lib/gcc-lib/i386-redhatlinux/3.2.2/include
/usr/include
순으로 헤더파일을 찾는다. - 기본 탐색 include 디렉토리를 추가하기 위해서는 gcc 옵션으로
I{include_directory}
를 붙이면 해당 디렉토리를 먼저 탐색할 수 있게 한다.
- 시스템 헤더파일 디렉토리로 등록된,
- 매크로 치환 및 적용
#define
된 부분은 심볼테이블에 저장되고, 심볼 테이블에 들어있는 문자열과 같은 문자열을 만나면 해당 내용으로 치환된다.#ifedf
와 같은 매크로를 처리하기 위해서는cpp0 -D{define_option}
을 지정해 주면 된다. 이는#define {define_option}
을 해 준것과 같다.- 시스템 관련된 predefined macro(
__linux__ 등
)는 이전 전처리기(cpp0
)에서는 사용자가 명시적으로 predefine 해야했으나cc1 -E
에서는 내부적으로 알아서 predefine 된다.
어셈블 과정 with cc1
어셈블 과정은 cc1
에 의해 수행되는데, 이는 크게 세 부분으로 나뉠 수 있다. front-end, middle-end, back-end 가 그것이다.
- front-end
- 언어 종속적인 부분을 처리한다. 소스코드가 올바르게 작성되었는지 분석하고 GIMPLE 트리를 생성한다.
- 어휘 분석, 구문 분석, 의미 분석, 중간 표현 생성을 수행한다.
- middle-end
- GIMPLE 트리를 이용해 아키텍처 비종속적인 최적화를 수행후 RTL(Register Transfer Language: 고급언어와 어셈블리 언어의 중간 형태)을 생성한다.
- GIMPLE 트리를 받아 SSA(Static Single Assignment) 형태로 변환한 후 아키텍쳐 비종속적인 최적화를 수행한다. 그 후 back-end에서 사용할 RTL을 생성한다.
- 아키텍처 비종속적인 최적화의 리스트
- return value optimizations
- full redundancy elimination
- conditional constant propagation
- dead code elimination
- copy propagation
- value range propagation
- branch prediction and profiling
- dead store elimination
- etc.
- RTL 표현을 보기 위해서는
gcc -o main main.c -da
와 같이da
옵션을 주면 된다.
- 아키텍처 비종속적인 최적화의 리스트
- back-end
- 아키텍처 비종속적인 최적화와 아키텍처 종속적인 최적화가 함께 수행된다. 그 후, 목적 코드(어셈블리 코드)를 생성한다.
- 아키텍처 종속적인 최적화의 리스트
- 각종 고전 최적화
- common sub-expression elimination
- GCSE/PRE
- constant propagation
- copy propagation
- cross-jumping, block merging
- basic block re-ordering
- IF-conversion
- 각종 loop 최적화
- loop un-rollling
- 연산 강도 경감
- loop 내의 불변 코드 옮김(Invariant code motion)
- Instruction scheduling
- register allocation
- satifying operand constraints
- 아키텍처 종속적인 최적화의 리스트
- 여기서는 Instruction scheduling, Register allocation이 일어난다.
- 아키텍처 비종속적인 최적화와 아키텍처 종속적인 최적화가 함께 수행된다. 그 후, 목적 코드(어셈블리 코드)를 생성한다.
기계어 코드 생성 with as
NULL
링킹 with collect2
정적 라이브러리와 동적 라이브러리란?
정적 라이브러리
파일 이름이 lib{name_of_library}.a의 형식을 가진다.
/usr/lib/ 디렉토리에 위치한다.
ar -x /usr/lib/libc.a를 수행하면 해당 라이브러리를 풀어 어떤 파일들로 구성되어 있는지 확인할 수 있다.
추가로, libc.a는 ioputs.o 등과같은 목적파일들로 구성되어 있다고 할 때, 이러한 목적 파일에 어떠한 함수가 들어있는지 확인하려면 nm ioputs.o 명령을 수행하면 된다.
이러한 오브젝트들을 gcc 옵션 중 -static 옵션을 주면 main.o와 링크되어 삽입된다.
collect2는 내부적으로 링커인 ld를 호출해 링킹과정을 수행하므로 실제로는 ld -o main main.o scanf.o printf.o가 수행된다.
정적 라이브러리를 이용한 컴파일은 실행 파일에 같은 오브젝트가 중복되어 링크되므로 메모리 공간을 낭비한다는 것이다.
공유 라이브러리
공유 라이브러리를 이용해 오브젝트와 링킹할 때에는 해당 함수를 직접 호출하지 않는다. 대신, PLT(Procedure Linkage Table) 섹션의 특정 영역을 호출하게 링크한다. 여기서, PLT 섹션의 코드에는 GOT(Global Offset Table)(정확히는 GOT.PLT)섹션의 해당 함수 엔트리에 있는 값을 in-direct로 점프하게 되어있다.
이는 프로그램 실행 중, system call을 발생시키고 이는 ELF 헤더를 읽어 동적 링커를 사용한다는 정보를 얻어 수행에 필요한 섹션들과 동적 링커(/lib/ld-linux.so.2)를 프로세스 주소 공간에 로드한다. 그 후, 제어권을 동적 링커에게 넘긴다.
공유 라이브러리는 lib{name_of_library}.so.{major_version}.{minor_version}.{patch_version}으로 구성되어 있다.
표준 C 공유 라이브러리와 동적 링커는 ldd {name_of_library}로 확인할 수 있다.
다음은 collect2의 수행 모습에 대한 간략한 코드와 이에 대한 설명이다.
.../collect2 --eh-frame-hdr -m elf_i386
--hash-style=gnu -dynamic-linker /lib/ld-linux.so.2 -o like
/usr/lib/gcc/crt1.o
/usr/lib/gcc/crt2.o
-L/usr/lib/gcc
like.o
-lgcc --ass-needed -lgcc_s --no-as-needed -lc
/usr/lib/gcc/crtend.o
/usr/lib/gcc/crtn.o
- m elf_i386: 바이너리 포맷과 CPU 타입 지정
- dynamic-linker /lib/ld-linux.so.2: 동적 링커로 ld-linux.so.2를 지정
- -o like: 링크 과정이 끝나고 생성되는 실행 파일명은
like
- L/usr/lib/gcc: 라이브러리 파일을 찾을 디렉토리를 지정
- lgcc: 링크할 라이브러리를 지정
-l 옵션 뒤에오는 것이 라이브러리 명이다. libc.so(libc.a)의 경우 -lc가 된다.
만약 정적 라이브러리와 동적 라이브러리가 동시에 존재한다면 동적 라이브러리 가 우선시 된다.
crt1.o, crtend.o 등의 파일은 초기화 루틴 & 종료 루틴을 위해 링크된다.
'빌드&테스트도구 > GCC&Make&CMake' 카테고리의 다른 글
CMake - (1) CMake의 전반적인 사항들 (0) | 2020.11.08 |
---|---|
make - make에 대한 개요 (0) | 2020.10.06 |
GCC - (4) 링커(ld)에 관하여 (0) | 2020.09.13 |
GCC - (3) gcc의 각종 옵션 (0) | 2020.09.09 |
GCC - (1) C 컴파일 과정 개요 (0) | 2020.09.02 |