VMA

Posted 2006. 9. 23. 04:02
ARM에는 취직 자리가 없나 싶어서 기웃거리다가 브로셔만 몇 개 읽었습니다. ARM은 칩 만드는 회사지만, 자바 쪽으로도 이런 저런 제품을 많이 내놓고 있더군요. 물론 ARM 칩을 자바에 특화시키는 쪽이긴 하죠. VMA TECHNOLOGY KIT FOR THE JAVA™ PLATFORM 같은 경우 자바 인터프리터 루프를 ARM 프로세서에 특화된 어셈블리 구현으로 대체시켜서 최대 5배 이상의 성능 향상을 노리는 제품입니다. CLDC VM을 위한 VTK-K와 CDC VM을 위한 VTK-C가 모두 있군요.

JSR 292

Posted 2006. 9. 20. 15:44
10월달 개발자 잡지 마이크로소프트웨어JSR 292 자바 플랫폼에서의 동적 타입 언어 지원(Dynamically Typed Language on the Java Platform)에 대한 글을 썼습니다.

2006년 자바원(JavaOne) 컨퍼런스의 특징 중에 하나는 스크립트 언어 지원에 대한 이야기가 많았다는 점이었다. JRuby, Groovy에 대한 세션뿐만 아니라 Java 6 무스탕에서의 스크립트 지원까지 다양한 발표가 있었다. 그 중에 하나가 JSR292 자바 플랫폼에서의 동적 타입 언어 지원(Dynamically Typed Language on the Java Platform)이다. 동적 타입 언어에는 여러 종류가 있지만 JSR292는 점점 인기를 얻고 있는 파이썬, 루비, Groovy 같은 스크립트 언어를 대상으로 하고 있다. 그동안 JVM은 자바 언어뿐만 아니라 여러 언어의 VM으로 사용되긴 했지만, 썬마이크로시스템즈가 별다른 지원을 한 적은 없었다는 점에서 이번 JSR292의 행보는 주목할 만하다. 자바 플랫폼이 동적 타입 언어를 어떻게 지원할 예정인지 살펴보자.


저작권 때문에 여기 올리지는 못하고, 궁금하신 분은 10월호 책을 사서 보세요 ^^...

Mixin

Posted 2006. 9. 20. 03:42
믹스인(mixin)은 일반적으로 스크립트 언어에서 다중 상속을 대신하는 방법으로 자주 쓰인다. 일반적으로 다중 상속을 사용하는 이유가 코드를 상속받기 위해서인데, Mixin은 클래스 계층을 변경하지 않고 코드만 포함시키는 방법을 제공하기 때문이다. 믹스인을 제공하는 대표적인 스크립트 언어가 루비인데, 다음 루비 프로그램을 살펴보자.


module Debug

  def whoAmI?

    "#{self.type.name} (\##{self.id}): #{self.to_s}"

  end

end

class Phonograph

  include Debug

  # ...

end

class EightTrack

  include Debug

  # ...

end

ph = Phonograph.new("West End Blues")

et = EightTrack.new("Surrealistic Pillow")

ph.whoAmI?       »       "Phonograph (#537766170): West End Blues"

et.whoAmI?       »       "EightTrack (#537765860): Surrealistic Pillow"

[http://www.rubycentral.com/book/tut_modules.html에서 발췌]


위에서 Phonograph와 EightTrack라는 두 가지 클래스를 정의했는데, 각각은 Debug 모듈을 임포트하고 있다. 루비언어는 클래스 내에서 모듈을 임포트할 경우 모듈 내의 함수가 자동으로 클래스의 메쏘드로 변환된다. 따라서 includeDebug라고 써주는 것만으로 두 클래스는 ph.whoAmI라는 메쏘드가 정의되게 된다. 즉 클래스 계층을 변화시키지 않고 코드만 재활용할 수 있는 것이다.

Job Interview

Posted 2006. 9. 18. 02:27
최근에 일본의 모회사와 면접을 봤는데, 기술 면접으로 다음과 같은 문제가 나왔습니다.

"링크 리스트에서 중간에 있는 엘리먼트를 찾는 효과적인 방법은 무엇인가?"

별로 어려운 문제는 아닌데, 러시아 특유의 영어 발음 때문에 문제를 알아 듣는데 조금 고생을 했습니다.

정답은 두 개의 포인터로 링크 리스트의 헤더를 가르킨 후에 한 포인터의 원소를 2개씩 증가시키고, 다른 한 포인터를 1개씩 증가시킵니다. 2개씩 증가시킨 포인터가 원소의 끝에 다다르면, 그 때 1개씩 증가시킨 포인터는 링크 리스트의 중간 원소를 가리키고 있는거죠.

Algebraic Simplification

Posted 2006. 9. 18. 01:49
algebraic simpification은 직관적인 컴파일러 최적화 기법이다. 예를 들어, 더하기 연산자(+ operator)의 항등원이 0 이라는 사실을 이용해 x + 0 이라는 expression을 x로 치환하는 것을 말한다.

원래 코드

main(int x, int y)
{
x = (y*1 + 0)/1;
}

변환 후에는

main(int x, int y)
{
x = y;
}

Strength Reduction

Posted 2006. 9. 18. 01:19
strength reduction은 타겟 머신에서 무거운(expensive) 연산을 가벼운(cheap) 연산으로 바꾸는 컴파일러 최적화 기법을 말한다. 쉬운 예로, 곱하기 연산을 더하기 연산으로 바꾸는 경우를 들 수 있다.

썬 마이크로시스템즈에서 나온 다음 논문(
Operator Strength Reduction)에 자세한 설명이 있다.

Operator strength reduction is a technique that improves compiler-generated code by reformulating
certain costly computations in terms of less expensive ones. A common case arises in array
addressing expressions used in loops. The compiler can replace the sequence of multiplies generated
by a direct translation of the address expression with an equivalent sequence of additions.


구체적인 예로는

for i from 1 to n do
   z= i*x
  ...
end

위와 같은 코드를 다음과 같이 바꿔 주는 것이다.

z=0;
for i from 1 to n do
  z=z+x
  ..
end
 

Loop Unrolling(unwinding)

Posted 2006. 9. 13. 01:31
직관적으로 쉽게 이해할 수 있는 최적화 중에 하나는 루프를 푸는 loop unrolling이 있다.

Loop: 루프를 100번 돈다
for (i = 0; i < 100; i++)
  g ();

Unrolled loop : 루프를 50번 돈다
for (i = 0; i < 100; i += 2)
{
  g ();
  g ();
}

g()를 두 번 실행하지만 루프의 길이는 반으로 줄였다. 이 최적화의 기본 아이디어는 루프를 실행하는데 드는 오버헤드를 줄이는데 있다. 루프는 loop condition을 검사하고 jump하는 오버헤드가 있기 때문에 루프 수를 줄이면 branching을 줄이고 cache hit ratio를 높여주는 효과가 있다.

물론 다음과 같은 부작용도 있다.

1) 코드 사이즈의 증가

2) 루프가 길어지면서 레지스터 사용량이 증가 (결국 일부가 spill out 됨)

최적화 컴파일러

Posted 2006. 9. 13. 01:20
최적화 컴파일러(Optimzing Compiler)는 전산계의 로켓 과학이므로 많은 개발자의 로망일 것입니다. 앞으로 공부도 할 겸 시간날 때마다 컴파일러 최적화에 대한 글을 써보려고 합니다! 관심 부탁드려요.

Orthogonal Persistence

Posted 2006. 9. 11. 22:04
오늘의 이야기는 Orthogonal Persistence입니다. 예전에 한국 썬에서 JDO에 관한 글을 보니 persistence를 지속성으로 번역해놨던데, 조금 어색하더군요. persistence는 의미상 지속된다는 뜻이 있지만 이 경우에는 파일 시스템이나 데이터베이스에 영구저장이라는 의미가 강하니깐요. 합당한 번역어를 찾기 힘들 때는 원어를 살려쓴다는 원칙을 따라 persistence로 부르겠습니다.

일반적으로 프로그램은 하드 디스크에 저장된 기계어로 된 코드를 말하고, 프로세스는 이런 프로그램을 컴퓨터의 메모리로 로드하여 실행시킨 상태를 말합니다. 따라서 갑자기 전원을 꺼버리면 프로세스는 모두 날라가버리지만 프로그램이나 프로그램이 파일 시스템에 저장한 데이터만 남아있게 되는거죠. 여기서 어떤 데이터를 영속시킬 것인가하는 것은 전적으로 프로그램에게 달려있습니다.

요즘이야 일부 PDA나 스마트폰처럼 메인 메모리를 플래시(flash) 메모리로 사용하는 경우 전원이 나가도 현재 프로세스들이 그대로 보존되기도 하지만, 원래 전원이 나가면 메인 메모리에 로드된 데이터는 모두 날라가게 되죠.

orthogonal persistence는 프로그래밍 언어나 운영체제가 persistence를 보장해주는 경우를 말합니다. 전원이 갑자기 꺼지더라도 운영체제를 다시 부팅하면 원래 상태로 돌아가게 되는거죠. 이런 기능을 구현한 대표적인 운영체제로 존스 홉킨스 대학에서 리서치 프로젝트로 만든 OS인 EROS가 있습니다.

EROS 프로젝트를 처음 접한 것은 메릴랜드 교환학생 때 운영체제 수업을 들으면서였는데, 그 때 교수님이 이렇게 묻더군요. "파일 시스템이라는 게 꼭 필요한가?", "모든 운영체제는 파일 시스템이 있는가?", "파일 시스템의 목표가 일부 데이터를 영속적으로 저장하기 위한 것이라면 절대 데이터가 날라가지고 않고 껐다켜더라도 모든 데이터가 그대로 보존되어 있다면 굳이 파일 시스템에 저장할 필요가 있는가?"

우리는 파일 시스템의 존재를 당연히 여기지만 실제로 EROS는 파일 시스템이 없습니다. 정확히 말하자면 필요가 없습니다. 프로세스가 사용한 모든 데이터는 별도로 파일 시스템을 통해 저장하지 않더라도 OS가 자동으로 저장해주기 때문이죠. 원리를 간략히 요약하면 OS는 메인 메모리를 캐쉬처럼 쓰고 하드 디스크를 메인 메모리처럼 쓰는 것입니다.

상업적으로 널리 성공한 MS 윈도, 리눅스와 같은 기존 운영체제와는 달리, 기존의 관습을 무시하고 순순한 엔지니어링 관점에서 운영체제를 다시 디자인한다면 그 모습은 지금의 OS와는 상당히 다를지도 모르겠습니다.

매트릭스 조직

Posted 2006. 9. 11. 21:22
S사에 면접 갔다가 회사 조직에 관한 설명을 들을 때 매트릭스 조직이라는 용어를 들었습니다. 정확히 어떤 용어인지 [백진원 기자의 경제 플러스]에서 찾아보니,

매트릭스조직이란 매트릭스 매니지먼트(Matrix Management)라는 경영기법에서 나오는 개념인데, 전문 스태프를 분산 배치해서 현지의 일상업무 조언자이자 중앙의 스태프로서 기능을 동시에 수행하도록 만든 조직을 말합니다. 지역과 제품 또는 기능과 프로젝트를 각각의 축으로 행렬식 매트릭스를 구성해 운영하는 조직형태입니다. 전통적인 기능형 조직과 사업부 방식의 조직을 통합해서 조직과 인력을 효율적으로 운영하는 형태입니다.

S사도 지역(한국 지사)라는 하나의 축과 제품이라는 또 하나의 축으로 구성된 매트릭스 조직이더군요.

장점으로는

- 조직의 상하계층을 줄이고
- 상호 연계활동을 확대해
- 내부의 자원을 각 사업부가 효율적으로 사용할 수 있고
- 외부환경에 신속히 대응한다


물론 2명의 상사에게 보고하고 통제를 받는 이중적인 구조이기 때문에 관리자들과 구성원들 모두 원할하게 의사소통할 수 있고 정보와 권한 공유되어야 한다는 선결 조건이 있습니다.

S사의 경우도 한국 지사라는 지역 뿐만 아니라 제품을 기반으로 러시아, 이스라엘, 미국, 한국의 엔지니어가 유기적으로 묶여 있더군요. 덕분에 면접도 서울에서 1차 면접이 있었고, 2차로 이스라엘에서 전화가 걸려온답니다. 매트릭스 조직이라는 게 장단점이 있겠지만, 한국이라는 좁은 지역에서 일하면서 여러 나라에 분포된 직원들과 일한다는 것이 기대되기도 합니다.

이명헌의 경영스쿨에 가보니 조직행동론에 매트릭스 조직을 소개해놨는데, 끝맺음에 다음과 같이 나오더군요.

매트릭스 조직은 80년대 미국 비즈니스스쿨을 중심으로 큰 화제를 모으며 유행되었습니다. 하지만 몇 년 뒤 위와 같은 문제점을 야기하며 조직 안정성을 해치는 것으로 드러나면서 크게 활용되고 있지는 않습니다.


다 좋을 수는 없나봅니다.

프로젝트 범위와 종료조건

Posted 2006. 9. 11. 18:07
방학동안 일했던 M사와 일을 어떻게 끝맺어야할지 모르겠군요. 계약서에 구체적으로 어떤 범위를 언제까지 구현할지 종료 조건은 무엇인지 명시하지 않은 게 화근이 되고 있습니다. 그냥 두루뭉술하게 프로젝트 해주겠다고 해놓았으니 요구사항이 바뀌거나 원래 없던 내용을 해달라고 해도 할 말이 없고, 기간이 명시되어 있지 않으니 한 없이 수정해달라고 해도 할 말이 없고 큰일이네요.

소프트웨어공학 시간에 대충 흘려들었던 내용이 이렇게 중요한 것인줄 이제서야 알았습니다. M사의 경우 외주 관리를 전혀 못하고 있는 게, 소프트웨어 개발을 맡겨놓고 진행상황도 전혀 묻지 않고, 테스트하고 싶다는데 환경 구축을 안 해줫 2-3주씩 일이 밀리고 하네요. 방학 끝으로 작업 시간을 못박아놨아야 하는데 실수입니다. M사 잘못으로 일정이 밀렸는데 이제 학교 다니면서 작업을 해줘야하는 상황이 되어버렸습니다.

이럴 경우엔 어떻게 해야 하는 걸까요?

오늘의 교훈

- 프로젝트의 범위와 기간, 종료 조건, 요구사항 변경시 추가 비용 청구 등을 확실히 계약서에 명기하자.
- 일을 잘못될 가능성을 염두에 두고 전체 프로젝트 금액에서 계약금의 비중을 무조건 높게 부르자.
소프트웨어는 보통 각각 전문화(specialized)된 서비스를 제공하는 여러 서브젝트(혹은 컴포넌트, 프로시저, 함수, 서브루틴, 오브젝트)로 구성된다. 안전한 시스템의 경우 이런 서브젝트가 일을 수행하기 위해서는 이를 사용하는 클라이언트가 적절한 권한을 가지고 있어야 한다. 이 경우 서비스 서브젝트는 대리인이라고 불리는데, 클라이언트에게 위임 받은 권한으로 필요한 일을 해주기 때문이다.

혼동된 대리인(confused deputy)는 서브젝트가 클라이언트의 권한을 사용해야 하는 경우에 실수로 자기 자신의 권한으로 일을 수행했을 경우를 일컫는다. 클라이언트는 권한이 없지만 대리인은 권한이 있는 일을 부탁했을 때 대리인이 혼동하여 이 일을 수행해 준다면 보안 문제가 발생하게 되는 것이다. [위키피디아]

혼동된 대리인 문제는 소프트웨어와 시스템의 권한 분리에서 기인한다. 유닉스와 윈도를 비롯한 대부분의 운영체제는 접근 제어 리스트(access control list, ACL)를이용해 어떤 서브젝트가 어떤 권한을 가지고 있는지 검사하는데, 소프트웨어는 별다른 권한 검사를 할 필요가 없다는 면에서 관심의 분리(separation of concerns)는 확실히 보안을 강화시키는 긍정적인 효과가 있다. 하지만 이 때문에 대리인은 스스로 필요한 일을 하는 경우와 클라이언트의 일을 대신해 주는경우를 구분하기 힘들게 되었다.

이런 서브젝트의 구체적인 예로 유닉스(UNIX) 시스템의 passwd 프로그램이 있다. passwd는 프로그램을 이용해 유닉스 시스템의 사용자는 자기 자신의 암호 파일을 고치게 되는데, 이 경우 클라이언트는 /etc/passwd 파일을 고칠 권한이 없다. 대리인인 passwd 프로그램은 setuid root인 프로그램이므로 루트 권한을 가지고 있다. 여기서 의도한 바는 각각의 유저는 자기 자신의 암호만 고칠 수 있도록 하는 것이지만, 이 방식은 전적으로 passwd 프로그램의 입력 값 검사(암호 검사)에 의존하게 된다. 버퍼오버플로우(buffer overflow)를 일으켜서 암호 검사만 우회할 수 있다면(대리인을 속일 수만 있다면) passwd 프로그램(대리인)의 모든 권한을 손에 넣을 수 있게 된다.

또 다른 예로는 서버 프로그램이 있다. 서버 프로그램은 네트워크로 들어오는 요청을 받아서 처리해준다는 면에서 대표적인 대리인에 해당한다. 서버 프로그램도 UID를 가지고 있으면 해당 UID의 권한으로 프로세스가 수행된다. 만약 루트 권한으로 서버가 돌고 있다면 이 대리인의 권한은 루트인 셈이다. 문제는 서버에 서비스를 요청해 온 클라이언트는 이러한 서버 프로그램을 혼동시켜 실제로는 권한이 없는 일을 수행하도록 부탁할 수 있다는 점이다.

이런 문제를 해결하기 위해서는 케이퍼빌러티(capability)라는 보안 메커니즘이 필요하다.


char []와 char *

Posted 2006. 9. 9. 21:27
어제 후배 녀석과 이야기하다가 char[]와 char *의 차이점에 대한 이야기를 하게 되었다. C 언어를 처음 가르칠 때 편의상 char[]와 char *를 혼용해서 쓰도록 가르친 탓인지 의외로 컴공과 3-4학년 이상되는 학생들조차 그 차이점을 제대로 모르고 있는듯 싶다.


char str[] = "abc";


char *str = "abc";

printf("%s", str) 하면 똑같이 abc가 찍혀나오니깐 별 차이가 없다고 생각할 수도 있겠지만 이 두 선언은 엄연히 다르다. 위 코드를 gcc -S 옵션으로 어셈블리로 생성해보면 두 코드 모두 "abc"를 .rodata 섹션에 할당한다,

gcc2_compiled.:
.section .rodata
.LC0:
.byte 0x61,0x62,0x63,0x0


여기서 .rodata는 읽기 전용(read only) 데이터를 말한다. 그렇다면 다음과 같은 코드를 실행하면 어떻게 될까?

int main()
{
    char *str = "abc";
    str[0] = 'b';
}


str은 .rodata 영역에 있는 "abc"에 대한 포인터이므로 이 값을 바꾸려고 하면 다음과 같이 오류가 발생한다.

./a.out
Bus error (core dumped)



str[]의 경우는 어떨까?

int main()
{
    char str[] = "abc";

    str[0] = 'b';
}


이 프로그램은 문제없이 수행되며 str을 출력해보면 "bbc"가 나온다. 이런 차이가 나는 이유는 str[]의 경우 함수 호출 시에 .rodata의 "abc" 문자열을 함수의 스택으로 복사해오기 때문이다.

또 하나의 차이점이 있는데, char *의 경우 포인터 변수를 할당하여 주소 값을 가지고 있는데 비해 char[]의 경우 str 그 자체가 주소값이 된다.

C++ try/catch

Posted 2006. 9. 9. 10:22
보통 C++ 코딩을 잘 안하는데, 이번에 M 회사 프로젝트하면서 리눅스에서 C++로 어플리케이션을 개발하게 되었습니다. 그래도 C와는 다르게 try/catch를 통한 예외 처리를 할 수 있어서 좋았습니다. 그런데 오랫동안 자바 코딩을 해와서 그런지 다음과 같은 실수를 저질렀습니다.

<예제>
int foo()
{
    throw new Exception();
}

...
try {
    foo();
} catch (Exception& e) {
}


이상하게 catch가 안 되길래 봤더니 C++의 경우 throw Exception()을 써야했던 것이더군요.C++은 throw로 던질 수 있는 타입에 제한이 없다는 점이 문제였습니다. 자바를 비롯한 여러 언어는 최상위 예외클래스(자바의 경우 java.lang.Throwable)이 존재하는데 비해서, C++은 throw 1 같이 primitive타입까지 던질 수 있는 것이죠.

<수정>
int foo()
{
    throw Exception();
}

...
try {
    foo();
} catch (Exception& e) {
}
A Static Analysis to Find Null Pointer Bugs with Property Simulation, Kwang Yul Seo

1학기 과제 연구의 결과로 나온 논문입니다. 자바 프로그램에서 컴파일 타임에 널 포인터 버그를 잡는 걸 주제로 하고 있습니다. Property Simulation이란 기법을 이용하였습니다. 방학 때 구현을 목표로 잡았으나 아르바이트에 발목을 잡혀서 논문인 채로 두게 되었네요.

Cyclone

Posted 2006. 8. 31. 04:01
예전에 마소에 실었던 글을 조금 수정하였습니다.

C 거의 모든 소프트웨어의 인프라스트럭처가 되고 있는 강력한 언어이지만, 타입 안전성을 고려하지 않은 언어 디자인으로 각종 버그와 보안 사고의 원인이 되고 있다. 그 이후에 출연한 여러 고급 언어는 이런 타입 안전성 문제를 해결하였지만, 기존의 C 언어를 대체하는데 실패하였다. 싸이클론(Cyclone) 프로젝트의 목표는 타입 안전성을 제공하면서도, C 언어처럼 각종 인프라스트럭처 소프트웨어 작성에 사용될 수 있을 만큼의 자유도를 주는 언어를 만드는데 있다. 싸이클론 언어의 특성에 대해 살펴보자.


C 언어는 강력한 프로그래밍 언어이다. 운영체제 커널과 디바이스 드라이버, 파일 시스템, 웹 서버 등 오늘날 소프트웨어의 대부분은 C 언어로 작성되어 있다. 자바나 C#과 같은 고급 언어도 런타임과 컴파일러를 작성하는 데는 결국 C 언어가 사용된다. 인터넷의 혁명을 가능하게 만든 각종 네트워크 장비, 통신 설비 등에 들어가는 시스템 소프트웨어도 C 언어로 작성되어 있다.

하지만 C 언어는 강력한 동시에 위험하다. 뉴스를 오르내리는 각종 보안사고와 웜의 직접적인 원인은 잘못 작성된 소프트웨어에 있다. 하지만 기술적인 취약점의 대부분을 차지하는, 버퍼 오버플로우, 포맷 문자열 버그, 힙 오버플로우 등의 문제점은 결국 이 소프트웨어를 작성하는데 사용된 C 언어가 타입 안전성을 제공하지 않기 때문이다. 오늘날 소프트웨어의 작동이 멈추면 얼마나 끔찍한 일이 벌어질지 상상해보면, 그런 중요한 인프라스트럭처가 이처럼 취약한 C 언어로 만들어졌다는 사실이 놀라울 뿐이다.

그렇다면 대안은 무엇일까? 가장 쉽게 생각할 수 있는 방법은 타입 안전성을 제공하는 자바나 C#과 같은 언어로 기존의 소프트웨어를 새로 작성하는 것이다. 실제로 전통적으로 C 언어로 작성되던 일부 응용 소프트웨어는 이 방법으로 큰 성공을 거두었다. 실제로 고급 언어일수록 높은 생상성과 품질, 단축된 프로젝트 시간을 가져다줄 확률이 크기 때문이다.

하지만 정작 가장 중요한 시스템 소프트웨어는 어떨까? 자바로 작성된 방화벽, 스위치, 라우터는 어떤가? 반응 시간이 치명적으로 중요한 리얼 타임 시스템은 또 어떨까? 자바가 큰 성공을 거두긴 하였지만, 어디까지나 웹 어플리케이션과 임베디드용 응용 프로그램 등 특정 분야에 한정되어 있다. 아직도 저수준 제어와 직접적인 메모리 제어가 필요한 대부분의 중요 시스템은 C 언어로 작성된다.

싸이클론(Cyclone)는 이런 문제를 해결하고자 만들어진 언어이다. 싸이클론의 목표는 디바이스 드라이버나 커널을 만들 수 있을 만큼 강력한 코딩 파워를 가지면서도, 컴파일 타임에 많은 오류를 잡고 런타임에 발생하는 문제를 즉각 처리할 수 있는 자바 수준의 안전성을 가지는 언어를 만드는 것이다. 물론 C 언어로 작성된 기존 코드 베이스를 무시할 수 없으므로, C 언어로 작성된 프로그램을 최소한의 노력으로 싸이클론으로 포팅할 수 있게 한다. 따라서 싸이클론을 안전한 C 언어라고 부르기도 한다.

실제로 싸이클론 코드는 C 언어와 매우 유사하다. 싸이클론은 ANSI-C 표준에서 출발하여 임의의 타입으로의 캐스팅(casting) 같은 안전성을 해치는 기능을 버리고, 각종 타입 메커니즘을 추가한 언어이다. 싸이클론의 컴파일러는 프로그램 분석 방법을 이용해서 기존 C 언어 사용 방법 중에서 안전한 것들은 자동으로 싸이클론 타입을 유추해낸다. 최상의 경우 C 언어 코드를 그대로 컴파일할 수도 있다. 대부분의 경우 기존 C 코드에 약간의 어노테이션(일종의 프로그램 주석)을 붙여주는 정도만 해주면 된다.

C의 문제점과 싸이클론의 해법

C 언어에서 발생하는 가장 흔한 오류의 예로 버퍼 오버플로우가 있다. 배열의 크기보다 더 큰 인덱스로 배열을 읽으면, 자바에서는 ArrayOutOfBoundException이 나지만, C는 이에 대한 검사가 없기 때문에 그 주소 위치에 있는 임의의 메모리를 읽어오게 된다. 마찬가지 방법으로 배열을 끝을 넘어서는 메모리에 임의의 값을 쓸 수도 있는데, 그 값이 스택상의 리턴 주소일 경우 임의의 코드로 점프시킬 수도 있다. 이것이 C 언어에 대한 가장 흔한 공격 방법 중 하나인 버퍼 오버플로우 공격이다.

반대로 자바는 버퍼 오버플로우 공격이 불가능하다. 배열마다 배열의 길이를 저장하고 있고, 배열의 원소에 접근하기 전에 인덱스가 배열의 크기보다 작은지 런타임에 항상 검사를 수행하기 때문이다. 이런 검사를 통해 인덱스가 잘못 되었으면 ArrayOutOfBoundException을 던지는 것이다. 물론 C 언어에도 이와 유사한 방법을 사용할 수 있다. 하지만 C 언어는 배열뿐만 아니라 포인터와 포인터 연산이 존재하기 때문에 문제를 복잡해진다.

포인터 x는 배열의 처음, 중간, 끝 어느 위치든 가리킬 수 있다. x++이나 x--를 사용하면 메모리의 위치를 바꿀 수도 있다. 즉 현재 x가 포인터의 끝을 가리키고 있는데, x++을 통해 포인터를 증가시킨 후 참조하면, 배열이 아닌 임의의 메모리를 읽게 된다. 싸이클론은 이 문제를 해결하기 위해 뚱뚱한 포인터(fat pointer)라는 개념을 도입하였다. 다음 그림을 살펴보자.





뚱뚱한 포인터는 일반 포인터와 달리 배열의 처음과 끝, 현재를 모두 가리킨다. q++을 통해 포인터의 값을 증가시키면 현재 포인터(c)만 다음으로 변하고 처음과 끝을 그대로다. 따라서 배열의 경계를 넘어가는 포인터 연산의 경우 오류를 발생시킬 수 있게 된다. 물론 이 방식은 큰 런타임 오버헤드가 있다. 특히 루프처럼 반복적으로 배열 값을 읽는 경우 반복된 체크의 오버헤드는 프로그램 수행 속도를 크게 떨어뜨린다. 싸이클론은 기존 포인터와 같은 날씬한 포인터(thin pointer) 또한 제공한다.

이와 유사한 문제는 NULL 값과 관련해서도 발생한다. 주로 NULL 포인터를 참조해서 발생하는 공포의 메모리 오류(segmentation fault)는 C 언어 개발자라면 피해갈 수 없는 숙명이다. 다음과 같은 간단한 파일 IO 프로그램을 살펴보자.

NULL 검사가 없는 파일 IO

 
int foo(char ?filename, char ?buf) {
  FILE *fp;
  fp = fopen(filename, "r");
  fread(fp, buf);
}


이 간단한 프로그램은 파일 열기에 실패했을 경우 프로그램 크래시(crash)가 발생한다. NULL 검사 없이 fread를 호출하였기 때문이다. 싸이클론 포인터는 int *와 int *@notnull로 나뉜다. int *@notnull은 줄여서 int @라고 쓰기도 하는데, 이 포인터는 절대 NULL이 될 수 없다는 뜻이다. NULL을 넘겼을 때 오류가 발생하는 fread와 같은 함수는 인자를 @로 선언해서 프로그래머가 반드시 NULL 검사를 하도록 만들 수 있다.

싸이클론 버전

struct FILE;
extern FILE *fopen(char ?name, char ?mode);
extern int putc(char, FILE@);
void foo() {
  FILE *f = fopen("/tmp/bar.txt", "+wb");
  char s[] = "hello";
  int i;
  for (i = 0; i < 5; i++) { putc(s[i], (FILE @)f); }


위 프로그램에서 putc 함수는 두 번째 인자로 FILE *@notnull을 받으므로, FILE은 NULL이 될 수 없다. foo()의 FILE *f는 FILE * 타입이므로 FILE @와 달라서 컴파일 타임에 타입 오류가 발생한다. (File @)f 는 FILE *를 FILE @로 캐스팅해 줄 뿐만 아니라 런타임 NULL 검사도 추가해준다. 따라서 파일이 없다고 프로그램이 크래시 되는 대신에 자바처럼 예외가 발생하게 되는 것이다.

정리

지금까지 싸이클론의 기능 한 가지를 살펴보았다. 싸이클론은 이보다 훨씬 방대한 언어이다. 싸이클론에서 메모리 할당은 리전 기반 타입 시스템(region based type system)을 사용해서 힙과 스택 영역 등의 포인터를 서로 다른 타입으로 구분한다. 또 각종 현대 타입 시스템 이론을 적절히 활용하여 C 언어의 부족함을 메워주고 있다.

필요는 발명의 어머니라는 말이 있다. C 언어의 부족함과 새로운 언어에 대한 필요성은 앞으로도 여러 가지 언어를 낳을 것이다. 싸이클론은 그런 언어의 하나로 아직 널리 사용되고 있지는 않다. 하지만 저수준 프로그래밍의 고충을 경험한 개발자라면 중요 시스템의 일부 영역만이라도 싸이클론의 적용을 고려해보기 바란다. 추가 정보는 최근에 통합된 싸이클론 홈페이지에서 얻길 바란다.
윈도우즈에 까는 프로그램 10가지입니다.

1. GOM Player
요즘 컴퓨터를 켜서 하는 일의 대부분이 영화, 드라마 감상인지라 윈도를 새로 깔면 자연스럽게 GOM Plyaer를 먼저 깔게 되네요. 예전에는 최의종 군이 만든 사사미(Sasami)도 즐겨썼던 거 같은데 어느 순간부터 업데이트 안 되고 어느 틈에 Gom Player를 쓰고 있더군요. 따로 코덱을 안 구해다 깔아도 되서 자연스럽게 사용하게 된 것 같습니다.

2. 빵집
다른 분들은 7-ZIP이라는 프로그램을 많이 쓰시던데 저는 처음 들어봅니다. 이해할 수 없는 알집의 alz 포맷과 윈도우의 리소스를 다 잡아 먹는듯한 알집 자동 풀기(exe)에 화가 나서 빵집으로 바꾸었습니다.

3. JDK
자바 환경에서 작업을 많이 하다보니 역시 JDK를 빼놓을 수가 없습니다. JDK6를 받아서 사용해보다가 요즘은 다시 안정적인 JDK5로 돌아왔습니다.

4. MS Office
윈도 깔아서 하는 일이라는 게 결국 리포트 쓰고, 피피티 만드는 일이 많은지라 역시 오피스도 필수 아이템입니다.

5. Acrobat Reader
PDF 포맷을 읽지 못하면 컴퓨터가 반쯤 바보가 되기 때문에 아크로밧도 우선 순위에 올라있습니다.

6. VIM
제 버릇 개못준다고 워낙 VIM 에디터에 손이 익어서 윈도우즈에서 윈도우용 VIM을 쓸 수 밖에 없습니다. 설치하고 나서 파일에 대고 오른쪽 버튼 클릭하면 빔으로 편집하기가 나오기 때문에 애용하고 있습니다.

7. Cygwin
윈도 개발도 거의 Cygwin에서 유닉스 환경으로 하기 때문에 Cygwin도 필수 항목입니다.

8. V3와 스파이제로
실시간 감시하면 컴퓨터 느려지기 때문에 최대한 이용을 피했는데, 윈도 설치하면서 각종 웜과 바이러스에 걸리는 현 상황에서는 안 깔수가 없더군요. 특히 스파이제로가 없으면 언제 걸렸는지도 모르는 이상한 스파이웨어가 익스플로러에 기생해 살고 있더군요.

9. 노턴 고스트
성격이 부주의한 편인지 하드를 날려 먹는 일이 많아서 이제 반드시 노턴 고스트를 깔고 주기적으로 하드 이미지를 백업 받고 있습니다. 윈도 새로 까려면 하루 반나절을 날려 먹기 때문에 이 방법이 좋더군요.

10. 네이트온
사실 네이트 온에 친구들이 별로 없기 때문에 필수적인 프로그램은 아니지만, SK에서 제공하는 공짜 문자 100건을 사용하기 위해 깔아둡니다. 핸드폰으로 문자 보내는 속도가 느려서 보통 네이트온으로 문자 보내는 경우가 많습니다.

파이썬의 Unicode 지원

Posted 2006. 8. 30. 23:39
프로그래밍 언어의 유니코드 지원은 빼놓을 수가 없는데, 자바와 파이썬을 비롯한 여러 언어가 escape sequence인 \u0000 형태로 유니코드 문자를 지정할 수 있다.

유니코드가 16비트로 표현할 수 없게 되자 파이썬은 \u00000000 형태의 escape sequence도 지원하고 있다. 여기서 한 술 더 떠서 유니코드 문자를 이름으로 지정할 수 있는 \N escape도 있다.

  # This string has 7 characters in all, including the spaces
  # between the symbols.
  symbols = u'\N{BLACK STAR} \N{WHITE STAR} \N{LIGHTNING} \N{COMET}'

또한 unichr()를 이용해 유니코드 문자를 지원할 수도 있다.
[Effective C++ (3rd Edition)](Scott Meyers, Addison-Wesley Professional, 2005)에 나오는 “Never Call Virtual Functions during Construction or Destruction”라는 글을 읽었다.

핵심만 간단히 요약하면 C++의 생성자 및 소멸자에서는 가상 함수가 가상 함수로 동작하지 않는다는 거다. 왜냐하면 베이스 클래스 생성자에서는 아직 하위 클래스의 멤버가 초기화되지 않았는데, 하위 클래스의 가상 함수는 하위 클래스의 멤버를 참조하기 때문이다.

C++과는 달리 자바와 C#에는 이런 조치가 없다. 이 경우 어떤 문제가 생기는지 보자

public class Base {
  public Base() {
    System.out.println("Base class");
    virtualFunction();
  }

  public void virtualFunction() {
    System.out.println("Base virtualFunction");
  }
}

public class Sub extends Base {
  public String str = "Hello world!";

  public Sub() {
    System.out.println("Sub class");
    System.out.println(str);
  }

  public void virtualFunction() {
    System.out.println("Sub virtualFunction:" + str);
  }

  public static void main(String ...args) {
    new Sub();   
  }
}


위 자바 코드를 실행시켜보면 다음과 같다.

Base class
Sub virtualFunction:null
Sub class
Hello World!

Base클래스의 생성자에서 부른 virtualFunction이 Sub 클래스의 virtualFunction을 호출하고, 이 메소드는str 멤버를 출력한다. 근데 이때 아직 Sub 클래스의 생성자가 불리지 않은 상태이므로, str는 초기화되지 않은 null값을 출력한다. 즉 의도하지 않게 아직 초기화되지 않은 값을 참조하게 되는 것이다.

C++의 경우 Base클래스에서 virtualFunction이 더 이상 가상 함수가 아닌 것으로 보고 Base 클래스의 virtualFunction을부르는 방식으로 이 문제를 해결한다. 하지만 Scott Meyers의 말처럼 생성자나 소멸자에서 가상 함수를 부르지 않는 편이안전하다.

일용직 프로그래머

Posted 2006. 8. 30. 21:29
DC 인사이드에 프로그래밍 갤러리에서 가져온 카툰입니다. 일용직이 되어버린 프로그래머의 일상을 멋지게 묘사한 수작이네요. 양극화 시대에서 개발자도 예외일 수는 없는 것 같습니다. 특히 개발 방법과 도구가 발달할수록 중급의 개발자 수요는 점차 줄어들 것 같습니다. 일용직 개발자들과 일부 전문 개발자들만이 살아남는 시대가 곧 올 것 같네요.
« PREV : 1 : ··· : 9 : 10 : 11 : 12 : 13 : NEXT »