argv[0]

main 함수의 인자인 argv 배열의 첫번째 항목에는 command line에서 입력된 첫번째 인자, 즉 수행되는 프로그램의 이름이 들어갑니다.1 일반적으로 이 argv[0]는 잘 사용하지 않지요. 간혹 usage 출력에 사용되기는 합니다만...

그런데 예전에 Embedded Linux 교육을 받으면서 보니 이 argv[0] 항목을 사용한 BusyBox라는 재밌는 프로젝트가 있더군요.

Embedded 시스템들은 일반 시스템들보다 컴퓨팅 파워나 메모리 용량, 디스크 크기(플래시 메모리)에 있어서 제약이 많습니다. 그리고 하나의 a.out에는 그 프로그램이 실행해야 할 내용외에도 수 kb의 내용이 오버헤드로 포함됩니다. 이 오버헤드를 줄이게 되면 좀 더 작은 디스크나 플래시 메모리를 사용할 수 있게 되죠.

이 오버헤드를 줄이기 위해서 하나의 a.out에 다양한 프로그램의 기능을 집어넣은 것이 BusyBox입니다. 이렇게 만들어진 busybox라는 a.out은 다양한 이름으로 link가 됩니다. 제가 교육받으면서 만들었던 시스템의 /usr/bin 디렉토리에서 ls -l 한 결과를 예로 보면 다음과 같습니다.

 
lrwxrwxrwx    1 507      507            17 Mar 28  2007 [ -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 ar -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 awk -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 basename -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 bunzip2 -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 bzcat -> ../../bin/busybox
lrwxrwxrwx    1 507      507             3 Mar 28  2007 captoinfo -> tic
lrwxrwxrwx    1 507      507            17 Mar 28  2007 chvt -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 clear -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 cmp -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 cut -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 dc -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 deallocvt -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 dirname -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 du -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 env -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 expr -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 find -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 free -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 ftpget -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 ftpput -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 head -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 hexdump -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 id -> ../../bin/busybox
lrwxrwxrwx    1 507      507             3 Mar 28  2007 infotocap -> tic
lrwxrwxrwx    1 507      507            17 Mar 28  2007 install -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 killall -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 last -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 loadfont -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 logger -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 logname -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 md5sum -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 mesg -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 mkfifo -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 nc -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 nslookup -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 od -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 openvt -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 passwd -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 printf -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 readlink -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 renice -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 reset -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 seq -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 setkeycodes -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 sort -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 strings -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 tail -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 tee -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 telnet -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 test -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 tftp -> ../../bin/busybox
-rw-r--r--    1 root     root            0 Feb 28  2007 tic
lrwxrwxrwx    1 507      507            17 Mar 28  2007 time -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 top -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 tr -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 traceroute -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 tty -> ../../bin/busybox
-rwxr-xr-x    1 507      507        234861 Feb 28  2007 udevinfo
-rwxr-xr-x    1 507      507        358008 Feb 28  2007 udevtest
lrwxrwxrwx    1 507      507            17 Mar 28  2007 uniq -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 unzip -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 uptime -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 uudecode -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 vlock -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 wc -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 wget -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 which -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 who -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 whoami -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 xargs -> ../../bin/busybox
lrwxrwxrwx    1 507      507            17 Mar 28  2007 yes -> ../../bin/busybox
 

그리고 구현 방법을 보면... 실제 코드는 조금 다르지만 원리는 간단히 argv[0]를 사용하는 것이죠.

 
...
else if (strcmp("ls", argv[0]) == 0) do_ls();
else if (strcmp("telnet", argv[0]) == 0) do_telnet();
...
 

아이디어 좋죠? :-)


  1. int main(int argc, char* argv[]) [back]

뉴욕의 프로그래머

뉴욕의 프로그래머라는 책을 읽었습니다. 이미 개발자들을 위한 재밌는 책들을 몇 권 쓰신 임백준씨의 책인데 기대보다는 좀... 장르가 애매해서일까요? 그래도 이런 책이 나왔다는 것 자체가 일단 좋은 일이라고 생각합니다. :-)

각 장의 처음에는 몇 개의 말들이 인용되고 있는데 그 중에서 다음 것들에 대해 생각해 보았습니다.

좋은 판단은 경험에서 나온다. 그리고 경험은 나쁜 판단에서 나온다. - 배리 르패트너

컴퓨터 사이언스를 가르치는 교육이 어떤 사람을 전문적인 프로그래머로 만들지 못하는 것은 붓질과 채색 방법을 가르치는 교육이 어떤 사람을 전문적인 화가로 만들지 못하는 것과 같다. - 에릭 레이먼드

첫번째 말에 따르면 좋은 판단, 그리고 그것보다 더 좋은 판단을 하기 위해서는 나쁜 판단을 계속 저질러봐야 한다는 얘기가 됩니다. 결국 모르는 것을 부끄러워하지 않고 지속적으로 배우고 공부해야 한다는 얘기인데... 말처럼 쉽지 않은 것 같습니다. 특히 부끄러워하지 않는 것이요. :-P

두번째 말은 다른 곳에서도 많이 들었던 것인데 책에서 읽고 이런 생각을 했습니다. 왜 붓질과 채색 방법을 가르치는 교육만 받은 사람은 전문적인 화가가 될 수 없는데 전자의 경우에는 전문적인 프로그래머가 될 수 있을까?1

제가 생각한 차이점은 붓질과 채색 방법만을 배운 화가의 그림은 진짜 화가의 그림과 쉽게 수준이 비교가 되지만 컴퓨터 사이언스를 배운 프로그래머가 만든 프로그램은 전문가가 만든 것과 쉽게 비교가 되지 않는다는 점입니다. 물론 사용하면서 발생하는 문제들과 문제가 처리되는 방법, 유지보수 용이성등과 같이 세부적인 항목들을 꼼꼼히 검사하면 전문가가 만든것과 확연히 구분되겠지만 일단 기능이 동작하느냐는 관점에서 보면 큰 차이가 나지 않을 수 있죠.2

더 큰 문제는 전문 화가의 경우에는 옆에 있는 무늬만 화가가 저질러 놓은 채색과 붓질을 신경쓸 필요가 없지만 프로그래머는 그럴 필요가 있다는 점, 게다가 이런 수정들이 훨씬 더 스트레스를 많이 준다는 점이죠.

.
.

책에 나온 말들 중 가장 재밌었던 말은 따로 있습니다. 리치 쿡이란 분... 많이 당하셨나봐요.

오늘날의 프로그래밍은 소프트웨어 엔지니어와 우주가 경쟁을 벌이는 양상으로 진행되고 있다. 소프트웨어 엔지니어는 바보조차 쉽게 사용할 수 있는 프로그램을 만들기 위해 노력하고, 우주는 바보를 만들어낸다. 적어도 지금까지는 우주가 승리를 거두고 있다. - 리치 쿡

여기서의 문제는 윗글에 나오는 바보들이 소프트웨어 엔지니어보다 위에 있다는 점이겠죠? :-)

아... 그리고 책이 제 기대보다 못 했다고 해서 재미가 없었다는 얘긴 아닌거 아시죠? ;-)


  1. 물론 요새는 프로그래머라는 직업에 대한 인기가 별로 없어 진짜 어설프게 배운 프로그래머의 신규 유입은 거의 없어지지 않았나 싶습니다.[back]
  2. 이 댓글과 비슷한 내용입니다.[back]

6년 9개월

오늘이 6년 9개월동안 다녔던 회사에 출근하는 마지막 날입니다. 이제 이 자리에서 블로그에 글 쓰는 건 마지막이군요. 생각해보면 그렇게 길게 느껴졌던 국민학교 6년보다 여기서 더 오래 생활했네요.

일주일 쉬었다가 다음 회사로 출근합니다.

왠지 섭섭한 마음에 PC 반납하기 전에 한 줄 적어봤습니다. :-(

C++ of the Day #45 - SQLite3 C++ wrapper #3

이번엔 sqlite3++를 소개하는 마지막 시간으로 SQLite3의 extension 기능에 대한 부분을 살펴 보겠습니다.

SQLite3는 사용자가 정의할 수 있는 function과 aggregate들을 통해 기본 기능을 확장할 수 있도록 하고 있습니다.1

예를 들어 A라는 column의 정보를 가지고 B와 C의 데이터 중 선택할 수 있는 cond(A, B, C)라는 function을 사용자가 정의할 수 있다면 복잡한 SQL 구문 대신 다음과 같이 간단하게 사용할 수 있을 것입니다.

 
SELECT cond(A, B, C) FROM ...;
 

그리고 특정 규칙을 적용한 filter를 사용하여 해당 filter에 의해 선택된 항목들의 개수만 세고 싶다면 다음과 같은 aggregate를 만들어 사용할 수도 있습니다. 아래 코드에서는 count_of가 사용자가 정의한 filter라고 가정합니다.

 
SELECT count_of(A, B, C) FROM ...;
 

이처럼 function이나 aggregate는 SQLite3가 기본적으로 제공하지 않거나 SQL만으로 작성하기에는 복잡한 기능들을 사용자가 정의할 수 있도록 해줍니다.

그럼 sqlite3++을 사용하여 간단하게 이런 function이나 aggregate를 만들 수 있는 방법을 알아보겠습니다.

먼저 ext::function의 경우에는 function pointer나 functor, boost::binder 그리고 boost::lambda등을 function으로 등록하여 사용할 수 있습니다. 예를 들면 다음과 같습니다.

 
int plus100(int val) {
  return val + 100;
}
// ...
sqlite3pp::ext::function func(db);
func.create<int ()>("plus100", &plus100);
 
// SELECT plus100(id) FROM ...;
 

위의 plus100()은 단순히 입력된 값에 100이라는 값을 더하여 리턴하는 함수입니다. 등록은 ext::function 객체의 create 함수를 사용하며 template 인자의 문법은 boost::function과 유사하게 function type이 사용됩니다. :-)

이처럼 간단한 코드는 boost::lambda로도 등록하여 사용할 수 있습니다.

 
using namespace boost::lambda;
func.create<int ()>("plus100", _1 + 100);
 

물론 다음과 같이 입력과 출력의 타입은 서로 다를 수 있으며 입력의 개수는 현재 0~5개까지 지원합니다.

 
int strlen_3(std::string const& s1, std::string const& s2, std::string const& s3)
{
  return (s1 + s2 + s3).size();
}
// ...
func.create_function<int (std::string, std::string, std::string)>("strlen_3", &strlen_3);
 
// SELECT strlen_3(A, B, C) FROM ...;
 

위에서 본 코드와 같이 ext::function은 비교적 간단히 사용할 수 있습니다만 ext::aggregate의 경우에는 약간 복잡합니다. ext::aggregate는 하나의 함수가 아니라 해당 입력값을 하나씩 받아들이는 step()함수와 모든 입력이 끝난 뒤에 리턴값을 처리하는 finish()함수로 구성됩니다.

ext::function보다 복잡하다고는 하지만 실제 사용 방법은 간단하게 step()함수와 finish()함수를 가지는 클래스를 구현하는 것입니다.

 
struct strlen_all
{
  strlen_all() : n_(0) {
  }
  void step(std::string const& s) {
    n_ += s.size();
  }
  int finish() {
    return n_;
  }
  int n_;
};
// ...
sqlite3pp::ext::aggregate aggr(db);
aggr.create<strlen_all, std::string>("strlen_all");
 
// SELECT strlen_all(name) FROM table;
 

위의 ext::aggregate는 해당 column에 있는 문자열의 길이의 합을 리턴합니다.

등록시에 사용되는 create 함수의 template 인자의 구성은 가 됩니다. aggregate의 리턴 타입은 자동으로 finish() 함수의 리턴 타입으로 결정됩니다. 여기서도 입력의 개수는 현재 0~5개까지로 구현되어 있습니다.

이것으로 구현한 sqlite3++의 사용 방법에 대한 설명을 마치겠습니다. 오랫만에 코드에 template을 좀 썼더니 재밌네요. 기회가 되면 sqlite3++에 사용된 template 구현 방법에 대해 설명해 보도록 하겠습니다. :-)

sqlite3pp - Google Code에서 코드들과 간단한 사용 예제들을 보실 수 있습니다.


  1. 이외에 collation sequence라는 확장 기능도 있습니다만 sqlite3++에서는 지원하지 않습니다. 아직까지는...[back]

C++ of the Day #44 - SQLite3 C++ wrapper #2

지난 번에 이어 구현중인 SQLite3 C++ wrapper에 대해 살펴보겠습니다. 먼저 이 wrapper의 이름을 sqlite3++로 결정했습니다. 물론 코드안에서와 같이 특수 문자를 사용할 수 없는 경우에는 sqlite3pp라는 이름을 사용합니다.

지난번 글의 내용에서 변경된 사항들에 대해 먼저 알아보죠.

먼저 몇몇 클래스와 함수들의 이름을 알기 쉽게 바꾸었습니다. 다음이 바뀐 이름들의 목록입니다.

  1. connection --> database
  2. connection::open --> database::connect
  3. connection::close --> database::disconnect
  4. command::step --> command::execute
  5. command::finalize --> command::finish

클래스 상속 관계도 수정이 되었으나 사용자들에게 보이는 변경 사항은 없습니다.

그리고 command::step() 함수의 성공시에 SQLITE_DONE(101)이 출력되던 것을 다른 함수들과의 일관성을 위해 command::execute() 함수는 SQLITE_OK(0)를 리턴하도록 수정하였습니다. 이제 sqlite3++에서 함수의 성공 여부는 리턴값이 0인지를 확인하면 됩니다.

다음으로 SQLite3에서 지원하는 busy_handler, commit_hook, rollback_hook, update_hook, authorizer_handler와 같은 callback 함수들을 등록하는 인터페이스를 database 클래스에 추가하였습니다. 사용하는 방법은 다음과 같이 몇가지가 있습니다.1

먼저 일반 함수 포인터를 사용하는 방법입니다.

 
int handle_authorize(int evcode, char const* p1, char const* p2, char const* dbname, char const* tvname) {
  cout << "handle_authorize(" << evcode << ")" << endl;
  return 0;
}
// ...
db.set_authorize_handler(&handle_authorize);
 

이런 callback 함수에 user data가 필요한 경우에는 이를 클래스로 만들어 멤버 함수 포인터를 등록할 수도 있습니다. 아래 코드에서는 boost::bind를 사용하여 멤버 함수 포인터를 전달하고 있습니다.

 
struct handler
{
  handler() : cnt_(0) {}
 
  void handle_update(int opcode, char const* dbname, char const* tablename, int64_t rowid) {
    cout << "handle_update(" << opcode << ", " << dbname << ", " << tablename << ", " << rowid << ") - " << cnt_ << endl;
    ++cnt_;
  }
  int cnt_;
};
// ...
handler h;
db.set_update_handler(bind(&handler::handle_update, &h, _1, _2, _3, _4));
 

물론 일반 functor를 만들어 사용할 수도 있습니다.

 
struct rollback_handler
{
  void operator()() {
    cout << "handle_rollback - " << cnt << endl;
    ++cnt;
  }
  int cnt;
};
// ...
db.set_rollback_handler(rollback_handler());
 

게다가 위의 경우처럼 함수의 내용이 간단한 경우 boost::lambda를 사용할 수도 있죠. :-)

 
db.set_rollback_handler(cout << constant("handle_rollback\n"));
 

위의 rollback_handler는 리턴값이 void라 위와 같이 쉽게 사용이 가능하지만 commit_handler와 같이 리턴값이 있는 경우에는 다음과 같이 boost::lambda의 , (comma) operator를 사용하여 리턴값을 지정해 주어야 합니다. 이때는 함수 인자로 인식하지 않도록 괄호를 추가로 사용해야 합니다.

 
db.set_commit_handler((cout << constant("handle_commit\n"), 0));
 

이 기능외에도 하나의 database 세션에서 다른 db 파일을 연결해서 쓸 수 있는 attach/detach 기능이나 enable_shared_cache() 기능을 위한 인터페이스도 추가되었습니다.

현재까지 작성된 코드들을 Google Code에 등록해 두었습니다. 다음 링크를 통해 현재 구현중인 코드를 확인할 수 있습니다.

http://sqlite3pp.googlecode.com/svn/trunk/

다음 글에서는 sqlite3++의 마지막 시간으로 SQLite3에서 제공하는 extension에 대한 C++ wrapping에 대해 작성해 보도록 하겠습니다. :-)


  1. boost::function을 사용하여 다양한 타입으로 등록할 수 있도록 구현하였습니다.[back]