gcc 이야기(2)
글쓴이 : holelee (2002년 04월 06일 오후 10:45)
[ 임베디드강좌/이규명 ] @ KELP
=== 시작하기에 앞서
gcc라는 컴파일러를 이용하여 C 언어 프로그램을 컴파일 하기 위해서 알아야 할 기본적인 옵션 및 발생할 수 있는 에러에 대해 초보자를 대상으로 작성된 글입니다. 고급 사용자라면 읽으실 필요가 없을 것으로 생각됩니다. Architecture dependent한 부분은 가능한 배제하였습니다. 단 gcc의 사용은 Linux를 비롯한 Unix 계열의 OS에서 사용된다는 가정을 하였습니다. 또한 이 글에 대한 모든 내용은 본인이 사용하고 있는 alzza linux 6.1에서 gcc-2.91.66을 바탕으로 하고 있습니다. gcc에 대하여 좀더 많은 것을 알고 싶으신 분은 gcc manpage와 gcc manual을 참조하시기 바랍니다.
이 글에 나오는 모든 내용이 정확하다고 할 수는 없으며, 그 글에 나오는 내용을 따라 gcc를 사용하는데 있어서 문제가 발생할 경우, 본인은 책임을 지지 않습니다. 이 글에 대한 저작권은 본인(holelee)에게 있습니다. 글에 대해 잘못된 점이나 지적할 사항이 있으신 분은 저 위의 "holelee"를 클릭하여 메일을 보내 주시기 바랍니다.
=== 시작 및 복습
좀 딱딱한 말로 글이 시작되었습니다. 양해 바랍니다.
전 글에서 우리는 C 언어 소스가 gcc를 사용하여 컴파일 될 때 거치는 네 단계에 대해서 알아보았습니다. 하나의 C 언어 소스가 실행파일(executable file)로 바뀌는데 C Preprocessing, C 언어 Compile, Assemble, Linking을 거치게 됩니다. 각각의 단계를 cpp, cc1, as, ld라고 하는 gcc와는 다른 실행파일 들이 담당한다고 알게 되었습니다. 이제 그 네 단계 중에 첫번째 단계인 C Preprocessing이 하는 일이 무엇이고, 어떤 gcc의 옵션이 그 수행에 영향을 미치며, 어떤 에러나 경고(Warning)가 날 수 있는지를 알아볼 차례입니다.
=== C Preprocessing(cpp)
C preprocessing을 우리말로 하면 "C 언어 전처리"라고 할 수 있겠죠? 모든 C 언어 문법책에서 정도의 차이는 있지만 C preprocessing에 대한 내용을 다루고 있습니다. C preprocessing에 대한 문법은 C 언어 문법의 한 부분으로 가장 간단한 예제인 hello.c에도 나오니 당연하겠죠. C preprocessing에 관한 문법은 모두 '#'으로 시작됩니다. 또한 정확하게는 '#'은 그 줄(line)의 선두 문자이어야 합니다. 즉, '#' 앞에는 어떠한 문자(공백 문자 포함)도 오면 안되죠. 하지만 대부분의 compiler가 '#'앞에 공백 문자가 오는 경우에도 처리를 해주는 것으로 알고 있습니다. 아무튼, 문법에 대해서는 가지고 있는 문법책을 참조하시길 바랍니다.
그럼 C preprocessing이 하는 일을 자세히(?) 알아보도록 하죠.
== C preprocessing이 하는 일
(1) 입력 : C 언어 소스 코드
(2) 출력 : C 언어 소스 코드(C preprocessing된)
(3) 하는 일
- 파일 포함(file inclusion : 직역이 어색하네요)
- 매크로(macro) 치환
- 선택적 컴파일(conditional compile)
- 기타(#line, #error, #pragma)
너무 간단한가요? 말로 하면 cpp는 C 언어 소스코드를 입력 받아서 C preprocessing에 관련된 문법 사항을 적절히 처리하고 결과로 C 언어 소스코드를 출력하는 프로그램입니다. 입력은 작성된 C 언어 소스 코드이므로 굳이 설명을 안 해도 될 듯하고 출력으로 나온 C 언어 소스 코드에는 C preprocessing 문법에 관련된 어떠한 것도 남아있지 않습니다. 즉, #define, #include 등을 찾을 수 없다는 이야기입니다. 단, 남아 있는 정보는 있습니다. 그것은 file 이름과 줄수(line number)에 관한 정보는 여전히 남아 있습니다. 그 이유는 추후의 컴파일 과정에서 에러가 날 때 그 정보를 이용해서 error를 리포팅할 수 있도록 하기 위해서 입니다. 그럼 C preprocessing을 직접 해보도록 하죠. shell command line에 다음과 같이 입력하세요.(당연히 $은 shell prompt이므로 입력하지 말아야 하고, hello.c는 전에 입력했던 그 소스코드 파일입니다.)
$ gcc -E -o hello.i hello.c
결과로 hello.i라는 파일이 생깁니다. 그 파일 내용이 너무 길어 여기에 싣지는 못합니다만 에디터로 한번 열어보세요. hello.c의 첫번째 줄에 있는 #include
(*) -E 옵션
-E 옵션은 gcc의 컴파일 과정 중에서 C preprocessing까지만 처리하고 나머지 단계는 처리하지 말라는 것을 지시하는 것입니다. 평소에는 별로 쓸모가 있는 옵션이 아니지만 다음과 같은 경우에 유용하게(?) 사용할 수 있습니다.
(1) C 언어 소스 코드가 복잡한 선택적 컴파일을 하고 있을 때, 그 선택적 컴파일이 어떻게 일어나고 있는지 알고 싶은 경우.
(2) preprocessing의 문제가 C 언어 에러로 나타날 경우. 다음과 같은 소스코드를 고려해 보죠.
-- start of cpperr.c
#define max(x, y) ((x) > (y) ? (x) : (y) /* 마지막에 ")"가 없다!!! */
int myMax(int a, int b)
{
return max(a, b);
}
-- end of cpperr.c
$ gcc -c cpperr.c
다음과 같은 에러가 납니다.(>>는 에러메시지를 나타내는 기호이며 실제로 출력되지 않습니다.)
>> cpperr.c: In function `myMax':
>> cpperr.c:4: parse error before `;'
cpperr.c파일의 4번째 줄에서 ';'가 나오기 전에 parse error(뒤를 참조)가 났다고 하는 군요. 하지만 실제 에러는 #define에 있었으므로 그것을 확인하려면 -E 옵션으로 preprocessing을 하여 살펴 보면 쉽게(?) 알 수 있습니다.
(*) 참고 : parse error before x(어떤 문자) 에러는 그야말로 parsing을 할 때 발생한 에러를 말합니다. parsing이란 syntax analysis라는 과정인데 쉽게 말하면 C 언어 소스코드를 읽어드려 문법적 구성요소 들을 분석하는 과정이라고 할 수 있습니다. 보통 gcc에서 parse error라고 하면 괄호가 맞지 않았거나 아니면 ';'를 빼먹거나 했을 때 발생합니다. 보통의 경우 before x라고하여 x라는 것이 나오기 전에 parse error가 발생하였음을 알려주기 때문에 그 x가 나오기 전에 있는 C 소스 코드를 뚫어지게 바라보면 문제를 찾을 수 있습니다.
C preprocessing의 문법과 나머지 C 언어의 문법과는 거의 관계가 없습니다. 관계가 있는 부분이 있다면 정의된 macro가 C 언어의 문법 상의 char string literal에는 치환되지 않는다는 정도입니다. (좀더 쉽게 이야기 하면 큰 따옴표 안에서는 macro 치환이 되지 않습니다.) 또한 c preprocessing은 architecture dependent하지 않습니다. 즉, i386용으로 컴파일된 cpp를 다른 architecture에서 사용해도 무방합니다. 조금 문제가 있을 수 있는 부분이 있다면 gcc의 predefined macro(i386의 경우 i386용 자동으로 define된다.)가 다를 수 있다는 점 뿐입니다. 따라서 cpp를 C 언어 소스코드가 아닌 다른 부분에서 사용하는 경우도 있습니다. 대표적으로 assembly 소스 코드에서도 사용합니다. assembler가 사용하고 있는 macro 문법이 c preprocessing의 macro문법 보다는 배우기 쉽기 때문이죠.(정확히는 쉽다고 하기 보다는 새로 assembler macro를 배우지 않아도 되므로...)
이제 preprocessing이 하는 일에 대해서 좀더 알아 보겠습니다.
== 파일 포함(file inclusion)
#include
#include "config.h"
위와 같이 많은 C 언어 소스코드에서 헤더 파일을 포함하죠. <>와 ""의 차이는 아실테고...(혹 모르신다면 C 언어 문법책을 참조하세요.) 그런데 여기서 한가지 의문이 생깁니다. "include한 헤더 파일을 어느 디렉토리에서 찾는가?"입니다. 보통은 default로 특정 디렉토리를 찾게 됩니다. Linux 시스템의 경우 /usr/include가 default 디렉토리죠.(실제로도 그곳에 stdio.h라는 파일이 있습니다.) 그 다음은 현재 디렉토리를 찾게 됩니다.(<>와 ""에 따라서 다릅니다만...) 파일이 없으면 당연히 에러가 나겠죠. gcc의 경우 다음과 같은 에러가 납니다. (>>은 에러메시지를 나타내는 기호로 실제로는 출력되지 않습니다.)
>>소스코드파일명:line number: 헤더파일명: No such file or directory
또는(LANG=ko일때)
>>소스코드파일명:line number: 헤더파일명: 그런 파일이나 디렉토리가 없음
그렇다면 include하고 싶은 파일이 default 디렉토리와 현재 디렉토리에 없으면 어떻게 할까요? 그런 문제를 해결하기 위해서 다음과 같은 옵션이 존재합니다.
(*) -Idir 옵션
여기서 dir은 디렉토리 이름이고 -I와 디렉토리 이름을 붙여 써야 합니다. 그럼 include한 헤더 파일을 그 디렉토리에서도 찾아 주게 됩니다. 당연히 옵션을 여러 번 다른 디렉토리 이름으로 줄 수도 있어서 헤더 파일을 찾을 디렉토리를 여러 개로 지정할 수 있습니다. 꼭 알아 두어야 할 옵션입니다.
관련 옵션을 하나만 더 알아보죠.
(*) -nostdinc 옵션
이 옵션은 default 디렉토리(standard include 디렉토리)를 찾지말라고 지시하는 옵션입니다. 어플리케이션 프로그래머는 관심을 둘 필요가 없지만 kernel 프로그래머는 관심 있게 볼 수 있는 옵션이죠.
== macro 치환
macro 치환에 대해서는 특별히 일어날만한 에러는 없습니다. 가끔 문제가 되는 부분이 macro 정의가 한 줄을 넘어선 경우 역슬레쉬('')로 이어져야 하는데 그 소스 파일이 windows용 에디터로 편집 되었으면 parse error가 나는 경우가 있습니다. 그것은 개행문자(new line character)가 서로 달라서 그런 것인데...음 자세히 이야기하자면 끝이 없으므로 그냥 넘어가도록 하죠. 또한
>> 'xxxx' redefined
macro 치환에서 대한 옵션 두개를 알아보도록 하죠.
(*) -Dmacro 또는 -Dmacro=defn 옵션
gcc의 command line에서 macro를 define할 수 있도록 하는 옵션입니다. 예를 들어 -D__KERNEL__이라는 옵션을 주면 컴파일 과정 중에 있는 C 언어 소스코드의 맨 처음에 #define __KERNEL__이라고 해준 것과 같이 동작합니다. 또한 -DMAXLEN=255라고하면 C 언어 소스코드의 맨 처음에 #define MAXLEN 255 라고 한 것과 동일한 결과를 줍니다. 선택적 컴파일을 하는 경우에 많이 이용하는 옵션으로 꼭 알아야 할 옵션입니다.
(*) -Umacro 옵션
이 옵션은 #undef하고 하는 일이 똑같은데 C 언어 소스코드와는 하등의 관계가 없습니다. -Dmacro옵션처럼 C 언어 소스코드의 맨처음에 #undef macro를 해주는 것은 아무런 의미가 없기 때문이죠.(어짜피 #define은 그 이후에 나올 것이므로...) 이 옵션의 목적은 위의 -Dmacro옵션으로 define된 macro를 다시 undef하고자 할 때 쓰는 옵션입니다. 평상시에는 별로 쓸 일이 없는 옵션이지만 그냥 -Dmacro와 같이 짝으로 알아 두시길 바랍니다.
== 선택적 컴파일
#if 시리즈와 #else, #elif, #endif 등으로 선택적 컴파일을 수행할 수 있는 것은 모두 아실 것으로 생각됩니다. 위에서 설명한 -Dmacro 옵션과 같이 쓰는 경우가 많죠. 암튼 특별히 설명할 옵션은 없고 #if와 #else, #endif의 짝이 잘 맞아야 합니다. 안 그러면 당연히 에러가 발생합니다. 단순히 parse error라고 나오는 경우는 드물고, #else, #if 에 어쩌고 하는 에러가 납니다. 많이 경우의 수가 있으므로 직접 에러가 발생되도록 코딩을 해보고 확인해 보셔도 좋을 듯 합니다.
== 기타(#line, #error, #pragma)
#line, #error, #pragma라는 것이 있는지도 모르는 분들이 꽤나 있을 듯 싶습니다. 자세한 것은 당연히 C 언어 문법 책을 찾아 봐야 겠죠. #line의 경우 C 언어 소스코드 직접 쓰이는 경우는 거의 없으니까 무시하고 #pragma는 compiler에 dependent하고 gcc에서 어떤 #pragma를 사용하는지도 알 수 없으므로 그냥 넘어가도록 하겠습니다. #error의 경우 C preprocessing 과정에서 강제로 에러를 만드는 지시어입니다. 선택적 컴파일 과정에서 도저히 선택되어서는 안 되는 부분에 간혹 쓰입니다. 당연히 #error를 만나면 에러가 생깁니다. linux kernel 소스코드에서 include 디렉토리를 뒤져 보시면 사용하는 예를 만나실 수 있습니다.
== predefined macro
사용자가 C 언어 소스코드에서 #define을 하지 않아도 이미 #define된 macro가 있습니다. ANSI C에서는 __LINE__, __FILE__, __TIME__, __DATE__, __STDC__ 다섯 가지는 이미 define되어 있는 macro로 강제적인 사항입니다.(당연히 모르면 문법책 참조) gcc도 당연히 다섯 가지 macro를 predefine합니다. 뿐만 아니라 GCC의 버전에 대한 macro, architecture에 관한 사항 등을 -Dmacro 옵션 없이도 predefine합니다. 전에 말씀드린 -v 옵션을 실행하면 출력되는 specs파일을 열어보시면 감을 잡으실 수 있을 겁니다.(specs파일이 어떻게 해석되는지는 저도 잘 모르니까 묻지 마시길...)
== 꼭 알아두면 좋은 옵션 한가지
다음과 같이 shell 상에 입력해 보세요.(hello.c는 계속되는 그 녀석입니다.)
$ gcc -M hello.c
어떤 것이 출력되나요? "hello.o: hello.c /usr/include/stdio.h 어쩌구저쩌구"가 출력될 것입니다. 어디서 많이 본 듯한 형식 아닌가요?
(*) -M 옵션
-M 옵션은 cpp에게 makefile로 만들 수 있는 rule을 만들어달라고 하는 요청을 보내는 명령입니다. file을 include하는 녀석은 cpp이므로 rule은 cpp가 만들 수 있겠죠. 당연히 -Dmacro, -Umacro, -Idir 옵션 등을 같이 사용할 수 있고 그에 따라 결과가 달라질 수도 있습니다. makefile을 좀 쉽고 정확하게 만들 때 쓰는 옵션이므로 알아두면 좋습니다. 단지 안 좋은 점은 default 디렉토리에 있는 보통 사용자는 고칠 수도 없는 파일까지 무식(?)하게 만들어 준다는 것입니다.
이제 총 정리 해 보도록 하겠습니다. C preprocessing은 C 언어 소스코드를 입력으로 받아 file inclusion, macro 치환, 선택적 컴파일, 기타를 처리하고 C 언어 소스코드를 출력하는 과정입니다. 그 중에 몇 가지 에러가 날 수 있는 부분이 있고(물론 사용자의 잘못으로) 몇 가지 중요한 옵션을 알아봤습니다.
이상 장황한 C preprocessing에 관한 gcc 이야기를 끝냅니다. 넘 길었나요? 다음에는 C 언어 compile과정에 대해서 알아보도록 하겠습니다.(다음 편은 매우 짧게 할 예정입니다.)
'[OS] > Embedded' 카테고리의 다른 글
[펌] gcc 이야기(3) (0) | 2005.09.06 |
---|---|
[펌] gcc 이야기(1) (0) | 2005.09.06 |
[펌] minicom과 sftp를 이용해서 타겟보드에 리눅스부트 이미지올리는법.. (0) | 2005.07.06 |
hyper104를 사용하기 위한 Toolchain구성 (0) | 2005.06.28 |
[펌] 막강한 부트로더 GRUB (0) | 2005.06.27 |