코드를 컴퓨터가 알아들을 수 있도록 기계어로 변환해야 하는데, 이 과정을 컴파일이라고 한다.
그리고 컴파일을 수행하는 명령은 다음과 같다.
$ gcc -g -o hello(실행파일명) hello.c(소스파일명)
또한 자주 사용되는 컴파일 옵션은 다음과 같다.
- -g : gdb 디버깅 정보 포함
- -I : include 경로
- -Wall : all warning enable
- -O : optimization for code ( = -O1 )
그럼 서론은 여기까지로 하고, 이제 GCC 컴파일러의 개념과 그 확장 개념에 대해서 조금 더 자세히 알아보자.
GCC 컴파일러
GCC는 GNU 컴파일 모음(GNU Compiler Collection)의 약자이다. GNU 프로젝트의 일환으로 개발되어서 널리 쓰이고 있는 컴파일러이다.
여기서 GNU란 GNU is not UNIX의 재귀약자로 리처드 스톨먼이 각종 자유 소프트웨어들이 돌아가고 번영할 수 있는 기반 생태계를 구축하기 위해 시작한 프로젝트이다.
여기서 컴파일(Compile)은 어떤 언어의 코드를 다른 언어로 바꿔주는 과정을 말하는데, 예를 들어, 사람이 인식하고 이해할 수 있는 C언어 코드를 컴퓨터가 이해할 수 있는 기계어로 바꿔주는 것이다. 즉, 컴파일러는 어떤 프로그래밍 언어로 쓰여진 소스 파일을 다른 언어로 바꿔주는 번역기이다.
그럼 이제 소스 코드가 실행 파일이 되기위한 4가지 단계에 대해서 더 자세히 살펴보자.
- 전처리 단계
- 컴파일 단계
- 어셈블 단계
- 링크 단계
우선 전처리기 단계에서는 전처리기가 소스 파일 내의 전처리기 지시자를 처리한다. 여기서 전처리기 지시자란 #으로 시작하고 세미콜론 없이 개행문자로 종료되는 라인을 의미한다. 즉, 전처리 과정이란 컴파일러가 소스 파일을 컴파일하기 이전 과정으로 전처리기 지시자를 사용해서 소스파일(*.c)와 헤더 파일(*.h)을 사용하여 전처리한 소스 파일을 컴파일하여 오브젝트 파일(*.obj)로 만들고, 링커를 통해 라이브러리와 합쳐 실행 파일(*.exe)로 만드는 것이다. 말로만 설명하면 너무 어렵고, C언어 흔히 보는 예제를 살펴보자.
- #include : 지정된 특정 파일의 내용을 해당 지시자가 있는 위치에 삽입한다. 즉, 프로그램 외부의 파일을 불러온다.
- <> : 컴파일러가 제공하는 헤더 파일을 포함한다.
- "" : 프로그래머가 직접 만든 파일을 포함하며, 경로를 \만으로 표현한다.
- #define : 매크로 함수 및 상수 정의에 사용한다. 즉, 코드 내의 해당 상수를 프로그래머가 정의한 문자열로 대체한다.
- #undef : 정의한 매크로를 취소한다. 해제한 매크로는 새로 재정의가 가능하다.
- #if ~ (#elif ~ #else ~) #endif : 조건부 컴파일로 전처리문에서 주어진 조건의 만족 여부에 따라 코드를 선택적으로 컴파일한다.
- #if ~ #endif : 조건이 참이면 컴파일에 포함한다.
- #ifdef ~ #endif : 매크로가 정의되어 있으면 컴파일한다.
- #ifndef ~ #endif : 매크로가 정의되어 있지 않으면 컴파일한다.
#if OS == 1
printf("Linux");
#elif OS == 2
printf("Windows");
#else
printf("else");
#endif
이렇게 전처리 단계를 거치면 소스 파일 hello.c 에서 확장 소스 파일인 hello.i 가 생성된다.
# 전처리 과정 실행
$ gcc -E main.c -o main.i
그리고 컴파일 단계에는 전처리된 파일인 hello.i 로부터 어셈블리어로 된 파일인 hello.s 파일을 생성한다.
# 컴파일 과정 실행 ( *.c -> (*.i) -> *.s )
$ gcc -S main.c
그 다음 어셈블 단계에는 어셈블리어 파일을 기계어로 된 오브젝트 파일로 변환한다. 즉, 컴퓨터가 읽을 수 있는 0과 1로 이루어진 2진수 코드로 변환한다.
# 어셈블 과정 실행 ( *.c -> (*.i) -> (*.s) -> *.o )
$ gcc -C main.c
마지막 링크 단계에서는 작성한 프로그램이 사용하는 다른 프로그램이나 라이브러리를 가져와서 연결한다. 그 결과로 실행 가능한 파일을 생성한다. (예: hello.o -> hello)
# 링크 단계 ( *.c -> (*.i) -> (*.s) -> (*.o) -> executable )
$ gcc -o main main.c func.c
GCC 컴파일 옵션
- -o 파일명 *.c : 지정한 파일명으로 실행 파일을 저장한다.
- -E : 전처리 단계를 수행한 후, 컴파일 과정을 거치지 않는다.
- -S : 컴파일 단계를 수행한 후, 어셈블 과정을 거치지 않는다.
- -c 파일명 *.c : 소스 코드를 컴파일 또는 어셈블하며 링크를 하지 않는다. 이 옵션을 생략하면 main 함수를 찾을 수 없다는 오류가 출력된다.
- -I 디렉토리명 : 디렉토리명에서 헤더 파일을 검색한다.
- -l 라이브러리 : 라이브러리 파일과 링크한다.
- -L 디렉토리명 : 디렉토리 내에서 라이브러리 파일을 찾는다.
- -D 매크로상수명=값 : 매크로 상수를 정의하기 위한 옵션이다.
- gcc -D BUFFER_SIZE=42
더 자세한 내용은 다음 링크를 참고하자.
https://jangpd007.tistory.com/220
gdb를 이용한 디버깅
컴퓨터가 변환된 프로그램을 수행하는 중에 발생하는 에러를 잡는 과정을 디버깅이라고 한다. 그리고 gdb는 이러한 디버깅을 도와주는 도구이다. 따라서 gdb는 프로그램이 매 단계마다 어떤 순서로 진행되는지와 해당 단계에서의 상태를 관찰할 수 있다.
디버깅을 위한 컴파일 명령은 다음과 같다.
$ gcc -g -o hello hello.c
디버거를 실행하는 방법은 다음과 같다.
$ gdb 디버깅할 실행프로그램
gdb안에서의 명령 중 디버깅 중인 코드를 보는 명령은 다음과 같다.
(gdb) list
실행 중단점 breakpoint를 지정하는 방법은 다음과 같다.
(gdb) break 5
디버깅을 시작하는 명령은 다음과 같다. 물론 breakpoint가 있다면, breakpoint에서 실행은 중지된다.
(gdb) run
다음 라인을 실행하는 명령은 다음과 같다.
(gdb) next or n
현재 중지상태에서의 변수의 상태값을 보는 명령은 다음과 같다.
(gdb) print 변수명
$1 = 21845
(gdb) n
(gdb) print 변수명
$2 = 30
현재의 모든 중단점 리스트를 보는 명령은 다음과 같다.
(gdb) info break
tui는 gdb를 GUI로 조금 더 보기 편리하게 도와주는 명령어로, 코드리스트와 중단점을 같이 보여준다.
$ gdb hello --tui
또는 gdb 실행 후
(gdb) layout src
이 밖에 자주 사용되는 설정은 .gdbinit 파일에 설정해서 사용하면 된다.
추가로 hw breakpoint 개수를 초과시에는 다음과 같이 설정할 수 있다.
// gdb 실행후, sw breackpoint, sw watchpoint 설정
(gdb) set breakpoint auto-hw off
(gdb) set can-use-hw-watchpoints 0
그 밖에 gdb command 는 다음을 참고하자.
'Language > C' 카테고리의 다른 글
구조체 (0) | 2023.11.05 |
---|---|
키워드 _CRT_SEUCRE_NO_WARNINGS에 대하여 (0) | 2023.11.05 |
typedef, enum, union (0) | 2023.11.05 |
포인터 (0) | 2023.10.27 |
헤더 파일 (0) | 2023.09.28 |