'CS&E'에 해당되는 글 71건
- 2008/07/03
- 2008/07/01
- 2008/07/01
- 2008/01/07
- 2007/06/02
- 2007/05/31
- 2007/05/27
- 2007/02/20
- 2006/12/22
- 2006/12/21


오늘은 한 몇년만에 나름 진지한 글을 적어보려고 합니다. 어쩌다보니 3분동안 하고 싶은 주제로 이야기를 할 기회가 돼어서 그 3분의 준비를 위해서 이전에 알고 있던 내용을 다시 정리해보려고 합니다. (사실 아무 주제로나 말하라고 했지만... 재미있게 할 만한 말이 없더라구요)
컴퓨터의 발전사 속에는 익히 사람들이 알다시피 소위 천재들이 곳곳에서 많은 활약을 했습니다.
멀리는 튜링머신으로 유명한 알렌 튜링(Alan Turing)에서 시작해서, 마우스를 고안한 것으로 잘 알려진 더글라스 앵겔바트(Douglas Engelbart)가 있겠고, 가까이는 C++을 만든 뱐 스트라우스트럽(Bjarne Straustrup), Java를 고안한 제임스 고스링(James Gosling), 이 복잡한 프로그래밍 세상에 무지한 중생들(?)을 일깨우고자 DP를 제창한 GoF역시도 여기에서 빠질 수는 없겠습니다.
이런 많은 사람중에서도 제가 소개하고 싶은 사람은 바로 배니바르 부시(Vannevar Bush)라는 사람입니다. 아마 컴퓨터 역사에서 가장 사람들의 존경을 받는 사람은 튜링인듯 싶지만... (컴퓨터 분야의 위대한 업적을 남긴 과학자에게 주는 상의 이름이 튜링상인 것을 보면)
하지만 저는 이 사람보다 배니바르 부시를 더욱 높게 평가하고 싶습니다. 이유는 이 사람이 제안한 메멕스라는 다소 황당한 기계장치 때문입니다.
메멕스(Memex)의 영어 정식 명칭은 MEMory EXtender 입니다. 바로 해석하자면 기억 확장기? 정도로 말할 수 있겠죠?
왜?? 기억 확장기라고 이 사람은 이 기계를 말했을까요?
이게 바로 부시가 제안한 메멕스라는 기계의 간단한 도면입니다. 그림이 하도 작으니 좀더 큰 그림으로 보죠.
중요한 점은 이런 모든 장치들이 부시가 메멕스를 제안한지 30년이 지난후에 개인용 데스크 탑이 나오게 돼었고, 50년이 지나서야 팀 버너스 리가 WWW를 고안하면서 웹의 세상이 열리면서 부시가 상상하던 세계가 열렸다는 점입니다.
자바의 classpath 를 사용하는 이유는 프로그램을 작성하면서 참조하는 다양한 라이브러리의 경로를 명시하여, jvm 구동시 이를 동적으로 링크하기 위함이다.
이를 지정하는 방법은 크게 두가지로 구분할 수 있다. 한가지는 시스템이 제공하는 환경변수 설정을 이용하는 방법, 두번째는 java 구동환경에서 option 을 설정을 통해서 명시적으로 지정하는 방법이다.
1. 환경 변수를 이용한 설정
운영체제는 시스템 운영상 필요한 경로를 시스템에 지정해두고서
참조하는 것이 가능하다. 이를 환경 변수라고 부르는데, 자바도
이곳에 필요한 경로를 설정해두고서 이용하는 것이 가능하다.
필요한 환경변수는 Path,
CLASSPATH 두개의 환경변수가 필요하며, 첫번째
Path 는 운영체제가 명령어를 실행하면서 해당 명령어에 맞는 실행파일을 찾아가는 순서를 명시한 환경변수이다.
이곳에 아래와 같은 식으로 설정을 함으로써 어떤 폴더에서나 자바의
컴파일러인 javac를 접근할 수 있다.
PATH=C:\Program Files\Java\jre1.6.0_05\bin;%SystemRoot%\system32;%SystemRoot%;
CLASSPATH= .;C:\Program Files\Java\jre1.6.0_05\lib\;
문제는 이렇게 환경변수를 설정하는 경우 컴파일시 다른 버전의
컴파일러를 실행하기 위해서는 변수 설정이 안됐을 때와 마찬가지로 절대경로로 접근해야하는 문제가 존재한다. 이런
상황에서 변리한 버전 변경을 위해서 아래와 같은 테크닉을 이용한다.
또한, 상기에서 보면 CLASSPATH에 현재 경로를 의미하는 . 를 추가했는데, 이는 자바 컴파일러가 명시적으로 CLASSPATH를 지정할 경우
현재 경로를 보지 않기 때문에 환경 변수상에서 현재 폴더를 추가해야만 정상적인 실행이 가능하기 때문이다.
JAVAPATH= C:\Program Files\Java\jre1.6.0_05;
PATH= %JAVAPATH%\bin;%SystemRoot%\system32;%SystemRoot%;
CLASSPATH= .; %JAVAPATH%\lib\;
즉, 환경변수는 시스템내에서 %{environmental
variables}%의 형대로 참조가 가능하다. 이렇게 설정을 할 경우 다른 버전의 자바
컴파일러를 사용할 경우 JAVAPATH 만을 변경해주면 된다.
도메인 신청에 대해서 아무것도 모르던 때에 현재 도메인을 구입해서 상대적으로 가격이 엄청 비쌌던 후이즈에서 신청을 했었습니다. (후이즈는 왜 이렇게 비쌀까요?)
전 원래 그 가격에만 등록이 돼는 줄 알았는데... 쩝...
하여간 슬슬 기간도 반년정도만 남아서 dotname으로 등록 기관을 이전했습니다.
생각보다 엄청 간단하더군요. ;;
그냥 기존의 등록기관에서 AUTH CODE를 받아서 새로운 등록 기관으로 신청만 하면 돼더라는...
1. JSP 를 이용한 BLOB 저장
-------------------------------------------------------------------------
File file = (File) param.get("sajin"); // 등록할 File
Blob emptyBlob = null;
OutputStream outstream = null;
FileInputStream finstream = null;
ResultSet rs = null;
try{
// EMPTY_BLOB() 처리
sql = "update table set sajin=EMPTY_BLOB() where id=?";
ps = con.prepareStatement(sql);
ps.setString(1, (String)param.get("id"));
if ( ps.executeUpdate() < 0 ) throw new Exception();
// 저장할 sajin Column 가져온다.
sql = "select sajin from table where id=?";
ps = con.prepareStatement(sql);
ps.setString(1, (String)param.get("id"));
rs = ps.executeQuery();
if ( rs.next() ) emptyBlob = rs.getBlob(1);
// db blob output stream
oracle.sql.BLOB bol = (oracle.sql.BLOB) emptyBlob;
outstream = bol.getBinaryOutputStream();
int size = bol.getBufferSize();
// 파일 input stream
finstream = new FileInputStream(file);
// 파일 읽어서 db에 넣기
byte[] buffer = new byte[size];
int length = -1;
while ((length = finstream.read(buffer)) != -1) {
outstream.write(buffer, 0, length);
}
} catch (Exception e){
throw(e);
} finally {
if( rs != null ) rs.close();
if( finstream != null ) finstream.close();
if( outstream != null ) outstream.close();
}
-------------------------------------------------------------------------
즉, 다른 타입처럼 update문이나 insert를 이용하지 않는다.
다시 한번 정리하면, insert할 column을 EMPTY_BLOB()로 초기화
초기화된 column을 select 하여 OutputStream을 통해 file을 DB에 저장한다.
2. JSP 를 이용한 BLOB 브라우저에서 보기
-------------------------------------------------------------------------
package showImage;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
public class ShowImageServlet extends HttpServlet
{
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
Connection con = null;
ResultSet rs = null;
PreparedStatement ps = null;
InputStream is = null;
// Image를 가져오기위한 키값들
String key1 = request.getParameter("key1"); // Primary key 1
String key2 = request.getParameter("key2"); // Primary key 2
String file_type = null; // Image 파일 타입
String content_type = null; // Image 보여주기위한 Content_type
// Image 가져올 SQL
final String SQL =
" SELECT image_type, image " +
" FROM cu_basic_t " +
" WHERE key1 = '"+key1+"' AND key2 = '"+key2+"' ";
try {
con = UtilDB.getConnection(); // DB 연결
ps = con.prepareStatement(SQL);
rs = ps.executeQuery(); // SQL 실행
// Image 가져오는 부분(content type도 정해준다.)
if (rs!=null && rs.next()){
file_type = rs.getString("image_type");
is = rs.getBinaryStream("image");
if (file_type.toUpperCase().equals("JPG")) file_type = "jpeg";
else if (file_type.toUpperCase().equals("GIF")) file_type = "gif";
content_type = "image/" + file_type; // "image/jpeg"나 "image/gif"
response.setContentType(content_type); // Content Type Set
// Image를 Stream을 통해 out
ServletOutputStream os = response.getOutputStream();
int binaryRead;
while ((binaryRead = is.read()) != -1)
{
os.write(binaryRead);
}
} else {
throw new Exception("사진이 없습니다.");
}
}
catch(ServletException e) {
e.printStackTrace();
throw e;
}catch(IOException e) {
e.printStackTrace();
throw e;
}
catch(Exception e) {
System.out.println("An error occurs : " + e.toString());
e.printStackTrace();
}
finally {
UtilDB.closeConnection(con, ps, rs); // DB 닫아준다.
}
}
}
package showImage;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
public class ShowImageServlet extends HttpServlet
{
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
Connection con = null;
ResultSet rs = null;
PreparedStatement ps = null;
InputStream is = null;
// Image를 가져오기위한 키값들
String key1 = request.getParameter("key1"); // Primary key 1
String key2 = request.getParameter("key2"); // Primary key 2
String file_type = null; // Image 파일 타입
String content_type = null; // Image 보여주기위한 Content_type
// Image 가져올 SQL
final String SQL =
" SELECT image_type, image " +
" FROM cu_basic_t " +
" WHERE key1 = '"+key1+"' AND key2 = '"+key2+"' ";
try {
con = UtilDB.getConnection(); // DB 연결
ps = con.prepareStatement(SQL);
rs = ps.executeQuery(); // SQL 실행
// Image 가져오는 부분(content type도 정해준다.)
if (rs!=null && rs.next()){
file_type = rs.getString("image_type");
is = rs.getBinaryStream("image");
if (file_type.toUpperCase().equals("JPG")) file_type = "jpeg";
else if (file_type.toUpperCase().equals("GIF")) file_type = "gif";
content_type = "image/" + file_type; // "image/jpeg"나 "image/gif"
response.setContentType(content_type); // Content Type Set
// Image를 Stream을 통해 out
ServletOutputStream os = response.getOutputStream();
int binaryRead;
while ((binaryRead = is.read()) != -1)
{
os.write(binaryRead);
}
} else {
throw new Exception("사진이 없습니다.");
}
}
catch(ServletException e) {
e.printStackTrace();
throw e;
}catch(IOException e) {
e.printStackTrace();
throw e;
}
catch(Exception e) {
System.out.println("An error occurs : " + e.toString());
e.printStackTrace();
}
finally {
UtilDB.closeConnection(con, ps, rs); // DB 닫아준다.
}
}
}
-------------------------------------------------------------------------
JSP 기준이다.
보여줄 JSP에 다음을 추가
<img src="/servlet/ShowImageServlet?key1=<%=key1%>&key2=<%=key12%>"
width="100" height="100" />
SRC에 image를 보여주도록 만들어진 서블릿을 입력
참고 (서블릿 실행을 위해 WAS(Jeus) 설정법)
WEB-INF\web.xml 내에 서블릿 등록을 등록하고 서버 재 Start시킨다.
<servlet>
<servlet-name>ShowImageServlet</servlet-name>
<servlet-class>showImage.ShowImageServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ShowImageServlet</servlet-name>
<url-pattern>/ShowImageServlet</url-pattern>
</servlet-mapping>
| backspace | URL, 파일 이름을 이용해서 torrent 를 추가한다. 디렉토리 내용을 보고 자동 완성하려고 Tab 키를 이용할 수 있다. 이때 ~/torrent/* 같이 와일드 카드 문자를 사용해서 입력하는 것이 가능하다. |
| return | torrent 를 비 활성화된 상태로 남긴다는 점을 제외하면 backsapce 와 동일한 기능을한다. (활성화 단축키 ^s ) |
| ^O | 선택된 torrent 를 위한 새로운 다운로드 디렉토리를 설정한다. torrent 가 아직 활성화된 상태가 아닐때만 동작한다. |
| ^s | 다운로드 시작. (완료 직전 이라면 해싱 작업을 먼저 시작한다) |
| ^d | 다운로드 중지. 혹은 정지된 다운로드를 제고 |
| ^r | 다운로드와 무관하게 torrent 의 해싱 체크를 시작한다. |
| a/s/d | 1/5/50 KB 씩 업로드 대역폭 증가. |
| z/x/c | 1/5/50 KB 씩 업로드 대역폭 감소. |
| A/S/D | 1/5/50 KB 씩 다운로드 대역폭 증가. |
| Z/X/C | 1/5/50 KB 씩 다운로드 대역폭 감소. |
| ^q | 프로그램 종료. (중복으로 명령을 내리면 트래커와 주고받는 신호를 무시하고 종료한다.) |
| up/down | torrent 선택 |
| left | 이전 화면으로 복귀 |
| right | 다운로드 뷰로 전환 |
| ^r | 토런트의 해시 체크 시작 |
| +/- | 토런트의 우선 순위 변경 |
| l | 로그 보기. Space 키로 종료 |
| M-1 | 모든 다운로드 내역 보기 |
| M-2 | 이름 순으로 모든 다운로드 내역 보기 |
| M-3 | 시작된 다운로드 내역 보기 |
| M-4 | 정지된 다운로드 내역 보기 |
| M-5 | 완료된 다운로드 내역 보기 |
| M-6 | 해싱중인 다운로드 내역 보기 |
| right | 토런트 파일 리스트로 전환 |
| left | 메인 뷰로 전환 |
| 1/2 | 최대 업로드 조절 |
| 3/4 | 최소 피어수 조절 |
| 5/6 | 최대 피어수 조절 |
| o | 트리커 리스트 출력.스페이스바로 한 구룹내의 트래커들을 순환. * 키를 이용해 선택된 트래커 의 활성화 설정을 토글 |
| p | 피어와 토런트 정보를 보기 |
| t/T | 트래커 요청 시작. 대문자 T를 이용하면 최소 요청 간격을 무시하고 강제로 요청을 할 수 있음. |
| u | 전송 내역 보기 |
| i | Chunk 빈도 보기 |
| left | 다운로드 뷰로 전환 |
| space | 파일 우선 순위 변경 |
| * | 모든 파일의 우선 순위 변경 |
※ 이 내용은 임대영 님의 강좌를 스크랩한 내용입니다.
GNU Make 강좌
임대영 RAXIS@hitel.net
v1.0, 1997년 8월 28일
--------------------------------------------------------------------------------
이 글에서는 컴파일 과정과 같이 반복되는 작업을 효과적으로 처리하는 GNU-Make에 대해서 설명한다.
--------------------------------------------------------------------------------
1. make (만든다 ?)
1.1 make 유틸리티
1.2 make의 필요성
2. 간단한 Makefile
2.1 Makefile 의 내부 구조
2.2 Makefile 예제
2.3 매크로의 사용
2.4 레이블의 사용
3. 매크로(Macro) 와 확장자(Suffix) 규칙
3.1 매크로란 무엇인가? (What is Macro)
3.2 미리 정해져 있는 매크로 (Pre-defined macro)
3.3 확장자 규칙 (Suffix rule)
3.4 내부 매크로 (Internal macro)
4. Makefile를 작성할 때 알면 좋은 것들
4.1 긴 명령어를 여러 라인으로 표시하기
4.2 확장자 규칙의 이용 (Use suffix rule !!)
4.3 매크로 치환 (Macro substitution)
4.4 자동 의존 관계 생성 (Automatic dependency)
4.5 다중 타겟 (Multiple target)
4.6 순환 make (Recursive MAKE)
4.7 불필요한 재컴파일 막기
5. make 중요 옵션 정리
6. Makefile 작성의 가이드라인
7. Makefile의 실제 예제
7.1 프로그램 제작에 쓰일 수 있는 Makefile
7.2 라이브러리와의 링크가 필요한 필요한 Makefile
7.3 LaTeX에서 쓰일 수 있는 Makefile
8. make 수행 시에 나타나는 에러들
1. make (만든다 ?)
1.1 make 유틸리티
영어 사전에서 make란 뜻은 누구나 알듯이 '만들다'라는 뜻의 동사이다. 그럼 make유틸리티는 왜 이름이 make인지 알 필요가 있을 것 같다. man으로 찾아보면 make에 대해 다음과 같이 설명하고 있다.
make - GNU make utility to maintain groups of programs
The purpose of the make utility is to determine automatically which pieces of a large program need to be recompiled, and issue the commands to recompile them.
우리말로 하면 make는 프로그램 그룹을 유지하는데 필요한 유틸리티이다. make유틸리티의 목적은 프로그램 그룹 중에서 어느 부분이 새롭게 컴파일되어야 하는지를 자동적으로 판단해서 필요한 커맨드(gcc따위)를 이용해서 그들을 재컴파일 시킨다고 되어 있다.
make는 일련의 프로그램 개발에만 쓰이지 않고, 컴파일러처럼 일종의 명령어 방식으로 처리되는 모든 곳에서 쓰일 수가 있다. 가령 LaTeX와 같은 경우도 .tex 파일에서 .dvi 파일을 만들고 다시 .ps 파일로 만드는 과정을 make를 사용해서 간단하게 만들 수가 있다.
쉽게 말하면 다음과 같은 경우에 make를 쓰면 유리합니다.
입력 파일이 바뀌면 자동적으로 결과 파일이 바뀌기를 원할 때, 기왕이면 좀 지능적으로 일이 수행되기를 바랄 때 말입니다.
위의 LaTeX 파일처럼 자동적으로 프로그램이 수행이 되기를 바랄 때... (배치(batch)의 개념이죠)
=> make는 위의 두 가지 개념을 모두 포함하고 있다고 봅니다. 보통 리눅스 프로그램에서는 make all을 입력하면 자세한 내막은 모르지만 자기가 알아서 모든 일을 다하죠... 그 다음으로 make install만 입력하면 되구요... 히...
GNU make는 보통 GNUmakefile, Makefile, makefile 중에서 하나가 있으면 그 파일을 읽게 된다. 하지만 일반적으로 Makefile을 추천하게 되는데, 그 이유는 우선 GNUmakefile은 기존의 make에서 인식을 못한다는 단점이 있고, makefile은 보통 소스 파일에 묻혀서 잘 안보이게 되기 때문이다.
Makefile은 make가 이해할 수 있도록 일종의 쉘 스크립트 언어같이 되어 있다(makefile database라 하기도 한다). 이 파일에는 결과 파일을 생성시키기 위한 파일들간의 관계, 명령어 등을 기술하고 있는데 이 강좌의 주된 목적이 바로 Makefile의 작성에 있다.
1.2 make의 필요성
우선은 make의 사용을 프로그램 개발과 유지 쪽으로 국한시키기로 한다. 보통 라인 수가 많아지면 여러 개의 파일로 나누어 (모듈로 나누어) 개발을 하게 된다. 이들은 알게 모르게 서로 관계를 가지고 있는데, 어느 하나를 필요에 의해 바꾸게 되었을 때 그 파일에 있는 함수를 이용하는 다른 파일도 새롭게 컴파일되어야 한다.
하지만 파일 수가 많은 경우 이를 일일이 컴파일을 하게 될 때, 그 불편함과 함께 컴파일하지 않아도 될 것도 컴파일을 하게 될 수도 있고, 컴파일해야 할 것도 미처 못하게 되는 경우가 있다(링크 에러의 원인이 되기도 하는데 에러의 원인을 제대로 찾기가 힘이 든다).
앞에서도 얘기했듯이 이런 상황에서 지능적으로 관계 있는 것만 새롭게 갱신을 할 필요가 있을 때 make파일은 빛을 발하게 된다.
2. 간단한 Makefile
2.1 Makefile 의 내부 구조
Makefile은 기본적으로 아래와 같이 목표(target), 의존 관계(dependency), 명령(command)의 세개로 이루어진 기분적인 규칙(rule)들이 계속적으로 나열되어 있다고 봐도 무방하다. make가 지능적으로 파일을 갱신하는 것도 모두 이 간단한 규칙에 의하기 때문이다.
--------------------------------------------------------------------------------
target ... : dependency ...
command
...
...
--------------------------------------------------------------------------------
여기서 목표(target) 부분은 명령(command)이 수행이 되어서 나온 결과 파일을 지정한다. 당연히 목적 파일(object file)이나 실행 파일이 될 것이다.
명령(command)부분에 정의된 명령들은 의존 관계(depenency)부분에 정의된 파일의 내용이 바뀌었거나, 목표 부분에 해당하는 파일이 없을 때 이곳에 정의된 것들이 차례대로 실행이 된다. 일반적으로 쉘에서 쓸 수 있는 모든 명령어들을 사용할 수가 있으며 bash에 기반한 쉘 스크립트도 지원한다.
=> 참고: 참고로 목표 부분에는 결과 파일만 올 수 있는 것이 아니고, 보통 make clean 에서와 같이 간단한 레이블(label) 기능을 제공하기도 한다.
=> 명령 부분은 꼭 TAB 글자로 시작해야 한다. 그냥 빈칸 등을 사용하면 make 실행 중에 에러가 난다. 명심하세요. make가 명령어인지 아닌지를 TAB 가지고 구별하기 때문이죠.
2.2 Makefile 예제
간단한 Makefile을 만들어 본다. 우리가 만들려고 하는 프로그램은 main.c read.c write.c로 구성되어 있고 모두 io.h라는 헤더 파일을 사용한다고 가정한다. (흐... 구성을 간단하게 합시다.) 이들을 각각 컴파일해서 test 라는 실행 파일을 생성시킨다.
% gcc -c main.c
% gcc -c read.c
% gcc -c write.c
% gcc -o test main.o read.o write.o
위의 방식은 make를 쓰지 않고 그냥 명령어를 주는 방식이다. 파일의 수가 작아서 오히려 더 간단하게 보일 수 있으나, 파일이 100개정도 된다고 가정하면... 아찔...
그리고, 아래는 위와 똑같은 일을 수행하는 Makefile의 내용이다.
Makefile예제 1
--------------------------------------------------------------------------------
test : main.o read.o write.o
gcc -o test main.o read.o write.o
main.o : io.h main.c
gcc -c main.c
read.o : io.h read.c
gcc -c read.c
write.o: io.h write.c
gcc -c write.c
--------------------------------------------------------------------------------
(대충 알아보시겠어요? 참 TAB문자 쓰는 것 있지 마세요)
make는 Makefile의 내용을 보고, 내부적으로 어떻게 파일들이 의존하고 있는지 조사한다. 위의 Makefile을 바탕으로 의존 관계를 그림으로 나타내 보면 아래와 같다.
--------------------------------------------------------------------------------
+---------------+
| io.h |
+------+--------+
|
+---------+----------+
| |
+--------------+ | +------+-------+ | +--------------+
| main.c | | | read.c | | | write.c |
+------+-------+ | +------+-------+ | +------+-------+
| | | | |
+------+-------+ | +------+-------+ | +------+-------+
| main.o +--+--| read.o | +--+ write.o |
+------+-------+ +------+-------+ +------+-------+
| | |
+--------------------+--------------------+
+-------|-------+
| test |
+------+--------+
--------------------------------------------------------------------------------
(텍스트 기반이라서 그림 그리기가 꽤 어렵네요. =)
위의 그림에서 보면 test 가 만들어지기 위해서는 main.o read.o write.o가 필요하게 각각의 목적 파일들은 모두 자신의 소스 파일과 io.h 에 의존함을 알 수가 있다.
가령 main.c를 고쳤다고 생각한다면 main.o가 컴파일되어 다시 생기고, test 도 다시 링크되어 갱신된다. 만약 io.h가 바뀌었다고 가정하면 모든 파일들이 컴파일되어서 목적 파일이 생기고, 그것들이 링크가 되어 test가 생긴다.
위와 같이 파일들을 구성한 다음 Makefile을 실행시켜 보자. Makefile의 실행은 그냥 make라고만 치면 된다.
% make
gcc -c main.c
gcc -c read.c
gcc -c write.c
gcc -o test main.o read.o write.o <- OK
=> 참고: 그냥 테스트에 불과하기 때문에 read.c writec io.h 는 모두 내용 없이 파일만 만들어 두기로 하고, main.c 에 간단히 printf 함수만 적어 봅시다. 정말 위와 같이 됨을 실감할꺼예요... 신기하게...
2.3 매크로의 사용
간단한 매크로 기능을 사용해 보자. main.o read.o write.o라는 것을 OBJECTS 라는 매크로로 바꾸는 것이 아래의 예제 2에 나와 있다.
Makefile예제 2
--------------------------------------------------------------------------------
OBJECTS = main.o read.o write.o
test : $(OBJECTS)
gcc -o test $(OBJECTS)
main.o : io.h main.c
gcc -c main.c
read.o : io.h read.c
gcc -c read.c
write.o: io.h write.c
gcc -c write.c
--------------------------------------------------------------------------------
위에서 보다시피 매크로는 그냥 프로그램 짤 때와 같이 사용해서 값을 대입한다. 대신 사용할 때는 반드시 $(..) 안에 넣어서 사용한다. 매크로 치환을 위한 특수한 방법이 아닐까... 히... 매크로의 사용법은 위와 같이 간단하므로 다양하게 정의해서 사용할 수 있다. 매크로에 대한 자세한 설명은 다음 장에서 언급하기로 한다.
2.4 레이블의 사용
목표 부분에 해당하는 부분이 그냥 레이블과 같이 사용될 수도 있다고 이미 설명하였다. 예제 2 에다가 목적 파일들을 모두 삭제하는 명령어를 추가하기로 한다.
Makefile예제 3
--------------------------------------------------------------------------------
OBJECTS = main.o read.o write.o
test : $(OBJECTS)
gcc -o test $(OBJECTS)
main.o : io.h main.c
gcc -c main.c
read.o : io.h read.c
gcc -c read.c
write.o: io.h write.c
gcc -c write.c
clean :
rm $(OBECTS)
--------------------------------------------------------------------------------
레이블로 사용될 때는 당연히 의존 관계 부분은 없어도 된다. 그리고 clean을 실행시키려면 아래와 같이 한다.
% make clean
rm main.o read.o write.o <- OK
다음 장에서는...
지금까지는 Makefile의 간단한 예제를 가지고 무엇을 할 수 있는지 대충 알아보았습니다. 예제를 많이 쓰다 보니까 내용이 불어나게 됐군요. RCS와 달리 make는 한번 알고 있으면 정말 유용한 유틸리티입니다.
다음 장에서는 본격적으로 Makefile의 구성및 그 사용법을 자세히 알아 보고자 합니다. 그냥 일반적으로 Makefile 사용하시려면 오늘 한 것에 몇 가지만 더 알고 계시면 됩니다. 계속 예제 중심으로 이해가 잘되도록... 그럼 계속 봐주시면 감사.
3. 매크로(Macro) 와 확장자(Suffix) 규칙
3.1 매크로란 무엇인가? (What is Macro)
앞에서 매크로에 대해서 대충 언급을 했다. 프로그램을 짜본 사람이나 로터스, 한글, 엑셀 등의 모든 패키지에서 매크로라는 것을 사용하게 된다. 은연중에 매크로의 정의는 대충 짐작하고 있을 것이다. 이미 알고 있는바와 같이 매크로는 특정한 코드를 간단하게 표현한 것에 지나지 않는다. Makefile에서 사용되는 매크로는 비교적 그 사용법이 간단하기 때문에 금방 익혀서 사용할 정도가 된다.
매크로의 정의는 프로그램을 작성할 때 변수를 지정하는 것처럼 하면 된다. 그리고, 매크로를 사용하기 위해서는 $(..)을 이용하면 된다. 아래는 매크로의 간단한 예제이다.
=> 참고: 매크로의 사용에서 ${..}, $(..), $..를 모두 사용할 수 있습니다. 그러나 대부분의 책에서는 $(..) 을 사용하라고 권하는군요.
Makefile예제 4
--------------------------------------------------------------------------------
OBJS = main.o read.o write.o
test : $(OBJS) <- (1)
gcc -o test $(OBJS)
..........
--------------------------------------------------------------------------------
첫 번째 장에서 다루었던 예제와 거의 비슷하다. 매크로는 사실상 복잡한 것을 간단하게 표시한 것에 지나지 않는다. (1) 번을 매크로를 안 쓰고 표현한다면 아마 아래와 같이 될 것이다.
Makefile예제 5
--------------------------------------------------------------------------------
test : main.o read.o write.o
gcc -o test main.o read.o write.o
--------------------------------------------------------------------------------
=> 참고: 예제 5가 더 쉽지 않느냐고 반문하는 사람은 매크로의 위력을 잘 모르는 사람입니다. 거의 모든 소프트웨어에서 매크로를 지원하는 이유를 한번 잘 생각해 봅시다. 예제 4 의 (1)부분이 이해하기 난해하다고 하실 지는 모르겠지만, 대충 형식이 정해져 있기 때문에 조금만 익숙해지면 오히려 더 편할 겁니다.
make에 관해 설명한 책에 다음과 같은 명언(?) 이 나온다.
Macro makes Makefile happy. (매크로는 Makefile 을 기쁘게 만든다.)
이 말은 Makefile을 작성함에 있어 매크로를 잘만 이용하면 복잡한 작업도 아주 간단하게 작성할 수 있음을 말해 주는 말이 아닐까 생각한다. 매크로에 대해서는 더 이상 말할 것이 없다. (너무 간단하죠 ?) 이제 남은 것은 여러분들이 자신의 매크로를 어떻게 구성하느냐이다. 어떤 것을 매크로로 정의해야 할지는 여러분들의 자유이며, 나중에 전반적인 지침을 설명할 것이다.
3.2 미리 정해져 있는 매크로 (Pre-defined macro)
여러분들보다 머리가 약간 더 좋은 사람들이 make 라는 것을 만들면서 미리 정해 놓은 매크로들이 있다. 'make -p' 라고 입력해 보면 make에서 미리 세팅되어 있던 모든 값들(매크로, 환경 변수(environment) 등등)이 엄청 스크롤 된다. 이 값들을 보고 미리 주눅 들 필요는 없다. 어차피 대부분의 내용들은 우리가 재정의 해주어야 하기 때문에 결론적으로 말하면 우리가 모두 작성한다고 생각하는 것이 마음이 편하다.,.
아래에는 대부분이 UNIX 계열의 make에서 미리 정해져 있는 매크로들 중에 몇 가지만 나열해 본 것이다.
Predefined Macro 예제 6
--------------------------------------------------------------------------------
ASFLAGS = <- as 명령어의 옵션 세팅
AS = as
CFLAGS = <- gcc 의 옵션 세팅
CC = cc (= gcc)
CPPFLAGS = <- g++ 의 옵션
CXX = g++
LDLFAGS = <- ld 의 옵션 세팅
LD = ld
LFLAGS = <- lex 의 옵션 세팅
LEX = lex
YFLAGS = <- yacc 의 옵션 세팅
YACC = yacc
MAKE_COMMAND = make
--------------------------------------------------------------------------------
=> 참고: 직접 make -p를 해서 한번 확인해 보세요. 과연 make는 내부적으로 어떤 변수들을 사용하고 있는지 알아봅시다. 매크로는 관습적으로 대문자로 작성되니까 이점에 유의해서 보세요. make는 쉘상에서 정의한 환경 변수값들을 그대로 이용한다는 것을 알고 계시기