GCC 개요, 빌드? 컴파일?
GCC가 컴파일러라는 것은 아는데 GCC가 뭔지에 대해서는 제대로 알아본적이 없는 듯하다.
GCC는 GNU Compiler Collection의 약자이다. 즉 GCC는 GNU프로젝트에서 만든 오픈 소스 컴파일러 컬렉션이다.
주로 Problem Solving을 할 때 사용하는 컴파일러가 GCC이고 Visual Studio에서는 Visual C라는 컴파일러를 사용한다. 그래서 Visual Studio에서는 되는 풀이가 문제풀이 사이트에서는 안되는 경우도 있는 것이다. 프로그래머스에서는 Clang이라는 컴파일러를 사용하고 있긴하다. 아래 그림은 백준에서 사용하는 C, C++ 컴파일러 정보이다.
컴파일러는 기본적으로 전처리된 소스 파일을 어셈블리어 파일(*.s)로 컴파일 해주는 역할을 한다. 컴파일이라는 과정의 범위가 여러 의미로 사용되고 있어 애매한 부분이 있다. 컴파일 과정에 4가지 단계(전처리 - 컴파일 - 어셈블 - 링크)를 컴파일 과정이라고 부르기도 하고 컴파일 단계와 링킹 단계를 구분하여 부르기도 한다. 위 4단계를 또한 빌드 과정이라고 한다.
두 번째 그림처럼 크게 컴파일, 링킹 두 단계로 나눴을 때 컴파일 단계에서의 컴파일러의 역할을 세부적으로 나누면 Analysis, Optimization 등의 단계로 나눌 수 있는데 이는 뒤에서 정리해보겠다.
GCC를 이용한 빌드 과정 진행해보기
위에서 언급한 4단계의 빌드 과정을 해주는게 결국 GCC이다. 그래서 gcc helloworld.c 를 입력하면 네 단계를 거치게 되고 최종적으로 실행파일이 생성되지만, 사실 각 단계에서 파일들이 임시로 생성되었다 사라진다. 한 번에 진행할 수도 있고, 각 단계를 쪼개서도 진행할 수 있다. 두 가지 경우를 모두 진행해보겠다.
먼저 helloworld.c 를 만들고 gcc helloworld.c를 하면 a.out 이 생성되었고 a.out을 실행시키면 helloworld가 출력된다.
이는 빌드 중간 과정을 모두 빠르게 지나 소스코드 파일과 실행파일(바이너리 파일)만 남는 과정이다.
a.out 파일은 실행파일인데 과거 유닉스 계통 운영체제에서 사용하던 파일이다. 지금은 대부분의 운영체제에서 ELF 형식으로 대체 되었지만 gcc의 출력 파일명 기본값으로 사용되고 있다.
두 번째로 위의 그림의 단계에 따라 전체 빌드 과정을 따라다며 진행해보겠다.
helloworld.c를 -E 옵션을 사용해 전처리만 진행해서 helloworld.i 파일을 생성한다.
1. 전처리 (pre processing)
$ gcc -E helloworld.c -o helloworld.i
*.i 파일은 700줄 가까이 되는 코드인데 main문 위로 700줄 가까이 전처리된 것을 확인할 수 있다. helloworld.c에서 stdio 헤더파일을 include 했으므로 printf()와 같은 관련 함수들도 모두 정의되었다.
2. 컴파일 (compilation)
$ gcc -S helloworld.i -o helloworld.s
helloworld.s 파일은 아래와 같이 어셈블리어 파일이다.
3. 어셈블리 (assembly)
$ gcc -c helloworld.s -o hellworld.o
helloworld.o 파일은 오브젝트 파일로 어셈블리어였던 코드가 기계어 코드가 된다.
4. 링킹 (liking)
$ gcc helloworld.o -o helloworld.exe
링킹 과정을 마지막으로 exe 파일이 생성된다.
컴파일 단계와 최적화
컴파일 단계를 세부적으로 나눌 수 있다고 했는데 이를 그림으로 나타내면 다음과 같다.
첫 번째로 helloworld.c 파일이 들어오면 Lexical Analysis를 한다. 이는 소스 코드를 토큰으로 분류하여 문법적으로 오류가 없는지 검사하 단계이다. 두 번째로 Syntax Analysis는 이전 단계로부터의 토큰 스트림으로부 파스 트리를 생성하는 단계이다. 세 번째로 Semantic Analysis 단계는 타입 검사 등을 수행하는 단계로 컴파일러 구현 목적에 따라 생략되기도 한다. 네 번째로 Intermediate code generation 단계는 이전 단계의 파스 트리로 부터 목적 코드를 생성하기 전 중간 코드를 생성하는 단계이다. 다섯 번째로 Code optimization 단계에서는 코드 최적화를 하는 단계이고 마지막 Target code generation 단계에서는 기계어 코드를 생성하는 단계이다. 즉 오브젝트 파일을 생성한다.
임베디드 개발에서 컴파일러
특히 임베디드 개발에서 컴파일러를 잘 고려해야 하는 것은 컴파일러에 따라 문법적 차이도 존재하기 때문이다. 컴파일러는 자신의 방식으로 코드를 해석, 처리하는데 이러한 예를 잘 보여주는 재밌는 코드가 있다.
#include <stdio.h>
#include <stdlib.h>
int main()
{
int num = 0;
printf("++ : %d, -- : %d\n", num++, num--);
system("pause");
}
코드를 실행하면 어떤 값이 출력될까. 결과는 ++ : -1, -- : 0이다. 이는 컴파일러가 후위연산과 전위연산이 한 줄에 나왔을 때 처리하는 순서가 다르기 때문이다. 위와 같은 예시 뿐만 아니라 컴파일러에 따라서 ++x 와 x++의 속도 차이도 존재한다. 특정 컴파일러에서는 ++x가 어셈블리로 구현되어 있어 ++x가 x++보다 더 빠르기도 한 것이다.
'Computer Science' 카테고리의 다른 글
64비트 운영체제(x64)란? C 자료형 uint32_t? (0) | 2024.06.22 |
---|---|
OS - 8. (0) | 2024.05.18 |
HTTP와 리퀘스트 메시지 (0) | 2023.07.26 |
URL, URI란? (0) | 2023.07.25 |
네트워크 - 6. 응답 데이터가 웹 서버에서 웹 브라우저로 (0) | 2023.07.25 |