리눅스에서 동작하는 프로그램을 작성하기 위해서는 반드시 크로스 컴파일 과정을 거쳐야 합니다.
아주 간단한 hello.c 라는 프로그램이 다음과 같은 소스로 작성되었다고 한다면
#include <stdio.h>
int main( int argc, char **argv ) { printf( "hello\n" ); }
이것을 컴파일 하기 위해서 다음과 같이 처리하면됩니다.
# armv5l-linux-gcc -o hello hello.c
프로그램 소스에 에러가 없어서 정상적으로 이 명령이 수행되었다면 수행 결과로서 hello 라는 프로그램 실행파일이 생성됩니다.
물론 이 실행파일은 PC 시스템에서는 동작하지 않습니다. arm 용 명령 코드를 가지는 실행파일이기 때문입니다. 그래서 이것을 실행해 보려면 ESP-NS 보드에 옮긴후에 수행해야 합니다.
이렇게 프로그램 소스가 하나일 경우에는 간단히 위와 같은 명령으로 처리해도 되지만 저 같은 게으른 사람에게는 위 명령을 컴파일을 할때 마다 매번 타이핑 하려면 죽음입니다. 물론 명령행에서 위 화살표키를 이용하는 방법도 있지만 그것도 여러가지 명령을 수행했다면 컴파일 명령만 따로 찾는것도 귀찮습니다.
더구나 여러가지 소스로 이루어진 프로그램을 컴파일한다면 그냥 때려치고 싶은 마음이 생깁니다.
이렇게 게으른 사람들에게는 Makefile을 만들어서 make 명령으로 하는 것이 아무래도 스트레스를 덜 받아서 수명연장에 도움이 됩니다.
Makefile을 만들때 딱 한번만 스트레스를 받으면 되기 때문입니다.
그.러.나.
Makefile을 어떻게 만들어야 하는가를 알아 보려고 인터넷을 뒤지거나 책을 사보면 그 방대한 양에 포기하고 싶은 마음이 굴뚝 같아 집니다. 도리어 그냥 타이핑 치는것이 마음이 편할수 있읍니다.
그.래.서
저는 아예 고정적으로 Makefile을 하나 만들어서 매번 수정해서 사용합니다. 그래서 여러분에게도 제가 사용하는 방법을 권장합니다. 여기서 소개하는 Makefile이 마음에 안 드신다면 직접 만들어서 사용해도 무방합니다. ㅜㅜ
제가 주로 사용하는 Makefile은 두가지 입니다. 하나는 자주 사용하는 루틴을 모아서 라이브러리로 만들기 위한 것과 응용 프로그램을 작성하기 위해서 사용하는 Makefile입니다.
ifeq (.depend,$(wildcard .depend)) include .depend endif ==[Makefile]=====================================================================
이 Makfile을 사용할때 주로 수정하는 부분은 다음과 같습니다.
TARGET = libsample.a OBJS = func1.o
INCLUDEDIR =
TARGET은 최종적으로 만들려고 하는 라이브러리 파일명입니다
라이브러리 파일명은 일정한 규칙이 있읍니다. 처음에 시작하는 것은 lib로 시작해야 하고 끝날때는 .a 로 끝나야 합니다.
예를 sample 이라는 이름을 가진 라이브러리 파일을 만들려고 했다면 libsample.a 라는 이름을 지정해야합니다. 컴파일 결과로 만들어지는 파일명은 당연히 libsample.a 입니다 .
이렇게 지정하는 것은 나중에 응용 프로그램을 만들때 지정하는 명칭 때문입니다.
OBJS은 TARGET에 지정한 라이브러리에 포함되는 오브젝트 명을 나열합니다. 여러 파일로 구성될때는 빈칸으로 오브젝트 파일을 구분합니다.
위 예에서는 하나의 파일에 대한 라이브러리를 만들었지만 func1.c 와 func2.c 로 구성되는 라이브러리를 만든다면 다음과 같이 지정해야 합니다.
OBJS = func1.o func2.o
너무 많은 소스 파일로 구성된다면 아무래도 여러줄로 나누어서 쓰는 것이 좋지요 이럴때는 다음 처럼 하면됩니다.
OBJS = func1.o \ func2.o
이렇게 할때 중요한것은 '\' 뒤에 바로 리턴으로 나누어져야 한다는 겁니다 만약에 뒤에 빈 공백 문자나 다른 문자가 있으면 문제가 됩니다.
INCLUDEDIR은 소스가 참조하는 헤더파일 디렉토리를 추가 할때 사용합니다. 예를 들어 /test/include 라는 디렉토리를 추가 하고 싶으면
INCLUDEDIR = -I/test/include
라고 하면 됩니다. ( -I 의 영문자는 아이[I] 입니다. 엘[L]소문자와 무척 헷갈리므로 조심하십시오 )
기본적으로 이 Makefile은 다음과 같은 디렉토리의 헤더파일들이 참조됩니다.
1. Makefile 이 포함된 디렉토리에 있는 헤더파일들 2. Makefile 이 포함된 상위 디렉토리 밑에 있는 include 디렉토리 3. 크로스 컴파일러에서 제공하는 include 디렉토리 ESP-NS는 /usr/armv5l-linux/include 가 됩니다.
이 Makefile을 이용하여 컴파일한다면 다음과 같은 과정을 거치면 됩니다.
# make clean # make dep # make
make clean은 이전에 생성된 오브젝트 파일을 지우기 위해서 사용합니다. make dep는 컴파일 대상이 되는 헤더파일들의 의존성 체크를 위한 파일 목록을 만들기 위해서 사용합니다. 이 명령의 수행결과로 .depend가 생깁니다.
make dep는 소스상에 새로운 헤더파일을 추가 했을때 수행해 주면됩니다. 물론 자신이 만들어서 수정될 수 있는 헤더파일을 추가했을 경우만 해주면됩니다.
보통 프로그램을 작성하다보면 헤더파일을 수정했는데 컴파일 타켓 대상이 되지 않아서 자신이 의도한 결과와 다른 결과를 만들수 있읍니다. 그래서 헤더파일이 변경되었을때 해당 헤더파일을 참조하는 모든 소스가 수정되도록 하기 위해서 이 명령을 사용합니다.
make clean은 보통 잘 사용하지 않아도 되지만 전체 소스를 재 컴파일 하고 싶을때 사용하면 됩니다. 한번 컴파일 되어서 수정되지 않는 소스의 오브젝트 파일은 컴파일 대상에서 제외 되기 때문에 가끔 의도하지 않는 일이 생길때가 있읍니다. 그럴때 확실하게 전체를 컴파일 하고 싶을때 사용하면 됩니다.
보통은 make 명령 하나로 끝낼수 있읍니다.
저는 생성된 라이브러리 파일과 라이브러리 파일을 사용하기 위한 헤더파일을 자동으로 목적지로 복사하는 기능도 Makefile에 포함시켜서 사용하는데 여기서는 제거했읍니다. 아무래도 자동보다는 수동이 확실합니다. ^^
위에서 제시한 Makefile 은 func1.c 소스를 컴파일 해서 libsample.a 라는 파일을 만들게 됩니다.
ifeq (.depend,$(wildcard .depend)) include .depend endif ==[Makefile]=====================================================================
이 Makefile 역시 라이브러리를 만드는 것과 무척 유사합니다.
이 Makfile을 사용할때 주로 수정하는 부분은 다음과 같습니다.
TARGET = test OBJS = main.o LIBS = -lsample
INCLUDEDIR = LIBDIR =
TARGET은 최종적으로 만들려고 하는 실행파일명입니다. 여기서는 test 라는 실행파일명을 만듭니다. 윈도우 프로그램을 하시던 분들의 입장에서 보면 실행파일명에 확장자가 없는것이 이상하겠지만 리눅스에서는 확장자가 없는 파일이면 거의가 실행파일명이거나 실행가능한 스크립트일 가능성이 높습니다. 도리어 데이터 파일들에 확장자를 붙이는 것이 관례입니다.
OBJS은 TARGET에 지정한 응용프로그램을 구성하는 소스가 컴파일되어야 하는 오브젝트 명을 나열합니다. 이것 역시 라이브러리의 Makefile과 같은 방법으로 여러 파일로 지정합니다.
LIBS은 포함될 라이브러리를 파일을 지정할때 사용합니다. 위 예에서는 libsample.a 라는 라이브러리를 지정합것입니다. 라이브러리 파일은 지정한 것에 컴파일러가 앞에 'lib' 와 뒤에 '.a'를 자동으로 지정합니다. 이점을 주의해야 합니다. 이 예는 앞에서 만든 라이브러리 파일을 이용하기 때문에 -lsample만 지정하면 됩니다. ( -l 의 영문자는 엘[L]의 소문자로 옵션입니다 )
INCLUDEDIR은 소스가 참조하는 헤더파일 디렉토리를 추가 할때 사용합니다. 예를 들어 /test/include 라는 디렉토리를 추가 하고 싶으면
INCLUDEDIR = -I/test/include
라고 하면 됩니다. ( -I 의 영문자는 아이[I] 입니다. 엘[L]소문자와 무척 헷갈리므로 조심하십시오 )
LIBDIR은 라이브러리가 포함된 디렉토리를 추가 할때 사용합니다.
예를 들어 /test/lib 라는 디렉토리를 추가 하고 싶으면
LIBDIR = -L/test/lib
라고 하면 됩니다.
기본적으로 이 Makefile은 다음과 같은 라이브러리 디렉토리를 참조합니다.
1. Makefile 이 포함된 디렉토리에 있는 라이브러리 2. Makefile 이 포함된 상위 디렉토리 밑에 있는 lib 디렉토리 3. 크로스 컴파일러에서 제공하는 lib 디렉토리 ESP-NS는 /usr/armv5l-linux/lib가 됩니다.
이 Makefile을 이용하여 컴파일한다면 다음과 같은 과정을 거치면 됩니다.
# make clean # make dep # make
이것 역시 라이브러리 Makefile과 같습니다. 글이 많은 것 처럼 보이기 위해서 다시 설명합니다. ^^
make clean은 이전에 생성된 오브젝트 파일을 지우기 위해서 사용합니다. make dep는 컴파일 대상이 되는 헤더파일들의 의존성 체크를 위한 파일 목록을 만들기 위해서 사용합니다. 이 명령의 수행결과로 .depend가 생깁니다.
make dep는 소스상에 새로운 헤더파일을 추가 했을때 수행해 주면됩니다. 물론 자신이 만들어서 수정될 수 있는 헤더파일을 추가했을 경우만 해주면됩니다.
보통 프로그램을 작성하다보면 헤더파일을 수정했는데 컴파일 타켓 대상이 되지 않아서 자신이 의도한 결과와 다른 결과를 만들수 있읍니다. 그래서 헤더파일이 변경되었을때 해당 헤더파일을 참조하는 모든 소스가 수정되도록 하기 위해서 이 명령을 사용합니다.
make clean은 보통 잘 사용하지 않아도 되지만 전체 소스를 재 컴파일 하고 싶을때 사용하면 됩니다. 한번 컴파일 되어서 수정되지 않는 소스의 오브젝트 파일은 컴파일 대상에서 제외 되기 때문에 가끔 의도하지 않는 일이 생길때가 있읍니다. 그럴때 확실하게 전체를 컴파일 하고 싶을때 사용하면 됩니다.
보통은 make 명령 하나로 끝낼수 있읍니다.
저는 생성된 라이브러리 파일과 라이브러리 파일을 사용하기 위한 헤더파일을 자동으로 목적지로 복사하는 기능도 Makefile에 포함시켜서 사용하는데 여기서는 제거했읍니다. 아무래도 자동보다는 수동이 확실합니다. ^^
위에서 제시한 Makefile 은 main.c 소스를 컴파일 하고 libsample.a 라는 라이브러리를 포함혀여 test 라는 실행파일을 만들게 됩니다.
E:\MinGW\bin\gcc -pass-exit-codes -o %P%N.exe %F 라고 써넣는다. ^^^^^^^^^^^^^^^ 여기는 mingw가 설치된 폴더이다.
5. Working Directory 에는 %P 라고 써넣는다. 6. Menu Item Name 에는 아무이름이나 써넣는다. 나는 GCC compiler 라고했다. 7. save active file 에 체크하고 8. Output to List Box 에 라디오박스를 찍는다. 9. 그리고 Capture Output 체크 10. Advenced 버튼을 누른뒤
위와같은 창이 뜨면
11. No Replace 에 찍고 OK를 누른다. 11-1. 참고로 위에 Toolbar bitmap/icon(file path) 에 아이콘파일의 경로를 써주면 그 아이콘이 나온다.
12. OK를 누르고 빠져나오면 이런 화면이 되어있을것이다.
13. 여기서 insert 를 누르면 아래에 흰 박스에 Complier by GCC 라는 항목이 생긴다. 14. Ok를 누른다.
String to character array 1. cp = s.c_str(); or s.data(); Pointer cp points to a character array with the same characters as s. 주의!! 리턴 타입이 const char*임, const char*는 char*로 변환이 거의 사용 불가능. ㅡ.ㅡ 2. 그래서 원하는 타입이 char*일때의 아래의 방법을 사용함
string q1; string str;
int len = Edge[i][0].length(); char *term = new char[len+1]; Edge[i][0].copy( term, len, 0 ); term[len] = '\0';//문자 끝을 알려줌(안그럼 이상한 문자로 채워짐) CStem::stem(term);//char*타입을 파라미터로 사용하는 메소드 str = term; q1 = str;
C Programming Master (Data Structure & Algorithm 포함) C언어란 1980년대말부터 널리 보급된 컴퓨터 프로그래밍 언어로 다른 어떠한 컴퓨터 언어보다도 광범위한 분야에 적합하도록 만들어진 언어입니다. 이처럼C언어의 광범위한 효율성때문에 컴퓨터 프로그램은 물론 컴퓨터 그래픽스, 컴퓨터 게임, 웹 마스터, 웹 디자인등 모든 컴퓨터를 이용한 작업의 심도있는 제어를 가능하게 하는 기본이 됩니다.
주차
일
개 요
세부교육과정
1
01
CLanguage Fundamentals
C Language Fundamentals (C언어의 개념) 컴파일, 링크, 실행, 디버깅 방법
02
Data Type
Data Type (데이터 형) ? 변수 & 상수 Type Casting (데이터 형 변환)
03
Standard Input/Output
Standard I/O Function (표준입출력 함수 - printf(),scanf()) Input/Output Function (한문자 단위의 입출력 함수) Operator (연산자)
04
Control Structure-Ⅰ
Control Structure ?if Statement (제어구조 ? if 문) switch case Statement (switch case 문)
05
Control Structure-Ⅱ
for Statement (for 문) while Statement (while 문) break Statement, continue Statement (분기문)
Visual C++ Programming Master (Microsoft Foundation Class) 윈도우즈용 응용 프로그램의 통합 개발 환경인 마이크로소프트 비주얼 C++에 부속되는 클래스 라이브러리로서 윈도우즈 응용 프로그램 작성에 유용한 많은 클래스를 제공합니다 (60시간)
C++ Programming Master
주차
일
개 요
세부교육과정
1
01
Object Oriented Programming
- OOP(Object Oriented Programming)의 개념 - Object Modeling(객체 모델링)-C++ 의 struct(구조체)
- constructor(생성자) 와 destructor(소멸자) - Encapsulation(캡슐화) - this pointer variable(this포인터 변수) - class & array(객체의 배열)
07
copy constructor
- copy constructor(복사 생성자) - 복사생성자의 형태 - Deep Copy(깊은 복사)를 하는 복사생성자 - 복사생성자가 호출되는 경우
08
inheritance (상속)
- inheritance(상속)의 개요 - derived class(파생클래스)의 정의 - 상속된 객체와 포인터 그리고 참조의 관계 - Static Binding(정적바인딩) & Dynamic Binding(동적바인딩)
09
virtual
- virtual(가상)의 의미 - virtual Function (가상 함수) - Subtype Principle (Subtype 의 원리) - Pure virtual Function (순수 가상함수) - virtual destructor(가상 소멸자)
10
다중상속
- 다중상속에 대한 이해 - 다중상속의 문제(모호성) - virtual base class(가상베이스클래스)
3
11
Operator Overloading
- Operator Overloading(연산자 중복정의)의 의미 - Overloading(중복정의)가 불가능한 연산자 - 단항연산자의 오버로딩 - 교환법칙 문제의 해결 - 참조의 리턴
12
새로운형태의typecasting
- typecasting(형변환)이 필요한 이유 - static_cast & dynamic_cast - downcast - const_cast & reinterpret_cast
13
RTTI
- run time type information(RTTI)가 필요한 이유 - 실행시에 객체의 클래스 이름 얻어오기 - dynamic creation(동적생성) - typeid
14
Exception Handling
- exception(예외)의 의미(fault 와 error) - 기존의 예외처리 방법과 C++의 예외처리 매커니즘 - 예외의 제한 및 전파(propagation) - Stack Unwinding(스택풀기) - exception handling class(표준예외처리 클래스)
15
File I /O Stream
- C++ File Input/Output(파일 입출력) - Binary & ASCII File(바이너리와 아스키파일) - Buffering(버퍼를 사용하지 않는 입출력)
4
16
template (템플릿)
- template(템플릿)의 이해 - function template(함수템플릿) - class template(클래스템플릿) - STL(Standard Template Library)
- Sound Play(사운드 연주 및 비동기 연주) - MCI(MCI_Open & Play & Close) - 동영상 재생
4
16
OLE
- OLE의 이해 - MFC의 OLE지원 - OLE 서버와 컨테이너의 구현 - ClipBoard를 이용한 데이터 전송
17
ActiveX Control
- ActiveX Control의 이해 - ActiveX Control 제작 및 사용 - Subclassing(서브클래싱)을 이용한 ActiveX Control
18
Automation
- Automation의 이해 - UserInterface가 없는 Automation Server - UserInterface를 갖는 Automation Server - 다중인터페이스 Automation Server
19
Database
- ODBC의 이해 - ODBC를 이용한 DataBase Application - ADO를 이용한 DataBase Record의 조작기법
20
Network
- Windows Socket - IP / Protocol - MFC Socket Class - 범용적인 Socket Class 제작
Visual C++ Programming Master(Component Object Model) COM (Component Object Model)은 Component를 만들고 이 Component에서 Application을 구축하는 방법에 대한 명세서(Specification)입니다. 따라서 COM은 일정한 형식을 가지고 있어야 하며, 그래야만 어떠한 Application(설령 다른 언어로 만들어진 Application이라 하더라도)에서라도 마치 레고 블럭을 맞추듯 사용할 수 있는 것입니다. 즉 다시 말해 COM은 프로그램의 Component 객체들을 개발하고 지원하기 위한 하부 기반구조로서, CORBA(Common Object Request Broker Architecture)에서 정의된 수준의 기능 제공을 목표로 합니다. 마이크로소프트의 OLE가 사용자가 화면에서 볼 수 있는 복합문서를 위한 서비스를 제공하는 반면, COM은 인터페이스 교섭, 생명주기 관리, 라이선스, 이벤트 서비스(다른 객체에서 발생된 이벤트의 결과로서 한 객체를 서비스에 배정) 등 하부 서비스를 제공합니다. COM은COM+, DCOM, ActiveX interfaces, 프로그래밍 도구 등을 포함합니다. 우리는 COM을 공부함으로써 언어에 독립적이고, 이식성이 강하며, 플랫폼에 독립적인 Component를 작성하는 능력을 배양할 수 있습니다.
세상에서 제일 뛰어난 C 컴파일러 중 하나인 gcc 는 리눅스나 기타 자유 운영체제 에 있어 커다란 보배가 아닐 수 없습니다. 우리가 알고 있는 유닉스가 C 언어로 거의 다 만들어졌듯이 리눅스의 모국어는 바로 gcc 입니다.
사실 많은 분들이 리눅스 해커(hacker), 구루(guru)의 경지가 되고 싶어합니다. 그렇게 되길 원하신다면 리눅스의 모국어인 gcc 를 익히십시요. gcc 를 알면 리눅스를 아는 것이나 다름 없습니다. 사실 C 와 유닉스가 따로 떨어진 것이 아니라 어떻 게 보면 일심동체라고 할 수도 있듯이 gcc 와 리눅스는 일심동체라고 봐도 무방합니다.
C 언어! 이는 유닉스와 심지어 마이크로소프트 제품에 이르기까지( 어떤 식으로 변 질되었든 간에 ) 컴퓨터 세상의 ``만국 공통어''입니다. 여태까지 이러한 언어의 통일을 이뤄낸 것은 C 언어 밖에 없습니다. 컴퓨터 언어의 에스페란토를 지향하는 많은 언어들( 자바, 티클/티케이 )이 나오고 있으나 이는 두고 볼 일입니다. 그리 고 그 언어를 구사한다 할 지라도 C 언어는 역시나 ``기초 교양 언어''입니다.
여러분은 리눅스에서 gcc 를 통하여 그 동안 도스/윈도 환경에서 배운 엉터리 C 언 어를 잊으셔야 합니다. 감히 말하건데 그것은 C 언어가 아닙니다. C 언어는 만국 공통어야 함에도 불구하고 몇몇 회사들, 도스/윈도와 같은 환경에서 변질되어 각 환경마다 ``새로운 문법''을 배워야 하는 어처구니없는 사태가 벌어졌습니다. 터보 C 와 MS-C 를 배우면서 혼란도 많았고 그 뒤에 나온 녀석들은 완전히 다른 놈들이나 다름 없습니다.
지금 리눅스에서 여러분은 C 언어의 ``정통 소림권법''을 익히실 수 있습니다. 기초가 없이 비법만 전수받아 보았자 다른 곳에 가면 수많은 비법을 지닌 무림고수 들에게 여지없이 깨지기 마련입니다. 하지만 아무리 괴로와도 처음에 물 길어오는 것, 마당 쓰는 일부터 시작하면 철통같은 신체를 단련하기 때문에 온갖 꽁수 비법 으로는 여러분을 헤칠 수 없습니다. 또한 정통 권법을 연마한 사람은 기본기가 갖춰져 있으므로 대련 중에도 상대의 비법을 금방 간파하고 심지어 상대의 비법마저 자신의 것으로 하기도 합니다. ^^
--------------------------------------- gcc 에 대한 이야기 하나 ---------------------------------------
gcc 는 GNU 프로젝트에 의해 만들어진 작품의 하나로서 그 명성은 하늘을 찌를 듯 합니다. GNU 프로젝트의 산물 중 가장 멋진 것을 꼽으라면 저는 주저하지 않고 C 컴파일러의 최고봉인 gcc 를 지목할 것입니다.
실제로 gcc 의 명성은 뛰어나며 수많은 상용 회사들이 스폰서를 해주고 있다는 것을 아시는지요? 예를 들어 넥스트사( 지금은 사라짐 )의 새로운 C 언어인 ``오브젝티브 C''는 gcc 를 가져다 만든 것이며 FSF 측에 다시 기증되었습니다.
gcc 는 아주 강력합니다! 이미 상용 유닉스에 달려오는 AT&T 스타일, BSD 스타일의 C 언어 문법은 물론 ANSI C 를 기본으로 하여 모든 문법을 소화해낼 수 있으며 특유의 문법도 가지고 있습니다. 아주 구식 컴파일러, 아주 구식 문법도 소화해낼 수 있습니다. 이미 많은 사람들이 상용 유닉스에도 gcc 를 설치하여 사용하는 경우가 많지요. ( 물론 금전적인 문제가 많이 작용 ^^ )
gcc 는 매우 단순합니다. 어떤 의미로 이런 말을 하는가 하면, 터보 C/볼랜드 C 통합환경이나 윈도 환경의 비주얼한 환경을 제공하지 않는다는 것입니다. -.- 그들이 상당히 오토매틱한 성격을 갖는 반면, gcc 는 오로지 수동 스틱방식입니다. 각각의 장단점이 있지만 여러분이 일단 gcc 를 만나시려면 각오는 하고 계셔야 합 니다. 도스/윈도에서 보던 것을 원하지 마십시요. gcc 는 껍데기에 신경쓸 겨를조차 없습니다. gcc 는 오로지 명령행 방식만을 제공 합니다. 그리고 그 자체로 파워풀합니다. 개발 방향은 계속 ``뛰어난 능력''이지 겉모양 화장을 아닐 것입니다. ( 만약 겉모양을 원하신다면 그것은 여러분의 몫입니다. xwpe 같은 것이 그 좋은 예라고 할 수 있습니다 )
gcc 는 어떻게 보면 C 언어에 대한 개념이 서지 않는 사람에게는 무리인 C 컴파일 러인 듯 합니다. 기초 지식없이 사용한다는 것은 불가능에 가깝습니다. 하지만 C 언어를 확실하게 기초부터 배워서 어디서든 쓰러지지 않는 무림고수가 되기 위해서는 gcc 를 권합니다. 자잘한 무공을 하는 깡패가 되느냐? 아니면 정신을 지닌 무림고수가 되느냐?는 여러분의 선택에 달렸습니다.
gcc 가 어렵기만 한가? 하면 그렇지는 않습니다. gcc 는 상당한 매력을 지니고 있습니다. 그 매력으로 인해 한 번 빠지면 다른 컴파일러가 상당히 우습게 보이기까지 합니다. ( 그렇다고 다른 컴파일러를 비웃지는 마세요 ^^ 그거 쓰는 사람들이 자존심 상해서 엄청 화를 낼 테니까요. 개인적으로 gcc 에 대적할 수 있을 정도 되는 컴파일러는 와콤 C 컴파일러 정도? )
gcc 를 배우시려면 정신 무장(?)이 중요하다고 생각해서 이렇게 장황하게 읊었습니 다. 심플하게 배우면서 여러분의 리눅스, C 컴파일러에 대한 두려움을 하나씩 없애 고 C 언어 위에 군림하시기 바랍니다.
자, 이제는 잡담없이 시작합니다.
---------------------------------------
gcc 에 대한 기본 이해
---------------------------------------
명령행 상태에서 다음과 같이 입력해봅시다. 여러분이 사용하고 있는 gcc 버전은 알아두고 시작하셔야겠죠?
[yong@redyong yong]$ gcc -v
Reading specs from /usr/lib/gcc-lib/i386-linux/2.7.2.1/specs gcc version 2.7.2.1
[yong@redyong yong]$
( 위 공백은 보기 좋으라고 고의로 띄웠습니다. )
gcc -v 이라고 입력하니까 ``Reading specs from..'' 이라고 말하면서 그 결과값을 ``gcc version 2.7.2.1''이라고 말해주고 있습니다. ``Reading specs from...'' 자, 어디서 gcc 에 대한 정보를 읽어오는지 봅시다.
/usr/lib/gcc-lib/i386-linux/2.7.2.1/specs
gcc 를 여러분이 소스를 가져다 손수 설치해보신 적은 없을 것입니다. 보통은 바이너리 패키지로 된 것을 가져다 설치하지요. 나중에 정말 휴일에 너무 심심하다 싶으면 gcc 의 소스를 가져와서 컴파일해보십시요. 참, 재미있는 경험이 될 것입니다.
이미 여러분이 갖고 있는 gcc 를 가지고 새로운 gcc 를 컴파일하여 사용합니다. C 컴파일러를 가지고 새 버전의 C 컴파일러를 컴파일하여 사용한다! 이런 재미있는 경험을 또 어디서 해보겠습니까?
gcc 패키지가 어떤 것으로 구성되어 있는지... gcc 가 제대로 설치되어 있는지 알아보면 좋겠죠?
다음과 같습니다.
/lib/cpp --------> /usr/lib/gcc-lib/i386-linux/2.7.2.1/cpp ( 링크임 ) /usr/bin/cc --------> gcc ( 링크임 ) /usr/bin/gcc C 컴파일러 ``front-end'' /usr/bin/protoize /usr/bin/unprotoize /usr/info/cpp.info-*.gz GNU info 시스템을 이용하는 도움말 화일들 /usr/info/gcc.info-*.gz " /usr/lib/gcc-lib
마지막 /usr/lib/gcc-lib 디렉토리에 아래에 gcc 에 관한 모든 내용이 설치됩니다.
보통 다음과 같은 디렉토리 구조를 가집니다.
/usr/lib/gcc-lib/<플랫폼>/< gcc 버전 >
보통 우리는 리눅스를 i386 ( 인텔 환경 )에서 사용하고 있으므로 다음과 같이 나타날 것입니다.
/usr/lib/gcc-lib/i386-linux/2.7.2.1
( i386-linux, i486-linux, i586-linux 는 각기 다를 수 있습니다. 하지만 상관없는 내용입니다. 미친 척 하고 다른 이름을 부여할 수도 있습니다. )
cc1이 진짜 C 컴파일러 본체입니다. gcc 는 단지 적절하게 C 인가, C++ 인가 아니면 오브젝티브 C 인가를 검사하고 컴파일 작업만이 아니라 ``링크''라는 작업까지 하여튼 C 언어로 프로그램 소스를 만든 다음, 프로그램 바이너리가 만들어지기 까지 모든 과정을 관장해주는 ``조정자'' 역할을 할 뿐입니다. C 컴파일러는 cc1, C++ 컴파일러는 cc1plus, 오브젝티브 C 컴파일러는 cc1obj 입니다. 여러분이 C++/오브젝티브 C 컴파일러를 설치하셨다면 cc1plus, cc1obj 라는 실행화일도 찾아보실 수 있을 겁니다. cpp 는 "프리 프로세서"입니다. #include 등의 문장을 본격적인 cc1 컴파일에 들어가기에 앞서 먼저(pre) 처리(process)해주는 녀석입니다.
참고로 g++ 즉 C++ 컴파일러( 정확히는 C++ 컴파일러 프론트 엔드 )에 대한 패키지는 다음과 같습니다.
/usr/bin/c++ -----------> g++ 에 대한 링크에 불과함 /usr/bin/g++ /usr/lib/gcc-lib/i386-linux/2.7.2.1/cc1plus ( 진짜 C++ 컴파일러 )
int main ( void ) { (void) printf ( "Hello, Linux Girls! =)\n" ); return 0; } ------------Cut here--------------------
참고로 제일 간단한 소스는 다음과 같은 것입니다. ^^
------------Cut here-------------------- main () {} ------------Cut here--------------------
컴파일을 해보겠습니다! $ 는 프롬프트이지 입력하는 것이 아닌 것 아시죠?
$ gcc hello.c $
무소식이 희소식이라... gcc <C소스 화일명> 이렇게 실행하고 나서 아무런 메시지도 나오지 않고 다음 줄에 프롬프트만 달랑 떨어지면 그것이 바로 컴파일 성공입니다.
여러분은 무심코 다음과 같이 결과 프로그램을 실행시키려 할 것입니다.
$ hello bash: hello: command not found $
예. 땡입니다. ^^
여러분은 다음과 같이 실행시켜야 합니다.
$ ./a.out
맨 앞의 도트 문자(.)는 현재 디렉토리를 의미합니다. 그 다음 디렉토리 구분 문자슬래쉬(/)를 쓰고 유닉스 C 에서 ``약속한'' C 컴파일러의 출력 결과 바이너리 화일인 a.out 을 써줍니다.
이러한 습관은 아주 중요합니다. 여러분이 현재 디렉토리에 어떤 실행 화일을 만들고 나서 테스트하려고 한다면 꼭 ./<실행 화일명> 이라고 적어줍니다.
유닉스는 기본적으로 PATH 라는 환경변수에 있는 디렉토리에서만 실행화일을 찾을 뿐입니다. 만약 PATH 라는 환경변수에 현재 디렉토리를 의미하는 도트 문자(.)가 들어있지 않으면 현재 디렉토리의 실행화일은 절대 실행되지 않습니다. 게다가 현재 디렉토리를 PATH 환경 변수에 넣어준다 할 지라도 도스처럼 현재 디렉토리를 먼저 찾는다든지 하는 일은 없습니다. 오로지 PATH 에 지정한 순서대로 수행합니다.
실행 바이너리명이 계속 a.out 으로 나오면 좀 곤란하죠. 뭐 물론 mv 명령으로 a.out 의 이름을 바꾸면 되지만서도...
=============== -o 옵션 ===============
-o 옵션( 소문자 o 임! )은 출력(output) 화일명을 정하는 옵션입니다. 위에서 우리는 hello.c 라는 소스를 가지고 일반적으로 hello 라는 이름의 실행화일을 만들고 싶어할 것입니다.
$ gcc -o hello hello.c ^^^^^^^^ ( ^ 문자로 밑줄을 친 것은 강조하기 위해서일 뿐임 )
또는 다음과 같이 순서를 바꿔도 무방합니다.
$ gcc hello.c -o hello ^^^^^^^^
워낙 유닉스 쪽은 명령행 방식이 전통적이고 주된 방식이라 명령행에서 하는 일은 뛰어납니다.
당연히 실행을 하려면 ./hello 라고 하셔야 합니다. 결과는 다음처럼 나오겠지요?
$ ./hello Hello, Linux Girls! =) $
<주의>
제일 안좋은 습관 중 하나가 바로 테스트용으로 만든 소스라고 다음처럼 하는 것입니다.
$ gcc -o test test.c $ test $
문제를 알아내기 위하여 위에서 작성한 hello.c 를 컴파일/링크해봅시다.
$ gcc -o test hello.c $ test $
원하는 문자열이 출력되지 않았습니다. -.-
$ ./test Hello, Linux Girls! =) $
=============== -c 옵션 ===============
어떤 이유에서든 오로지 컴파일(compile) 작업만 하고 싶은 경우가 있습니다. 그럴 때는 다음과 같이 합니다.
$ gcc -c hello.c $
그 결과 만들어지는 화일은 전통적으로 hello.c 에서 .c 부분을 떼어내고 .o 를 붙인 화일입니다. 오브젝트 화일, 목적 화일이라고 하지요.
hello.o 라는 화일이 만들어집니다.
여러분은 C 언어로 조금이라도 복잡한 프로그램을 만들기 시작하면 여러 개의 소스로 나누어서 전체 프로그램을 짜게 됩니다. 그럴 때는 각 소스가 전체 기능에 기여하는 특정 기능의 함수들을 가지게 되고 오로지 한 녀석만 main 함수를 가집니다.
만약 어떤 프로그램이 foo.c, bar.c 이렇게 두 개의 소스로 이루어져 있다고 합시다. 이럴 때는 다음과 같이 하는 것이 가능합니다.
위에서 보면 "아니! C 컴파일러에 .c 즉 소스 화일이 아닌 오브젝트 화일도 막 써주나?"라는 생각을 하시게 될 겁니다. 그렇습니다! 왜냐? gcc 는 정확히 말해서 C 컴파일러가 아닙니다. gcc 라는 실행화일 자체는 "C 컴파일러를 돌리는 녀석"입니다. 더욱 더 정확히 말해봅시다.
첫번째 예는 헤더 화일이 현재 소스 하위 디렉토리(..)에 있다는 뜻이며 두번째는 현재 디렉토리의 include라는 디렉토리에 들어있다는 뜻입니다.
-I 옵션은 얼마든지 여러번 쓸 수 있으며 주어진 순서대로 헤더 화일을 검색합니 다.
<주의>
디렉토리명은 -I 라는 문자 바로 다음에 붙여서 씁니다. 즉 -I <디렉토리> 라는 식이 아니라 -I<디렉토리> 입니다.
또한 유닉스에 있어 표준 헤더 화일 디렉토리는 /usr/include 라는 사실을 기억하시기 바랍니다. 또한 리눅스에 있어서는 커널 소스가 아주 중요한데 리눅스 고유의 기능을 쓰는 리눅스용 프로그램의 경우에는 /usr/include/linux, /usr/include/asm, /usr/include/scsi (최신 커널의 경우) 라는 디렉토리가 꼭 있 어야 하며 각각은 커널 소스의 헤더 디렉토리에 대한 링크입니다. 따라서 커널 소스를 꼭 설치해두셔야 합니다.
이러한 라이브러리는 우리가 컴파일 과정을 거쳐서 만든 .o 화일을 한 곳에 통들어 관리하는 것에 불과합니다. 따라서 archive 를 의미하는 .a 라고 이름을 짓게 된 것이죠. 라이브러리는 ``도서관''으로 서 그냥 .o 를 무작위로 집어넣은 것은 아니고 당연히 도서관에는 소장하고 있는 책에 대한 목록(index)을 가지듯 포함되어 있는 .o 에 대한 인덱스(index)를 가지고 있습니다.
라이브러리는 다음과 같이 구성되어 있다고 할 수 있는 것입니다.
라이브러리 = 목차(index) + ( a.o + b.o + c.o + ... )
libc.a 를 가지고 한 번 놀아볼까요? 라이브러리 아카이브를 관리하는 ar 이라는 GNU 유틸리티를 써보겠습니다.
$ cd /usr/lib $ ar t libc.a assert-perr.o assert.o setenv.o ftime.o psignal.o mkstemp.o sigint.o realpath.o cvt.o gcvt.o ctype-extn.o ctype.o <등등... 계속>
main 함수에서 say_hello 라는 함수를 사용하게 됩니다. 이 정도야 그냥 이렇게 해버리고 말죠 ^^
$ gcc -o say_linux hello.c myfunc.c
하지만 라이브러리를 만들어보고 시험해보려고 하는 것이므로 일부러 어렵게 한번 해보기로 하겠습니다.
$ gcc -c myfunc.c $ ar r libmylib.a myfunc.o $ ar s libmylib.a $ ar t libmylib.a myfunc.o $ gcc -o say_linux hello.c -lmylib ^^^^^^^^ ld: cannot open -lmylib: No such file or directory
흠... 처음부터 만만치 않죠? ^^ 실패하긴 했지만 몇 가지를 일단 알아봅시다.
------------- -l 옵션 -------------
링크(link)할 라이브러리를 명시해주는 옵션이 바로 -l ( 소문자 L ) 옵션입니다. -I 옵션과 마찬가지로 바짝 붙여서 씁니다. 절대 떼면 안됩니다.
우리는 libmylib.a 라는 라이브러리를 만들어두었습니다. 그것을 사용하기 위해서는 -lmylib 라고 적어줍니다. 라이브러리 화일명에서 어떤 글자들을 떼내고 쓰는지 주목하십시요.
libmylib.a ^^^^^ 앞의 lib 를 떼내고 맨 뒤에 붙는 .a 를 떼냅니다.
링크(link)라는 것이 어떤 것이 모르신다면 당장 C 프로그래밍 책을 다시 읽어보시 기 바랍니다. 이 글에서 설명할 범위는 아닌 듯 합니다. ------------- -L 옵션 -------------
ld 는 유닉스에서 사용되는 링커(Linker)입니다. C 프로그램 컴파일의 맨 마지막 단계를 맡게 되지요.
위에서 우리는 다음과 같은 에러 메세지를 만났습니다.
ld: cannot open -lmylib: No such file or directory
자, 이제 배워야 할 옵션은 ``라이브러리의 위치를 정해주는'' -L ( 대문자 L ) 옵션입니다. 사용형식은 -L<디렉토리명> 입니다.
리눅스에서 어떤 라이브러리를 찾을 때는 /lib, /usr/lib, /usr/local/lib 와 같은 정해진 장소에서만 찾게 되어 있습니다. 그것은 규칙이지요. 중요한 사실은 아무리 여러분 라이브러리를 현재 작업 디렉토리에 놓아두어도 ld 는 그것을 찾지 않는다는 사실입니다. ld 더러 라이브러리가 있는 장소를 알려주려 면 다음과 같이 -L 옵션을 붙이십시요.
$ gcc -o say_linux hello.c -lmylib -L. ^^^ -L. 은 현재 디렉토리에서 라이브러리를 찾으라는 말입니다. -L 옵션은 여러번 줄 수 있습니다.
성공적으로 컴파일되었을 겁니다.
$ ./say_linux Hello, Linux guys!
지금까지 여러분은 gcc 옵션 중 두번째로 중요한 -I, -l, -L 옵션에 대하여 배우셨 습니다. 그리고 또한 라이브러리 만들기에 대하여 맛보기를 하였습니다. 다음 시간에 뵙기로 합시다.
다음은 make 강좌를 시작합니다. 그 이후 다시 gcc 관련 사용법을 더욱 자세히 알 아보도록 하겠습니다.
Makefile 강의
1. 머리말 =========
소스 한두 개로 이루어진 C/C++ 언어 교양과목 과제물을 제출하는 것이 아니라면 약간만 프로젝트가 커져도 소스는 감당할 수 없을 정도로 불어나게 되고 그것을 일일이 gcc 명령행 방식으로 처리한다는 것은 상당히 곤역스러운 일입니다.
그래서 하나의 프로젝트를 효율적으로 관리하고 일관성있게 관리하기 위하여 Makefile 이라는 형식을 사용하고 make 라는 유틸리티를 사용합니다.
여러분이 리눅스에서 소스 형태로 되어 있는 것을 가져와서 컴파일하게 되면 보통 마지막에는 make 라는 명령, 또는 make <어쩌구> 이런 식으로 치게 됩니다.
make 라는 유틸리티는 보통 현재 디렉토리에 Makefile 또는 makefile 이라는 일정한 규칙을 준수하여 만든 화일의 내용을 읽어서 목표 화일(target)을 만들어냅니다. Makefile의 이름을 다르게 명시하고 싶을 때는 다음과 같이 합니다.
$ make -f Makefile.linux
보통 멀티플랫폼용 소스들은 Makefile.solaris, Makefile.freebsd, Makefile.hp 이런 식으로 Makefile 을 여러 개 만들어두는 경향이 있지요. 또는 적절하게 만들어두어 다음과 같이 make <플랫폼> 라는 식으로 하면 컴파일 되도록 하기도 합니다.
$ make linux
이런 일은 보통의 관례일 뿐이죠. 더 예를 들어보자면 이런 식입니다. 우리가 커널 컴파일 작업할 때를 보십시요.
$ make config /* 설정 작업을 한다 */ $ make dep /* 화일 의존성을 검사한다 */ $ make clean /* 만든 화일들을 지우고 깨긋한 상태로 만든다 */ $ make zImage /* zImage(압축커널)를 만든다 */ $ make zlilo /* 커널을 만들고 LILO를 설정한다 */ $ make bzImage /* bzImage(비대압축커널)를 만든다 */ $ make modules /* 커널 모듈을 만든다 */ $ make modules_install /* 커널 모듈을 인스톨한다 */
복잡한 것같아도 우리는 항상 make, make, make ... 일관성있게 make 라고만 쳐주면 됩니다 ^^ 분량이 작은 소스들의 경우에는 일반적으로 다음만 해도 되는 경우가 많죠.
$ make 또는 make all $ make install
영어권에 사는 사람들에게는 더욱 친밀하게 느껴질 겁니다. 그렇겠죠? ``만들라!''라는 동사를 사용하고 있는 것이고 그 다음에는 그들의 정상적인 어순에 따라 목적어가 나오죠.
$ make install.man
또한 관례상 ``맨페이지'' 같은 것은 별도로 인스톨하도록 배려하는 경우가 많습니다. 프로그램에 대해 잘 아는 사람이라면 맨페이지를 자질구레하게 설치하고 싶지 않을 때도 많으니까요.
다른 사람에게 공개하는 소스라면 더욱 make 를 사용해야 합니다. 그들 뿐 아니라 여러분 자신도 make 라고만 치면 원하는 결과가 나올 수 있도록 하는 것이 좋습니다. 많은 소스를 작성하다 보면 여러분 스스로도 까먹기 쉽상입니다.
일단 make를 사용하는 일반적인 관례를 익히는 것이 중요하다고 봅니다. 리눅스 배포판 패키지만 설치하지 마시고 적극적으로 소스를 가져다 컴파일해보십시요. 실력이든 꽁수든 늘기 시작하면 여러분은 더욱 행복해지실 수 있습니다. =)
2. 본문 =========
일관성있게 make라고만 치면 모든 일이 술술 풀려나가도록 하는 마술은 Makefile 이라는 것을 어떻게 여러분이 잘 만들어두는가에 따라 결정됩니다. 바로 이 Makefile 을 어떻게 만드는지에 대하여 오늘 알아봅니다.
상황 1)
$ gcc -o foo foo.c bar.c
여기서 foo 라는 실행화일은 foo.c, bar.c 라는 2 개의 소스로부터 만들어지고 있습니다. 여러분이 지금 계속 코딩을 하고 있는 중이라면 이 정도 쯤이야 가상콘솔 또는 X 터미널을 여러 개 열어두고 편집하면서 쉘의 히스토리 기능을 사용하면 그만이지만 하루 이틀 계속 해간다고 하면 곤역스러운 일이 아닐 수 없습니다.
자, 실전으로 들어가버리겠습니다. vi Makefile 해서 만들어봅시다. ( 편집기는 여러분 마음 )
---------------------------------------------------------------------------- 목표: 목표를 만드는데 필요한 구성요소들... 목표를 달성하기 위한 명령 1 목표를 달성하기 위한 명령 2 ... ---------------------------------------------------------------------------- Makefile은 조금만 실수해도 일을 망치게 됩니다.
맨 첫번째 목표인 foo 를 살펴보죠. 맨 첫 칸에 foo: 라고 입력하고 나서 foo가 만들어지기 위해서 필요한 구성요소를 적어줍니다. foo가 만들어지기 위해서는 컴파일된 foo.o, bar.o 가 필요합니다. 각 요소를 구분하는데 있어 콤마(,) 같은 건 사용하지 않고 공백으로 합니다.
중요! 중요! 그 다음 줄로 넘어가서는 <탭>키를 누릅니다. 꼭 한 번 이상은 눌러야 합니다. 절대 스페이스키나 다른 키는 사용해선 안됩니다. 목표 화일을 만들어 내기 위한 명령에 해당하는 줄들은 모두 <탭>키로 시작해야 합니다. Makefile 만들기에서 제일 중요한 내용입니다. <탭>키를 사용해야 한다는 사실, 바로 이것이 중요한 사실입니다. foo를 만들기 위한 명령은 바로 gcc -o foo foo.o bar.o 입니다.
다시 한 번 해석하면 이렇습니다. foo 를 만들기 위해서는 foo.o와 bar.o가 우선 필요하다.( foo: foo.o bar.o ) 일단 foo.o, bar.o 가 만들어져 있다면 우리는 gcc -o foo foo.o bar.o 를 실행하여 foo 를 만든다.
-----------------------------foo.c 의 내용---------------------------------- extern void bar ( void );
int main ( void ) { bar (); return 0; } ----------------------------------------------------------------------------
-----------------------------bar.c 의 내용---------------------------------- #include <stdio.h>
void bar ( void ) { printf ( "Good bye, my love.\n" ); } ----------------------------------------------------------------------------
Makefile을 위처럼 만들어두고 그냥 해보죠.
$ make 또는 make foo gcc -c foo.c gcc -c bar.c gcc -o foo foo.o bar.o
명령이 실행되는 순서를 잘 보십시요. 여기서 감이 와야 합니다. ^^
$ ./foo Good bye, my love.
다시 한 번 실행해볼까요?
$ make make: `foo' is up to date.
똑똑한 make는 foo를 다시 만들 필요가 없다고 생각하고 더 이상 처리하지 않습니다.
이번에는 foo.c 를 약간만 고쳐봅시다. return 0; 라는 문장을 exit (0); 라는 문장으로 바꾸어보죠. 그리고 다시 한 번 다음과 같이 합니다.
$ make gcc -c foo.c gcc -o foo foo.o bar.o
자, 우리가 원하던 결과입니다. 당연히 foo.c 만 변화되었으므로 foo.o 를 만들고 foo.o가 갱신되었으므로 foo도 다시 만듭니다. 하지만 bar.c는 아무 변화를 겪지 않았으므로 이미 만들어둔 bar.o 는 그대로 둡니다 소스크기가 늘면 늘수록 이처럼 똑똑한 처리가 필요하지요.
$ rm -f foo $ make gcc -o foo foo.o bar.o
이것도 우리가 원하던 결과입니다. foo 실행화일만 살짝 지웠더니 make는 알아서 이미 있는 foo.o, bar.o 를 가지고 foo 를 만들어냅니다. :)
make clean이라는 작업 또한 중요한 작업입니다. 확실히 청소를 보장해주어야 하거든요.
make, make clean 이런 것이 되면 상당히 멋진 Makefile 이라고 볼 수 있죠? 이번 clean 에서 보여드리고자 하는 부분은 이런 것입니다.
우리의 머리 속에 clean 이라는 목표는 단지 화일들을 지우는 일입니다. clean: 옆에 아무런 연관 화일들이 없지요? 그리고 오로지 rm -f foo foo.o bar.o 라는 명령만 있을 뿐입니다. clean 이라는 목표를 수행하기 위해 필요한 것은 없습니다. 그러므로 적지 않았으며 타당한 make 문법입니다.
이런 식으로 해두면 어떤 장점이 있는지 알아봅시다. 보통 make all 하면 foo1, foo2, foo3가 모두 만들어집니다. 그런데 어떤 경우에는 foo1만 또는 foo2만을 만들고 싶을 때도 있을 겁니다. 괜히 필요없는 foo3 같은 것을 컴파일하느라 시간을 보내기 싫으므로 우리는 단지 다음과 같이만 할 겁니다.
$ make foo1 $ make foo2
물론 일반적으로 다 만들고 싶을 때는 make all 이라고만 하면 됩니다. make all 이건 아주 일반적인 관례이지요.
=========================== Makefile 기초 마지막 강의 ===========================
이어지는 이야기입니다. ( 이 내용은 RUNNING LINUX 라는 유명한 리눅스 책에서 발췌한 것이고 약간의 설명을 덧붙였을 뿐이다. 오렐리 출판사에서 나온 아주 유명한 책이며 Matt Welsh, Lar Kaufman 씨가 집필한 책입니다. 저는 이 책을 누더기가 될 때가지 보고야 말겠습니다 ^^ )
여기서 .c.o 의 의미를 생각해보겠습니다. ".c 를 입력화일로 받고 .o 화일을 만든다"
gcc -c ${CFLAGS} $<
이 문자을 보면 일단 눈에 띄는 것은 ${CFLAGS}라는 표현과 $< 라는 암호와도 같은 표현입니다. 여기서는 일단 $< 라는 기호의 의미를 알아보겠습니다. 유닉스에서 쉘을 잘 구사하시는 분들은 눈치채셨을 겁니다. 작다 표시(<)는 리다이렉션에서 입력을 의미하는 것을 아십니까? 그렇다면 $< 는 바로 .c.o 라는 표현에서 .c 즉 C 소스 화일을 의미합니다.
예를 들어 foo.c 가 있다면 자동으로
gcc -c ${CFLAGS} foo.c
가 수행되며 gcc 에 -c 옵션이 붙었으므로 foo.o 화일이 만들어질 것입니다.
-------------------- GNU make 확장 기능 --------------------
.c.o 라는 전통적인 표현 말고 GNU 버전( 우리가 리눅스에서 사용하는 것은 바로 이것입니다 )의 make 에서 사용하는 방법을 알아봅시다.
위에서 예로 든 것을 GNU 버전의 make 에서 지원하는 확장문법을 사용하면 다음과 같습니다.
그냥 설명 전에 잘 살펴보시기 바랍니다. 우리가 위에서 알아보았던 표준적인 .c.o 라는 꼬리말 규칙(Suffix rule)보다 훨씬 논리적이라는 것을 발견하셨습니까? 우리가 바로 전 강의에서 main.o : main.c 이런 식으로 표현한 것과 같은 맥락이지요? 이것을 우리는 패턴 규칙(Pattern rule)이라고 부릅니다.
콜론(:) 오른쪽이 입력 화일이고 왼쪽이 목표 화일입니다. 화일명 대신 퍼센트(%) 문자를 사용한 것만 유의하면 됩니다. 여기서 foo.c 라는 입력화일이 있다면 % 기호는 foo 만을 나타냅니다.
gcc -c -o $@ ${CFLAGS} $<
라는 표현을 해석해봅시다. ( 후~ 마치 고대 문자판을 해석하는 기분이 안드십니까? ^^ )
$< 는 입력화일을 의미하고 $@ 은 출력화일을 의미합니다. .c.o와 같은 꼬리말 규칙과 별 다를 바 없다고 생각하실 지 모르나 -o $@ 를 통하여 .o 라는 이름 말고 전혀 다른 일도 해낼 수 있습니다.
자, make CFLAGS="-O" 이런 명령을 한 번 봅시다. ${CFLAGS}에서 {} 표현은 유닉스 쉘에서 변수값을 알아낼 때 쓰는 표현입니다.
CFLAGS 값을 여러분이 Makefile에 고정적으로 집어넣지 않고 그냥 make 만 실행하는 사람에게 선택권을 주기 위해서 사용하거나 자기 스스로 어떤 옵션이 제일 잘 맞는지 알아보기 위해서 사용합니다.
다른 옵션으로 컴파일하는 것마다 일일이 다른 Makefile을 만들지 말고 가변적인 부분을 변수화하는 것이 좋습니다.
3) 마지막 주의 사항 =============================================================================
---------------------------------------------------------------------------- target: cd obj HOST_DIR=/home/e mv *.o $HOST_DIR ----------------------------------------------------------------------------
하나의 목표에 대하여 여러 명령을 쓰면 예기치 않은 일이 벌어집니다. 기술적으로 말하자면 각 명령은 각자의 서브쉘에서 실행되므로 전혀 연관이 없습니다. -.- cd obj 도 하나의 쉘에서 HOST_DIR=/home/e도 하나의 쉘에서 나머지도 마찬가지입니다. 각기 다른 쉘에서 작업한 것처럼 되므로 cd obj 했다 하더라도 다음번 명령의 위치는 obj 디렉토리가 아니라 그대로 변함이 없이 현재 디렉토리입니다. 세번째 명령에서 HOST_DIR 변수를 찾으려 하지만 두번째 명령이 종료한 후 HOST_ DIR 변수는 사라집니다.