[컴퓨터시스템] GCC 명령어와 컴파일 시스템 알아보기(작성중)
vsCode를 사용하면 “F5”버튼으로 간단하게 c를 컴파일 할 수 있다.
자동으로 gcc컴파일과정이 수행된다.
직접 gcc명령어를 사용하던 사람들도 있고 아닌 사람들도 있겠지만 컴파일 과정을 자세히 들여다보고 싶으면 필연적으로 gcc 명령어를 사용해야한다.
놀랍게도 이 과정은 생각보다 재미있다.
gcc는 과거에도 gcc였고 현재에도 gcc지만 과거의 gcc는 GNU C Compiler를 의미했고 1999년부터 현재까지는 GNU Compiler Collection을 의미한다. 오직 C언어를 위한 전용 컴파일러에서 여러 언어를 지원하는 컴파일러 프레임워크로 발전했기 때문이다.
여기서 GNU는 무엇일까? GNU는 일단 “GNU’s Not Unix!”의 약자이다. 여기서 나오는 GNU역시 “GNU’s Not Unix!”의 약자다. 그렇다. 아주 재밌는 재귀 약자이다. 간단하게 말하면 GNU는 자유롭게 사용할 수 있는 유닉스 스타일 운영체제를 만들기 위한 프로젝트이자 그 시스템의 이름이다.
서론이 길었다. 시작해보자.
GCC 명령어
1
gcc [옵션] [소스파일] -o [출력파일]
이런 형태로 사용하면 된다.
1
gcc main.c
a.out이라는 실행파일 생성
1
gcc main.c -o [실행파일]
직접 지정한 이름의 실행파일 생성
위의 명령어들을 사용하면 한 번에 모든 컴파일 과정을 진행 할 수 있다.
그치만 우리가 원하는 것은 이런 것이 아니다.
그럼 아래의 옵션들을 참고해서 gcc를 우리가 원하는 용도로 사용해보자.
자주 사용되는 gcc 옵션
옵션 | 설명 |
---|---|
-o <파일명> | 출력 파일 이름 지정 (기본은 a.out ) |
-c | 컴파일만 하고 링크는 하지 않음 → .o 오브젝트 파일 생성 |
-Wall | 모든 경고 메시지 출력 |
-Wextra | 추가적인 경고 메시지 출력 |
-g | 디버깅 정보를 포함 (gdb 용) |
-O0 , -O1 , -O2 , -O3 | 최적화 수준 (0은 비활성, 3은 최고) |
-std=c99 등 | C 표준 지정 (c89 , c99 , c11 등) |
-I<디렉토리> | 헤더파일 포함 경로 추가 |
-L<디렉토리> | 라이브러리 경로 추가 |
-l<라이브러리명> | 라이브러리 링크 (-lm 은 math.h) |
-E | 전처리만 수행 (#include , #define 처리 결과만 출력) |
-S | 어셈블리 코드로 변환한 결과 출력 (.s 파일 생성) |
-v | 컴파일 과정 자세히 보기 |
위는 자주 사용되는 gcc옵션들이긴 하나 현재 우리에게 조금 더 필요한 옵션들만 선정해보자.
우리가(내가) 원하는 것은 컴파일과정에서 파일들이 어떻게 변하는 지에 관해서다.
아래는 컴파일 흐름의 순서이다.
- 전처리 (Preprocessing)
- 컴파일 (Compilation)
- 어셈블 (Assembly)
- 링킹 (Linking)
위 과정을 뜯어보기 위해서 필요한 옵션들을 지금부터 소개하겠다.
-E
-S
-c
-o
놀랍게도 위의 4개만 있다면 우리는 컴파일 과정을 하나 뜯어 볼 수 있다. (최적화나 디버깅 옵션은 제외했다.)
전처리 (Preprocessing)
1
gcc -E main.c -o main.i
컴파일 (Compilation)
1
gcc -S main.i -o main.s
어셈블 (Assembly)
1
gcc -c main.s -o main.o
링킹 (Linking)
1
gcc main.o -o main
명령어는 위와 같다.
아래와 같은 방법으로 전처리 단계를 건너띄는 것도 가능하다.
1
gcc -S main.c -o main.s
GCC로 컴파일 과정 뜯어보기
이제 그럼 간단한 c파일을 만들어서 각 과정에서 어떤 일이 벌어지는 지 확인해보자.
1
2
3
4
5
6
7
8
// hello.c
#include <stdio.h>
int main(){
printf("Hello, gcc!");
return 0;
}
전처리
hello.c를 만든 디렉토리에서 아래 명령어를 실행해보자.
1
2
gcc -E hello.c -o hello.i
hello.i라는 hello.c가 전처리된 파일이 만들어진다.
1
2
cat hello.i
1
2
3
4
5
6
7
8
9
10
#
#
# 엄청나게 긴 sdtio.h의 텍스트 파일이 전부 들어옴
#
#
int main(){
printf("Hello, gcc!");
return 0;
}
우리가 작성한 c코드 위에 stdio.h파일의 모든 내용을 복사해와서 포함시킨다.
내가 오해를 하고 있었던 점이 이것이다. #include라는 지시문이 링커단계에서 동작 할 줄 알았지만 사실은 #include라는 지시문은 전처리 단계에서 동작하며 텍스트를 그저 복사해서 붙여넣는 수준으로 동작 한다는 것이 점이다.
나중에 다루겠지만 링킹이 되려면 각각의 파일이 모두 목적파일 단계에 있어야 한다.
정리하자면 #include는 링킹하라는 지시문이 아닌 코드를 복사해서 합치라는 지시문이고
링킹
또한 소스코드 내에서는 링킹과 관련된 어떠한 지시가 없다. 다만 링킹이 필요하다는 신호만 있을 뿐이다.
1
extern int printf(const char *fmt, ...);
sdtio.h에는 이런 식으로 선언 되어 있다. extern
은 “이 변수(또는 함수)의 정의는 다른 곳에 있다”라고 알려주는 키워드 일 뿐이다. 하지만 함수 선언에는 기본적으로 다음 의미가 포함되어 있어 extern
은 생략되어 있을 수도 있다.