gcc란?
GNU Compiler Collection(gcc)는 전처리기(cpp0), C컴파일러(cc1), 어셈블러(as), 링커(ld)를 각각 호출하는 역할을 한다.
컴파일과정
sample.c > 전처리기(cpp0) > sample.i > C컴파일러(cc1) > sample.S > 어셈블러(as) > sample.o > 링커(ld) > a.out
소스코드를 컴파일 해 바이너리를 만들어내는 과정은 자동차를 제작하는 원리와 비슷하다. 자동차는 수많은 부품들이 있고 완성 후에도 여러가지 옵션으로 부품을 추가하거나 업그레이드 할 수 있는데 이것으로 인해 성능 또한 달라질 수 있다.
gcc도 정말 많은 옵션들을 제공하고 있다. 컴파일 시에 이러한 옵션들로 인한 성능 향상으로 속도적 효율이 증대되거나 메모리 또는 저장공간의 공간적 효율을 가져올 수 있다.
gcc 옵션
-E
-E 옵션은 전처리 과정의 결과를 화면에 보이는 옵션이다.
하지만 –save-temps 옵션을 사용해 [파일명].i 파일을 읽는 것이 더 좋은 방법이다.
-S
cc1으로 전처리된 파일을 어셈블리 파일까지만 컴파일하고 멈춘다.
최종결과물: 어셈블리 파일(.S)
-c
-c 옵션은 as에 의한 어셈블까지만 수행하고 링크는 수행하지 않는다.
최종결과물: 오브젝트 파일(.o)
-v
이전에 많이 사용하던 옵션으로 gcc가 컴파일을 어떤 식으로 수행하는지를 화면에 출력해서 보여준다.
--save-temps
컴파일 과정에서 생성되는 중간파일(전처리 파일과 어셈블리 파일)을 지우지 않고 현재 디렉토리에 저장한다.
컴파일 과정에서 발생되는 오류를 분석할 때 이용한다.
-o
컴파일된 파일명을 지정한다.
전처리기(cpp0) 옵션
-I
전처리 과정에서 헤더 파일을 탐색하는 기본 디렉토리를 추가할 때 사용하는 옵션이다.
“그런 파일이나 디렉토리가 없음” 이라는 에러가 뜨면 헤더를 찾아 –I[디렉토리명] 옵션으로 추가한다. 그러면 추가한 디렉토리에서 우선적으로 찾게 된다.
기본적으로는 다음의 디렉토리에 위치한 헤더 파일만을 참고한다.
- /usr/local/include
- /usr/lib/gcc-lib/리눅스머신/리눅스버전/include
- /usr/include
-include [헤더파일경로]
헤더 파일을 소스 내에 추가할 때 사용한다. 소스에서 #include “헤더파일경로” 한 것과 동일하다.
-D[매크로]
매크로를 외부에서 define 할 때 사용한다.
만약 –DDEBUG 옵션으로 컴파일을 했다면 소스의 제일 앞에 #define DEBUG 옵션을 추가한 것과 같다.
주로 디버깅 메시지 활성&비활성화 시에 사용한다.
-D[매크로]=[매크로 값]
코드 내에서 #define [매크로] [매크로값] 을 추가한 것과 같다.
-U
–D와 반대로 소스 파일(.c) 내에 #undef [매크로] 옵션을 추가한 것과 같다.
–M 과 -MM
Makefile을 작성할 때 유용한 종속 항목 리스트를 출력한다. 종속항목이란 타겟이 컴파일 되기 위해 필요한 항목을 의미한다.
-M: make를 위한 소스파일의 모든 종속 항목을 출력한다.
-MM: make를 위한 소스파일에서 기본 include 디렉토리에 있는 헤더 파일은 빼고 종속 항목을 출력한다.
-nostdinc
디폴트 include 디렉토리(/usr/include)에서 헤더파일을 찾지 않고 –I 옵션으로 추가한 디렉토리에서만 헤더를 찾는다.
리눅스 커널과 같이 표준 C함수를 사용하지 않는 프로그램을 컴파일할 때 잘못된 include로 인한 오류가 발생하는 것을 방지한다.
-C
-E 옵션과 함께 사용하며 전처리 과정에서 주석을 제거하지 않는다.
-Wp, [옵션]
만약 cpp0와 gcc의 옵션이 중복되면 gcc의 옵션으로 해석된다. gcc가 모든 컴파일 도구들을 컨트롤하기 때문이다. gcc의 해석을 거치지 않고 바로 cpp0의 옵션으로 전달하고 싶을 때 –Wp 옵션을 사용한다.
-Wp, -DDEBUG, -I/usr/include, -M 이렇게 사용하면 cpp0로 -DDEBUG, -I/usr/include, -M 옵션이 바로 전달된다.
C컴파일러(cc1) 옵션
gcc의 수많은 옵션 중 거의 대부분은 cc1의 옵션이다. cc1의 옵션은 크게 4가지 종류로 나뉘는데
C언어 옵션, 경고메시지 옵션, 최적화 옵션, 디버깅 옵션이다.
C언어 옵션
주로 c언어 종류와 표준에 관한 옵션으로 K&R C, Traditional C, ANSI C 그리고 GNU C 같은 각 종류와 표준에 부합하는 C 소스를 작성하고자 할 때 사용한다. 기본적으로는 GNU C89 문법을 가지고 체크한다.
-ansi
ANSI C 표준에 부합하는 소스작성시 사용하는 옵션으로 GNU C의 확장 문법은 사용이 불가하다.
이식성이 높다는게 장점이고 이 옵션을 사용할 때는 –pedantic 경고 옵션도 사용하면 좋다. 이는 ANSI C89 문법을 가지고 문법체크를 한다.
-std=[C 표준들]
기타 다른 표준을 지정할 때 사용한다. c89, c99, gnc89, gnu99 가 있다.
-traditional
오래된 Traditional C(K&R C)문법으로 문법을 검사한다.
-fno –asm
gnu89 문법에서 지원하는 asm, inline, typeof 키워드를 사용하지 않는다. asm은 C 소스내의 어셈블리 코드를 삽입할 때, inline은 인라인 함수를 지정할 때, typeof 키워드는 변수의 타입을 알아볼 때 사용하는데 이러한 키워드는 ANSI C89 표준에서는 지원하지 않기 때문에 ANSI C89에서 지원하는 __asm__, __inline__, __typeof__ 키워드를 사용해야한다.
경고메시지 옵션
cc1의 옵션을 조정하여 경고수위를 조정할 수 있다. 모든 경고를 활성화 하는 –Wall –W 옵션을 사용하면 지나칠 정도로 사소한 것까지 경고하기 때문에 이 옵션을 활성화 시켰을 때 어떻게 수위 조절을 할 것인지가 중요하다.
-Wall 와 –W
-Wall: 모든 모호한 문법에 대한 경고 메시지를 출력한다.
–W: 합법적이지만 모호한 코딩에 대해서 부가적인 정보를 제공한다.
소스의 엄격성을 제공하기 위해서는 두 옵션은 반드시 사용해야 한다.
-w
모든 경고 메시지 제거
-Werror
모든 경고를 컴파일을 중단하는 오류로 취급하여 경고가 하나만 나와도 컴파일은 중단된다.
-pedantic
ANSI C89 표준에서 요구하는 모든 경고 메시지를 표시
-pedantic –errors
ANSI C89 표준에서 요구하는 모든 오류 메시지를 표시
-Wtraditional
소스가 ANSI C와 K&R C 간에 서로 다른 결과를 가져올 수 있는 부분이 있다면 경고
최적화 옵션
최적화는 실행파일의 크기를 줄여 메모리와 하드디스크의 공간을 절약하는 것과 실행 속도를 향상시키는 것을 의미한다. 물론 프로그램의 원래 기능은 유지되어야 할 것이다. 최적화가 중요한 이유는 컴파일 한 프로그램이 언제까지나 계속 사용될 수 있다는 것이다.
공간 최적화는 임베디드 시스템에서 더욱 의미가 있다. 1바이트라도 절약해야 하는 작은 시스템에서의 공간 최적화는 그 의미가 더 크다.
최적화 기법
- 불필요한 함수 호출 제거
- 상수의 전달과 변수 복제 전달의 최적화
- 불필요한 루프 제거
- 상수의 곱셈 최적화
- 함수 호출 인수의 지연된 pop 동작 방지(스택 최소화)
- 재귀 호출이 힘수의 최종부에서 일어나지 않도록 최적화
- 단순 시간 지연 구간에 기능 삽입
- 자동 레지스터 할당
- 유추에 의한 변수 최적화
- 인라인 함수의 최적화와 스택 프레임 포인터의 제거
- 각 명령의 실행 사이클을 고려한 스케쥴링
- 반복되는 코드의 제거
- 루프에서의 동작 불변
- 인스트럭션에 대해 속성 부여 기능
- 각 하드웨어에서 지원하는 단축명령 기능에 의한 지역 최적화
gcc는 최적화에 대해서도 굉장한 유연성을 제공한다. 최적화 기법을 일일이 선택할 수도 있고 –O[최적화 레벨] 옵션을 통해 미리 여러 최적화 기법의 묶음으로 묶여있는 최적화 방법을 사용할 수도 있다.
-O0
최적화를 수행하지 않는다. 컴파일 시 최적화 옵션을 붙이지 않으면 이 옵션과 같다. 또한 인라인 함수가 사용돼도 확장되지 않는다. 리눅스 커널같이 인라인 함수가 많은 소스를 컴파일 할 경우엔 최적화를 하지 않을 시 문제가 발생한다.
-O1
최적화를 하지 않을 때보다 속도향상과 사이즈감소가 있다. 인라인 함수도 확장된다.
-O2
가장 많이 사용하는 최적화 옵션이고 일반 응용 프로그램이나 커널 컴파일시 사용된다. 대부분의 최적화를 수행한다.
-O3
모든 함수를 인라인 함수와 같이 취급한다. (함수를 호출할 때 call 인스트럭션을 사용하지 않고 호출) 가장 높은 레벨의 최적화라고 해서 가장 빠른 것도 아닐뿐더러 너무 많은 소스의 변경이 생기기 때문에 왜곡이 발생할 수 있다. (사용하지 않는 것이 좋다.)
-Os
사이즈 최적화를 수행한다. 임베디드 시스템 같은 곳에서 사용한다.
디버깅 옵션
-g
gdb에게 제공하는 정보를 바이너리에 삽입한다. 디버깅 심벌이라 하여 C 소스를 바이너리 안에 삽입한다. 실제 소스내용은 아니고 어떤 인스트럭션이 어떤 소스파일의 몇 번째 행이라는 정보만 삽입된다.
–g 옵션도 –O 옵션과 같이 레벨이 있는데 –g0은 디버깅 정보를 삽입하지 않고 –g3은 디버깅 정보를 가장 많이 제공한다. –g 는 –g2와 동일하다. 하지만 –g 옵션으로 컴파일 한 바이너리는 용량이 매우 크기 때문에 개발 초기에만 사용하고 개발이 끝나면 제거한다.
-pg
프로파일을 위한 코드 삽입. 이 옵션을 사용한 프로그램이 종료되면 프로파일 정보가 gmon.out 파일에 저장된다. 이후 gprof에 의해 gmon.out 파일의 내용을 분석하여 어떤 함수가 얼마나 호출되었고 시간은 얼마나 걸렸는지 확인할 수 있다. 그래서 병목 현상이 일어나는 부분과 어떤 부분을 최적화 해야 하는지 알 수 있다.
collect2 또는 ld 옵션
링크 옵션은 매우 중요하다. 이유는 자신이 작성한 프로그램은 링크 오류가 잘 안나지만 다른 사람이 작성한 프로그램을 컴파일하려고 하면 링크 오류가 자주 나기 때문이다. 원하는 바이너리를 만들기 위해서는 링크의 개념과 사용되는 옵션을 명확하게 알아야 시행착오를 격지 않을 것이다.
-L[라이브러리 디렉토리]
라이브러리를 찾을 디렉토리를 지정한다. 라이브러리 위치가 다를 때 사용한다.
기본적으로는 다음의 디렉토리에 위치한 라이브러리 파일만을 참고한다.
- /usr/lib
- /usr/lib/gcc-lib/리눅스종류/리눅스버전
-l [라이브러리 이름]
같이 링크할 라이브러리를 지정한다.
–l 옵션은 꼭 소스파일의 뒤에 와야 한다.
-shared
공유 라이브러리와 정적 라이브러리가 같이 있을 경우 공유 라이브러리를 우선하여 링크한다.
기본적으로 공유 라이브러리를 우선시한다.
-static
공유 라이브러리와 정적 라이브러리가 같이 있을 경우 정적 라이브러리를 우선하여 링크한다.
정적라이브러리는 크기가 크지만 속도가 빠르고 어디서나 실행 가능하다. 반면 공유 라이브러리는 사이즈가 작지만 속도가 느리고 다른 컴퓨터에서 실행할 때 컴파일 당시의 공유 라이브러리와 메이저 버전이 다를 경우 실행되지 않는다.
-nostdlib
링크시에 표준 C 라이브러리를 사용하지 않는다.
운영체제나 부트로더, 디바이스 드라이브와 같이 표준 C 라이브러리를 사용하지 않는 프로그램을 컴파일 할 때 사용한다.
-nostartfiles
crt1.o 등과 같이 start up 파일을 링크하지 않는다.
역시 운영체제, 부트로더와 같은 프로그램을 컴파일 할 때 사용한다.
-Wl, [링크옵션들]
gcc를 거치지 않고 바로 링커에게 옵션을 정해주고자 할 때 사용한다. 사용법은 –Wa와 동일하다. 옵션들은 다음과 같다.
- -s: 실행 파일에서 심볼 테이블 제거
- -x: 출력 파일에 로컬 심볼 제거
- -n: 텍스트 영역을 읽기 전용으로 만듦
- -r: 추후 링크가 가능하게 오브젝트를 만듦
- -e [name]: 시작 심볼을 name 심볼로 사용(기본적으로 _start 심볼이 시작 심볼)
- -M: 심볼들의 정보를 자세하게 출력
- -oformat [format]: 주어진 형식의 오브젝트 파일을 생성