[OS]/Embedded

[펌] gcc 이야기(1)

하늘을닮은호수M 2005. 9. 6. 22:18
반응형

gcc 이야기(1)

글쓴이 : holelee (2002년 03월 31일 오후 10:00)

[ 임베디드강좌/이규명 ] @ KELP

=== 시작하기에 앞서
이곳 KELP에 이런 글을 올려도 되는지 의심스럽습니다. gcc를 비롯한 개발 툴은 embedded linux뿐만 아니라 일반적인 linux 시스템 및 상용 Unix 시스템에도 널리 사용되고 있습니다. 따라서 embedded linux 시스템을 주로 다루는 KELP에 이런 글을 올린다는 것은 조금은 어울리지 않습니다만, 다른 곳에 제가 쓰는 허술한 글을 올린다는 것이 허락되지 않기 때문에…(그렇다고 KELP를 폄하하는 것은 아닙니다.)
원래는 제목을 “gcc 이야기”가 아닌 “개발툴 이야기”나 다른 것으로 정하려고 했습니다. gcc 한가지만 이야기 하고 싶어도 관련된 assembler나 linker등 binutils에 있는 툴 들의 이야기도 빠질 수 없기 때문입니다. 하지만 개발툴이라고 하면 정말 다양한 것들이 있고(예를 들어 다른 언어 개발 툴을 비롯해 make, IDE환경들, yacc & lex 등…) 그것들을 제가 모두 아는 것이 아니기 때문에 C언어만을 주로 생각하는 “gcc 이야기”로 정했습니다. 실제로 gcc는 예전에는 GNU C Compiler의 약자였으나 지금은 GNU Compiler Collection의 약자로 다양한(?) 언어의 컴파일러들의 집합체입니다. 하지만 이 글은 C 언어만 주로 다루도록 하겠습니다.
이야기 하고 싶은 부분은 gcc가 하는 일이 무엇이고 어떤 중요한 옵션들이 그런 일을 하는데 영향을 미치고 잘못된 코딩 때문에 나타날 수 있는 에러나 경고(Warning)는 무엇인지에 관한 내용입니다. gcc의 사용자로서의 일반적인 이야기를 주로 하겠으며 특정 architecture에 dependent한 내용은 없습니다. 이 글은 C 언어를 이해하고 있고 gcc를 사용해 본 적이 있는 초보자를 대상으로 작성되었으므로 고급 사용자는 읽어봐야 도움이 안될 겁니다. 그리고 gcc의 역사나 누가 개발 했고 등등의 사용하고는 전혀 관련 없는 이야기는 직접 찾아보시길 바랍니다.


=== gcc 일반
gcc 사용해 보셨나요? 주로 make를 이용해 linux kernel을 비롯해 기존에 제공된 패키지를 컴파일해 보셨을 것으로 생각됩니다. gcc는 한마디로 GNU에서 개발된 ANSI C 표준을 따르는 C 언어 컴파일러라고 말할 수 있습니다. gcc는 ANSI C 표준에 따르기는 하지만 ANSI C 표준에는 없는 여러 가지 확장 기능이 있습니다. 또한 gcc는 통합개발환경(IDE)을 가지고 있지 않은 command line 컴파일러입니다. Visual C++을 사용하시는 분들은 cl.exe, 옛날 turbo-C를 주로 사용해 보셨던 분들은 tcc.exe와 비슷한 녀석이라 보시면 되겠습니다.
이제 직접 gcc를 실행해 보면서 이야기를 계속 하도록 하겠습니다. 다음과 같이 shell상에서 입력하면 결과가 나옵니다.

$ gcc –v
Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/specs
gcc version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)
당연히 “$”은 shell prompt이므로 혼동하시지 마십시오.
(*) –v 옵션
현재 사용되고 있는 gcc의 버전을 나타내는 옵션입니다. 간혹 특정 소프트웨어 패키지를 컴파일하기 위해 어느 버전 이상의 gcc를 쓰도록 권장하는 경우가 있는데 시스템에 깔려있는 gcc의 버전을 파악하려면 위와 같이 하면 되겠습니다. 위의 결과는 alzza linux 6.1(이제는 더 이상 나오지 않는 linux distribution이죠.)에 깔려있는 gcc의 버전을 나타냅니다. 당연히 여러분들의 시스템에는 다른 결과가 나올 수 있습니다. 위의 결과는 “/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/specs 파일을 읽어보니 2.91.66 버전이다.” 정도로 해석할 수 있겠습니다.

이제 직접 프로그램 하나를 컴파일 해 보도록 하겠습니다. 정말 간단한 hello.c를 해보죠.

-- start of file “hello.c”
#include

int main()
{
printf(“hello gccn”);
return 0;
}
-- end of file “hello.c”
$ gcc –o hello hello.c
로 컴파일 하면 실행파일 hello가 만들어 집니다. 어떻게 실행하고 그것이 어떤 결과를 주는지는 설명 안 해도 아시겠죠?
(*) –o 파일이름 옵션
gcc의 수행 결과 파일의 이름을 지정하는 옵션입니다. 위의 예제를 단순히
$ gcc hello.c
로 컴파일 하면 hello라고 하는 실행파일이 만들어 지는 것이 아니라 보통의 경우 a.out이라는 이름의 실행파일이 만들어 집니다. –o hello 옵션을 줌으로써 결과(여기서는 실행파일)를 hello라는 이름의 파일로 만들어 주었습니다.

위의 컴파일 과정을 외부적으로 보기에는 단순히 hello.c파일이 실행파일 hello로 바뀌는 것만 보이지만 내부적으로는 다음과 같은 단계를 거쳐 컴파일이 수행됩니다.
(1) C Preprocessing
(2) C 언어 컴파일
(3) Assemble
(4) Linking

C Preprocessing은 C 언어 배우실 때 배운 #include, #define, #ifdef 등 #으로 시작하는 여러 가지를 처리해 주는 과정이라는 것을 아실 겁니다. 그 다음 C 언어 컴파일은 C Preprocessing이 끝난 C 소스 코드를 assembly 소스코드를 변환하는 과정입니다. Assemble은 그것을 다시 object 코드(기계어)로 변환하고 printf()함수가 포함되어 있는 라이브러리와 linking을 하여 실행파일이 되는 것입니다.
위의 네 가지 과정을 모두 gcc라는 실행파일이 해 주는 것일까요? 겉으로 보기에는 그렇게 보일지 모르지만 실제로는 gcc는 소위 말해 front-end라고 하여 껍데기에 지나지 않고 각각을 해 주는 다른 실행파일을 gcc가 부르면서 수행됩니다.
C Preprocessing을 전담하고 있는 실행파일은 cpp라고 하여 /usr/bin 디렉토리와 /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66 디렉토리(당연히 gcc버전과 시스템에 따라 디렉토리 위치가 다릅니다. gcc –v로 확인해보세요.)에 존재합니다. C 언어 컴파일은 cc1이라는 실행파일이 담당하는데 /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66 디렉토리(당연히 gcc버전과 시스템에 따라 디렉토리 위치가 다릅니다. gcc –v로 확인해보세요.)에 존재합니다. Assemble과 linking은 각각 as와 ld라는 실행파일이 담당하고 /usr/bin 디렉토리에 존재하는 파일입니다. (참고 : 시스템에 따라 /usr/bin이 아니라 /bin또는 /usr/local/bin 디렉토리에 존재할 수도 있습니다.)

재미있는 것을 한 번 더 해보도록 하죠. 다음과 같이 입력을 해보죠. (당연히 “//”은 주석이니깐 입력하지 말아야겠죠?)
$ mv hello.c hello.s // hello.c파일의 이름을 hello.s로 바꿉니다.
$ gcc –o hello hello.s // 컴파일
어떤 결과가 나오나요? Assembler error라고 메시지가 뜨죠?(참고 : gcc가 혁신적인 버전업을 했을 경우 에러가 안 나고 제대로 컴파일 될 수도 있습니다.) 파일명만 바뀌었을 뿐 똑 같은 입력을 주었는데 이번에는 에러가 납니다. 이것은 gcc라는 실행파일이 주어진 입력 파일명의 확장자를 보고 이것이 C 언어 소스코드가 아니라 assembly 코드(확장자는 .S또는 .s입니다.)로 인식하고 위에서 설명한 (1)번과 (2)번을 건너 뛰고 (3)을 바로 수행했기 때문입니다. 당연히 assembler(as 실행파일)는 assembly 문법과 다르기 때문에 에러를 냅니다.

이제 gcc라는 실행파일이 하는 일을 정리해 보면 다음과 같습니다.
(1) 사용자에게 옵션과 소스 파일명들의 입력을 받는다.
(2) 소스 파일명의 확장자를 보고 어떤 단계를 처리해야 할지 결정합니다.
(3) 사용자의 옵션을 각각의 단계를 맡고 있는 실행파일의 옵션으로 변경합니다.
(4) 각각의 단계를 맡고 있는 실행파일을 호출(fork와 exec이겠죠?)하여 단계를 수행하도록 한다.


이제까지 gcc라는 이름의 실행파일이 하는 일을 알아보았고, 중요한 옵션 두 가지(-o, -v)에 대해서 살펴보았습니다. 다음에는 C Preprocessing을 맡고 있는 cpp가 하는 일과 그에 해당하는 gcc옵션, 수행 중에 일어날 수 있는 에러 등에 대해서 알아보도록 하겠습니다.

반응형