gcc로 컴파일하는 방법

1. gcc 동작 과정

2. gcc 실행하기

gcc를 이용해 컴파일 하는 방법은 다음과 같습니다.

$ gcc 소스파일 이름

gcc를 이용해 컴파일에 성공하면 a.out이라는 파일이 생성된 것을 확인할 수 있는데 여기서 그냥 명령어로 a.out을 입력하면 명령어를 발견할 수 없다는 오류 메시지가 나온다. 이는 a.out가 저장된 디렉토리를 path로 설정하지 않았기 때문이다. path로 설정되지 않는 디렉토리에 있는 명령어는 디렉토리 위치를 지정해주지 않는 한 실행되지 않는다. 그러므로 a.out를 실행하려면 다음과 같이 해야 한다.

$ ./a.out

즉, 프로그램 이름 앞에 디렉토리 위치 정보인 ./를 추가해야 현재 디렉토리에 있는 명령어가 실행되는데, 여기서 마침표(.)는 현재 디렉토리를 의미하고, 슬래시(/)는 디렉토리를 구분하는 문자다.

3. gcc 옵션

옵션 의미
-E 전처리를 실행하고 컴파일을 중단하게 한다.
-c 소스 파일을 컴파일만 하고 링크를 수행하지 않으며, 오브젝트 파일을 생성한다.
-o 바이너리 형식의 출력 파일 이름을 지정하는데, 지정하지 않으면 a.out라는 기본 이름이 적용된다.
-I 헤더 파일을 검색하는 디렉토리 목록을 추가한다.
-L 라이브러리 파일을 검색하는 디렉토리 목록을 추가한다.
-l 라이브러리 파일을 컴파일 시 링크한다.
-g 바이너리 파일에 표준 디버깅 정보를 포함시킨다.
-ggdb 바이너리 파일에 GNU 디버거인 gdb만이 이해할 수 있는 많은 디버깅 정보를 포함시킨다.
-O 컴파일 코드를 최적화시킨다.
-ON 최적화 N 단계를 지정한다.
-DFOO=RAR 명령라인에서 BAR의 값을 가지는 FOO라는 선행 처리기 매크로를 정의한다.
-static 정적 라이브러리에 링크한다.
-ansi 표준과 충돌하는 GNU 확장안을 취소하며, ANSI/ISO C 표준을 지원한다. 이 옵션은 ANSI 호환 코드를 보장하지 않는다.
-traditional 과서 스타일의 함수 정의 형식과 같이 전통적인 K&R(Kernighan and Ritchie) C 언어 형식을 지원한다.
-MM make 호환의 의존성 목록을 출력한다.
-V 컴파일의 각 단계에서 사용되는 명령어를 보여준다.

1) 자주 사용되는 옵션

-o 옵션

C 소스 코드를 컴파일 할 때 생성되는 출력 파일 이름을 지정하는 옵션으로 사용법은 다음과 같다.

-o 옵션
기능 : 생성되는 출력 파일 이름을 지정한다.
기본형 : gcc -o 출력파일이름 소스파일이름

$ gcc -o file file.c 또는 $ gcc file.c -o file을 입력한다. 그럼 출력파일이 file이라는 이름으로 생성이 된다. 이를 실행하려면 $ ./file 이라고 실행하면 된다. -o 옵션을 생략하고 컴파일을 하면 실행 파일 이름은 a.out가 된다. 그런데 이럴 경우 두 가지 다른 소스를 차례로 컴파일할 때 먼저 생성된 실행 파일 a.out를 나중에 생성된 a.out가 경고없이 덮어쓰므로 주의해야 한다.

-E 옵션

컴파일의 첫 단계인 전처리까지만 실행한 결과를 화면에 출력한다.

-E 옵션
기능 : 전처리까지만 실행하고 결과를 화면에 출력한다.
기본형 : gcc -E 소스파일이름

$ gcc -E file.c로 컴파일을 하면 매우 방대한 내용이 소스파일 위에 붙는 것을 확인할 수 있다. 이처럼 -E 옵션만 주면 전처리된 결과가 화면에만 출력되고 파일로 저장되지는 않는다. 그러므로 파일을 저장하려면 -o 옵션을 함께 주어야 하고, 그러면 file.i라는 전처리된 파일이 디스크에 저장된다.

$ gcc -E file.c -o file.i

-c 옵션

-c 옵션은 전처리, 컴파일, 어셈블까지 실행하여 오브젝트 파일(.o)을 생성한다.

-c 옵션
기능 : 전처리, 컴파일, 어셈블까지 실행하여 오브젝트 파일을 생성한다.
기본형 : gcc -c 소스파일이름

file.c를 -c 옵션을 사용해 컴파일 하면 file.o라는 오브젝트 파일이 생성된다. 즉, -c 옵션을 주어 ‘소스파일이름.c'를 컴파일하면 오브젝트 파일 이름은 ’소스파일이름.o'가 된다.

gcc -c file.c

그리고 이 오브젝트 파일을 이용해 실행 파일을 생성하려면 다음과 같이 gcc를 이용하면 된다.

gcc file.o

그럼 a.out이라는 기본 출력 파일이 생성된다. 여기서 file 이라는 이름의 실행파일을 생성하려면 -o 옵션만 주면 된다.

gcc file.o -o file

-c 옵션은 하나의 프로그램을 여러 파일로 분리해 작성한 다음, 함께 컴파일 하는 ‘분리 컴파일 시’ 많이 사용된다. 예를 들어 main.c와 hi.c 두 개의 소스로 구성된 프로그램을 살펴보자.

// main.c
extern void hi();

main() {
  hi();
}
// hi.c
#include <stdio.h>

void hi() {
  printf("Hi\n");
}

이 둘을 함께 컴파일 하는 방법은 다음과 같으며, 이를 분리 컴파일이라 한다.

$ gcc main.c hi.c -o test
$ ./test
Hi

만약 다음과 같이 파일별로 컴파일 하면 컴파일 오류가 발생하는데, main.c 파일에 대한 오류는 호출하는 함수 hi라는 함수가 정의되지 않았기 때문이고, hi.c 파일에대한 오류는 main 함수가 없기 때문이다.

# 오류가 난다. 오류의 종류는 생략한다.
$ gcc main.c -o test
# 오류가 난다. 오류의 종류는 생략한다.
$ gcc hi.c -o hi.c

그러나 다음과 같이 각 파일별로 오브젝트 파일을 만들고 나중에 링크하는 것은 가능하다.

$ gcc -c main.c
$ gcc -c hi.c
$ gcc main.o hi.o -o test

이런식으로 분리 컴파일을 하게 되면 만일 hi.c 파일이 수정되면 main.c와 hi.c를 모두 컴파일할 필요 없이 hi.c 파일에 대한 오브젝트 파일만을 생성하고 링크하면 원하는 실행 파일이 생성된다.

# hi.c를 수정하고 나서 다시 컴파일
$ gcc -c hi.c
$ gcc main.o hi.o -o test

-I 옵션

-I 옵션은 C 소스가 표준 디렉토리가 아닌 위치에 있는 헤더 파일을 가질 때 그 디렉토리 위치를 지정해준다.

-I 옵션
기능 : 표준 디렉토리가 아닌 위치에 있는 헤더 파일의 디렉토리를 지정한다.
기본형 : gcc 소스파일이름 -I디렉토리 이름

먼저 myheader.h 파일이 age.c 파일이 있는 디렉토리의 하위 디렉토리인 my약에 있다고 하자.

// age.c
#include <stdio.h>
#include "myheader.h"
main() {
  printf("%d\n", AGE);
}
// myheader.h
#define AGE 20

age.c 파일을 컴파일하면 다음과 같이 myheader.h 파일이 없다는 메시지의 컴파일 오류가 발생한다.

# 컴파일 오류가 발생한다.
$ gcc age.c 

myheader.h 파일이 표준 디렉토리에 없기 때문에 오류가 발생하는 것이다. 이와 같이 표준 디렉토리가 아닌 디렉토리에 있는 헤더 파일을 이용하려면 -I 옵션으로 디렉토리 위치를 지정해야 한다. 즉, myheader.h 파일이 있는 mydir 디렉토리를 다음과 같이 지정해 컴파일하면 성공한다.

$ gcc age.c -Imydir
$ ./a.out
20

2) 라이브러리 지정 옵션

라이브러리란?

자주 사용되는 유용한 함수에 대한 오브젝트 파일을 모아둔 것이 라이브러리로, 사실 라이브러리에는 함수 목록(index)도 포함된다.

라이브러리

목록
함수 1의 오브젝트 파일
함수 2의 오브젝트 파일
...

시스템에서 제공하는 라이브러리는 /usr/lib 디렉토리에 있으니 확인해보면 된다. 아주 많은 라이브러리가 있는데, 이름은 lib로 시작하고 ar 명령어에 의해 생성되므로 확장자는 .a다.

이 중에서 libc.a는 표준 라이브러리고 libm.a는 수치 연산 라이브러리다. ar 명령어를 이용하면 libc.a가 어떠한 오브젝트 파일로 이루어졌는지 알 수 있다. ar 명령어의 t 옵션은 .a 파일의 내용을 표시해주는 것이다.

$ ar t libc.a

라이브러리를 직접 만들어 보기 위해 우선 다음과 같이 plus.c와 minus.c 두 개의 파일을 mylib 디렉토리에 만들자.

// plus.c
int plus(int x, int y) {
  return x+y;
}
// minus.c
int minus(int x, int y) {
  return x-y;
}

plus.c와 minus.c 파일을 생성했으면 이들 파일에 대한 오브젝트 파일인 plus.o와 minus.o를 다음과 같이 생성한다.

$ gcc -c plus.c minus.c
$ ls
minus.c minus.o plus.c plus.o

그러면 라이브러리 파일을 생성할 준비가 끝났다. 다음과 같이 ar 명령어를 이용하면 plus.o와 minus.o에 대한 libmy.a가 생성되는데, r옵션은 .a 파일을 생성한다.

$ ar r libmy.a plus.o minus.o

그리고 라이브러리 파일에 목록을 추가해야 하는데, 다음과 같이 s 옵션을 주어 ar 명령어를 실행하면 라이브러리 파일이 생성된다.

$ ar s libmy.a

-l 옵션 -l 옵션은 표준 라이브러리가 아닌 라이브러리를 사용하고자 할 때 그 라이브러리를 지정해준다.

-l 옵션
기능 : 표준 라이브러리가 아닌 라이브러리를 지정한다.
기본형 : gcc 소스파일이름 -l라이브러리이름
// test1.c
#include <stdio.h>
#include <math.h>

main() {
  printf("%g\n", pow(2, 3)); //pow(x, y)는 x^y를 구하는 함수
}
# 오류가 난다.
$ gcc test1.c

수치 연산 라이브러리의 이름은 libm.a다. 그러므로 이 라이브러리 이름을 -l 옵션 뒤에 지정해야 하는데, lib와 .a를 제외한 m만 쓴다. 즉, 다음과 같이 설정하고 컴파일하면 성공한다.

$ gcc test1.c -lm
$ ./a.out

이제 앞서 직접 만든 libmy.a는 표준 라이브러리가 아니므로 -l 옵션을 주어 지정해야 한다.

// test2.c
#include <stdio.h>
int plus(int x, int y)
int minus(int x, int y)

main() {
  printf("%d %d\n", plus(2, 3), minus(2, 3));
}
# 컴파일 오류가 난다.
$ gcc 16_9.c -lmy

오류가 발생하는 dlb는 링커인 ld가 라이브러리를 찾을 때 /lib, /usr/lib와 같이 정해진 디렉토리만 찾기 때문이다. libmy.a 라이브러리는 현재 작업 디렉토리의 하위 디렉토리인 mylib에 있으므로 링커가 찾지 못하는데, 이는 -L 옵션을 사용해 해결할 수 있다.

-L 옵션

-L 옵션은 사용할 라이브러리의 위치를 지정해주므로 사용자가 라이브러리 파일을 직접 만들어 사용하거나 새 라이브러리를 내려 받아 사용할 때 이용된다.

-L 옵션
기능 : 사용할 라이브러리의 위치를 지정한다.
기본형 : gcc 소스파일이름 -L라이브러리위치

컴파일 오류가 발생했던 16_9.c를 다음과 같이 -L 옵션을 주어 libmy.a 라이브러리가 있는 디렉토리인 mylib를 지정하면 성공적으로 컴파일이 된다.

$ gcc 16_9.c -lmy -Lmylib
$ ./a.out

3) 디버깅 관련 옵션

-g, -ggdb 옵션
기능 : 디버깅 정보를 삽입한다.
기본형 : gcc -g 소스파일이름
gcc -ggdb 소스파일이름

-g 옵션에는 실행파일에 삽입될 디버깅 정보의 양에 따라 -g1, -g2, -g3와 같이 세 가지 단계가 있는데, 숫자 없이 -g 옵션을 주면 기본적으로 -g2의 디버깅 정보가 삽입된다.

-g 옵션의 단계

옵션 의미
-g1 역추적 스택 덤프 생성에 필요한 정보를 포함하지만 지역변수, 문장 번호를 위한 디버깅 정보는 삽입하지 않는다.
-g2 확장 기호 테이블, 문장 번호, 지역과 외부 변수에 대한 디버깅 정보를 삽입한다.
-g3 -g2 옵션의 디버깅 정보와 모든 매크로 정의를 삽입한다.

-g 옵션 없이 컴파일 할때와 -g 옵션을 주었을 때 생성된 실행 파일의 크기가 훨씬 커지는데, 그 이유는 디버깅 정보가 추가되었기 때문이다. -ggdb 옵션도 -g와 거의 유사하나 특별히 다른 점이 있다면 디버깅 작업을 도와주는 추가 정보가 필요하고, gdb 외의 다른 종류의 디버거에서는 사용이 불가능하다는 것이다. -g, -ggdb 옵션에 의한 디버킹 코드 삽입은 파일의 크기를 크게하기 때문에 최종 실행 파일을 생성할 때는 이러한 옵션을 주지 말고 컴파일하는 것이 바람직하다. 또 최적화된 코드는 디버깅을 어렵게 만들기 때문에 최적화 수행 전에 디버깅을 하는 것이 좋다.

4) 최적화 옵션

성능을 개선시키기 위해 코드를 최적화하면 불필요하거나 비효율적인 계산 과정이 효율적 게산 과정으로 대체되어 코드의 크기와 실행 시간을 줄일 수 있다. 대신, 컴파일 시간이 늘고 컴파일 과정에서 메모리 사용량이 늘어나는 단점이 있다.

-O 옵션
기능 : 코드를 최적화 시킨다.
기본형 : gcc -O 소스파일이름

-O 옵션은 -ON(숫자)을 써줌으로써 최적화 단계를 구분할 수 있는데, N 값은 gcc 버전마다 차이가 나며 값이 커질수록 더욱 최적화된 코드가 나온다. 일반적으로, -O1, -O2를 많이 사용하며, -O1, -O2, -O3에 의한 최적화 내용은 다음과 같다.

옵션 의미
-O1 -O 옵션과 같은 단계의 옵션으로 최소한의 스레드 분기 동작 횟수를 줄이고, 호출된 각 함수 반환 시 스택에 인수를 모아 두었다 동시에 꺼내게 해준다.
-O2 -O1 단계의 최적화와 함께 프로세서가 다른 명령어의 결과나 캐시 메모리 또는 메모리의 데이터를 기다리는 동안 컴파일러가 다른 명령어를 실행하도록 한다. 컴파일 시간이 더 오래 걸리지만, 수정된 코드는 더 최적화되어 실행이 빨라진다.
-O3 -O2 단계의 모든 최적화와 루프 해체, 그 밖의 프로세서 전용 특징을 포함한다.



comments powered by Disqus