Post

[컴퓨터시스템] GCC 명령어와 컴파일 시스템 알아보기(작성중)

[컴퓨터시스템] 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=c99C 표준 지정 (c89, c99, c11 등)
-I<디렉토리>헤더파일 포함 경로 추가
-L<디렉토리>라이브러리 경로 추가
-l<라이브러리명>라이브러리 링크 (-lm은 math.h)
-E전처리만 수행 (#include, #define 처리 결과만 출력)
-S어셈블리 코드로 변환한 결과 출력 (.s 파일 생성)
-v컴파일 과정 자세히 보기

위는 자주 사용되는 gcc옵션들이긴 하나 현재 우리에게 조금 더 필요한 옵션들만 선정해보자.

우리가(내가) 원하는 것은 컴파일과정에서 파일들이 어떻게 변하는 지에 관해서다.

아래는 컴파일 흐름의 순서이다.

  1. 전처리 (Preprocessing)
  2. 컴파일 (Compilation)
  3. 어셈블 (Assembly)
  4. 링킹 (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은 생략되어 있을 수도 있다.

This post is licensed under CC BY 4.0 by the author.