dev.log2009. 3. 3. 18:17
회사에는 goto를 좋아하는 사람이 한명 있다. goto를 그냥 남발하는 게 아니다. 진심으로 goto를 좋아해서 쓴다 -_-a 이런 종류의 인간이 제일 난감한데, 이런 식이다. '남들이 다 쓰지 말라고 하는 거지만 난 잘 쓸 자신이 있다'는 식이다. 남들과 달리 잘 쓸 수 있으므로 쓰겠다는 입장. 그래서 모든 로직의 핵심을 goto로 구현해 놓고 자랑스러워한다. '이 로직은 goto로 완벽하게 짜봤지요' ㄷㄷㄷ

bool func()
{
    bool succ = false;
    mutex.lock();
    if ( do_something() == false )
       goto exit;

    if ( do_another() == false )
       goto exit;

    if ( do_blabla() == false )
       goto exit;

    succ = true;
exit:
    mutex.unlock();
    return succ;
}


사실 난 되도록이면 코드는 자기가 짜고 싶은 대로 짜면 된다;는 입장이라, 이 인간이 goto를 쓰건 말건 걍 놔 뒀었다. 그리고 위와 같은 코드는 일반적으로 용인되는 goto의 쓰임과 크게 어긋나지도 않기 때문에. 문제는 이런 부분.

void* get()
{
retry:
    mutex.lock();  

    if ( !available )
    {
        if ( try_alloc() == false )
            goto must_alloc
    }

    void* result = do_postprocess();
    mutex.unlock();
    goto done;   

must_alloc:
    mutex.unlock();

    if ( alloc()  )
        goto retry;
    return 0;

done:
    return result;
}

대충 이런식. 이 코드를 내가 디버깅해야 할 비상사태가 오면서 문제가 불거지기 전까지는 문제의 심각성을 몰랐다. -_- 뒤로갔다 앞으로 갔다 하는 goto의 흐름을 쫓다가 디버깅시간의 2/3가 흘러간 걸 보고서 고쳐놔야 겠다는 생각이 들었다.

사실 앞의 코드는 너무 쉽다. 그냥 true/false를 바로 리턴하도록 바꾸고 뮤텍스는 로컬 객체로 래핑해서 생성자/소멸자에서 lock/unlock을 부르게 하면 되니까. 뒤에 꺼는 좀 까다롭다. 분명히 루프이긴 한데, 루프의 종료조건이 뭔지 알기가 매우 힘들다 -_- 종료조건이라 하더라도 거기에서도 분기가 두갈래다. 깔끔한 코드를 만들기가 어렵다. 나름 도전의식이 생기는 코드랄까.

Posted by uhm
dev.log2009. 2. 23. 00:09
C++ 0x 표준안이 올해 확정된다는 소식을 들었다. Design & Evolution of C++에서 봤던 변천 과정에 한가지가 더해진다는 느낌. D&EC++은.. 읽다가 말기도 했고, 오래전이라 기억이 가물가물하기도 해서 한번 뒤져봤다.

1979 : C with Classes
Bjarne Stroustrup께서(이하 B.S.) 시뮬라에서 힌트를 얻어 C의 개량판을 구상하기 시작하던 시절이다. 시뮬라는 본격 객체 지향 언어였지만, CwC에는 그냥 ADT(abstract data type) 개념만을 집어넣고 C에서는 거의 하지 않았던 타입 체크를 강화하는 쪽으로 구현. 이때는 C 컴파일 단계 전에 파싱하여 C소스코드를 생성하는 전처리기로 구현했었다. D&EC++에 따르면, 당시의 CwC는 다음 기능을 갖고 있었다.

  • classes
  • inheritance
  • access control (private/public)
  • cons/des (new/delete)
  • monitor (call/return)
  • friend classes
  • type check (undeclared function, void args, conversion)
  • inline func
  • default args
  • assignment overloading

대부분은 아직까지 유지되고 있지만 변경되거나, 없어진 기능들도 있다. 이를테면, call/return 멤버 메소드를 이용한 - call에서 락을 걸고, return에서 락을 해제하는, 일종의 콜백개념 - 동기화 구현은 B.S.자신을 제외한 그 누구도 쓰지 않았기에 없애는 쪽으로 결정되었다고 한다 -_-a 그리고 당시에는 생성자와 소멸자 메소드가 new/delete 라는 이름을 갖고 있었다. 그리고 연산자 오버로딩은 = 연산자만 지원.

1982 : C++ (Cfront)
C++이란 이름을 처음 갖고 발표된 C++ release 1.0의 시기. 하지만 여전히 C의 전처리기로 구현되었으며, 그 전처리기의 이름은 Cfront. Cfront의 기능은 위의 것에 더하여 다음이 추가.

  • virtual functions
  • function/operator overloading
  • reference
  • const
  • new operator
  • type check ( func args, ... list)
  • line comment
  • scope resolution

이때는 가상함수를 통한 동적 다형성을 지원하면서 객체지향 언어의 면모를 갖추게 된다. 또한 타입 체크가 점점 강화되는 쪽으로 가는데, 그것은 초기 C++의 설계 정책중 하나가 C와의 하위호환성 보장이었기 때문에, C코드를 컴파일할 수 없는 언어 스펙은 보급이 곤란하기 때문이었다고나. 충분한 수의 C++유저가 확보되지 않은 상태에서는 점진성이 타당한 선택이었을 듯. 그외에는 사소한 차이점들. 지금과 같은 개념의 new연산자가 도입되면서, 기존의 new/delete대신 지금의 형태를 가진 생성자/소멸자가 도입되었다. 또한 이때의 오버로딩에는 overload라는 키워드가 필요했다. 1984년에는 The C++ Programming Language가 출간되기도.

1985 : C++ R2.0
사람들의 관심이 서서히 증가하면서 달라진 기능의 C++ release 2.0을 발표. 이 시기는 B.S.가 만든 Cfront 프리프로세서를 대체할 (진짜) 컴파일러가 여기저기서 만들어지기 시작한 때이다.

  • multiple inheritance
  • type-safe linkage
  • overloading resolution
  • init/assign definition in member-wise basis
  • dynamic initialization
  • new overloading
  • abstract class
  • static member func
  • const member func
  • protected members
  • member to pointer (->) overloading
  • pointers to members
  • libraries

B.S의 말에 따르면 R2.0으로의 변화는 기능의 추가라기보다는 제약의 해제라고 한다. 단1개의 베이스클래스에서 여러개의 베이스클래스를 허용한다든가, 그전까지 오버로딩 불가능했던 연산자들에 대한 허용이라거나; 하는 것들. 또한 지금의 형태를 가진 기본적 라이브러리가 구현되기 시작한 시기이기도 하다. 물론, 이때는 템플릿이 없었으므로 주로 stream IO나 문자열 라이브러리들부터 구현되었다.
   
1988 : C++ARM
Annotated Reference Manual이 발표되면서 지금의 C++의 모습이 거의 갖추어진다. 다음의 변화가 추가되었다.

  • template
  • exception
  • nested class
  • pre/post ++ overloading
  • local statics
  • volatile
  • STL

이 시기의 C++이 가장 많은 사람들에게 알려진 C++의 형태이다. <iostream.h> 헤더를 사용하던 형태가 바로 이때의 C++. 표준화가 이루어지기 직전이다. 이때의 C++ 문법들은 거의 지금 그대로 남아있다. 가장 중요한 변화는 템플릿의 도입인데, 이로 인해서 C++이 지원하는 프로그래밍 패러다임이 하나 더 늘어나게 되었다. 템플릿과 함께 STL도 도입.
   
1998 : C++98
표준화가 이루어진 C++의 스펙이다. ISO/IEC 14882로 검색해 보면 표준에 대한 설명을 찾아 볼 수 있다. 주요 변경사항은 다음과 같다.

  • runtime type information
  • namespace
  • casting operators
  • bool type
  • explicit template instantiation
  • explicit template args in template function calls

표준화 작업이 거의 10년이나 지속됐기에, ARM형태의 C++이 널리 퍼진 다음에 발표된 표준은 확산이 매우 더뎠던 걸로 기억한다. MSVC도 최근에 와서야 C++98 스펙을 거의 다 지원하게 되었으니 말 다한 셈. 이때부터는 표준라이브러리는 모두 std네임스페이스에 속하도록 정해졌으며 기존 ARM형태의 라이브러리와 혼동을 피하기 위해 표준헤더도 .h확장자를 떼는 쪽으로 결정.

   
2003: C++03
1998년 표준 문서에서 발견된 모순이나 오류들을 수정한 문서이다. 그다지 크게 달라지지는 않았으나 스펙이 명료한 표현으로 대체되었다. 따라서 현재 C++ 표준은 ISO/IEC 14882:2003 이다. 주목할만한 점이라면 vector의 메모리 연속성에 대한 보장이 표준에 의해 명시되었다는 점 정도.

   
2005 : C++TR1
C++ 표준 라이브러리의 확장을 위한 첫번째 technical report. C++유저들 사이에 널리 쓰이거나 요구되는 라이브러리를 정리한 공식 표준으로 ISO/IEC TR19768로 검색하면 관련 내용이 나온다.

  • tuple
  • array containner
  • unordered_map
  • regular expression
  • reference wrapper
  • polymorph wrapper
  • smart pointer
  • func binder
  • return type wrapper
  • random number lib

면면을 살펴보면 기존의 Boost 라이브러리에 있던 개념들이 다수 포함되었다. Boost 자체가 C++표준화 위원회의 임원들이 다수 포진하여 만든 라이브러리이니 당연한 결과일지도 -_-a 전체적으로는 요즘 기조에 맞게 템플릿의 활용도를 높이는 쪽으로 가고 있다.


2009 : C++0x
ISO/IEC14882:2003을 대체할 차기 표준안. 올해 확정되는 바로 그 C++ 스펙이다. 엄청나게 많은 점이 달라진다.

  • template >> priority
  • const expression
  • sizeof member without object
  • peer constructor
  • unrestricted friend
  • extern template instantiation
  • r-value reference
  • long long int
  • null type
  • enum class
  • attributes
  • explicit conversion
  • unrestricted union
  • static_assert
  • control of implicit member
  • initializer constructor
  • inheriting constructor
  • return type abduction
  • template typedef
  • string literals (encoding, raw)
  • user-defined literals
  • thread_local

사실 여기까지는 사소한 변화이다. C++ 유저들의 스타일을 근본적으로 바꿀 변화는 따로 있다.

  • type inference
  • vari arg template
  • concept
  • ranged for
  • lambda func

각 항목의 변화가 덩치가 너무 크므로 여기선 생략. 여튼 이로 인해 표준 라이브러리에 다음 구성요소가 추가된다.

  • tuple
  • range
  • unordered_map
  • regular expression
  • smart pointer
  • random
  • reference wrapper
  • polymorph wrapper
  • general function binder
  • threading (thread, mutex, condition, atomics)
  • type_traits
  • return type wrapper

라이브러리 구성요소는 TR1의 스펙이 거의 다 포함되는 쪽이며, 오히려 컨셉트나 가변 템플릿 매개변수 같은 언어 자체의 변경사항 덕분에 더 명료해진 편이라는 인상.

이런저런 것들이 바뀌었지만 대체적으로 기존 표준에 적합한 코드는 거의 다 제대로 동작한다. 오히려 더 간편한 표현을 가능하게 하는 요소가 생겼으므로 (람다나, 초기화목록 생성자, 범위기반 for 같은) 더 유연한 언어가 되었다고 봐야 맞을 듯.
하지만, 안그래도 '메저키스트의 언어'라는 평을 듣는 C++에 이런 것들을 추가하면 메저키스트 정도로는 끝나지 않을 것 같기도 하다. 뭐, 사용자도 사용자지만, C++98이 제대로 구현되는데 거의 10년이나 걸렸음을 감안하면, C++0x를 제대로 구현한 컴파일러는 2019년쯤에나 나올거 같다.

Posted by uhm
geek.log2009. 2. 18. 16:31
은하수를 여행하는 히치하이커를 위한 안내서를 보면, 깊은 생각이 인생, 우주와 모든 것들에 대한 궁극적인 해답을 내놓을 것을 요구받으면서 대략 750만년이 걸릴 것이라고 말한다. 결과적으로 750만년동안 철학자들의 실업 사태는 유예되었지만, 중요한 것은 철학자들의 실업사태 따위가 아니다. 사람들이 잘 주목하지 않는, 심지어는 컴퓨터 전공자까지도 잘 주목하지 않았던 중요한 사실은, 깊은 생각을 만들어낸 범차원적 초지성체들은 정지문제(halting problem)를 해결할 수 있었다는 점이다!!

정지문제는, 임의의 알고리듬이 어떤 (유한한 길이의) 입력에 대하여 종료될 것인가 아닌가를 실행해 보지 않고 판단할 수 있는 알고리듬이 존재할 수 있는가-의 문제인데, 결론적으로는 이런 알고리듬은 존재할 수 없다. 왜냐하면, 이런 알고리듬은 그 자체도 종료될 수 있는가 아닌가도 판단할 수 있어야 하는데, 이 알고리듬의 결과를 사용하는 쪽에서, 종료한다는 결과가 나왔을 때 무한루프를 돌아버리면 모순된 결과가 되기 때문이다. 따라서 모든 알고리듬에 대해 정지문제를 풀 수 있는 알고리듬은 존재할 수 없다.

그런데 우리의 깊은 생각은, 문제를 듣자마자 잠시 생각한 후 계산이 750만년 후에 종료된다고 답을 냈다. 깊은 생각이 그 문제를 풀기 위해 만들어진 컴퓨터이긴 하지만, 문제를 듣고난 후에야 풀기 시작했다는 것은, 깊은 생각은 임의의 알고리듬에 대해 정지문제를 풀 수 있었다는 뜻이 아닐까! 일부 알고리듬에 대해 정지문제를 결정할 수 있는 알고리듬은 존재할 수 있지만, 범차원적 초지성체의 세계에서는 모든 알고리듬에 대해 정지문제를 결정할 수 있는 방법이 있었을 지도 모르는 일.

Posted by uhm