시작...

블로그 이미지
mutjin

Article Category

분류 전체보기 (148)
기록 (3)
개발새발 (8)
2010년 이전 글 (133)

Recent Post

Recent Comment

Recent Trackback

Calendar

«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

Archive

My Link

  • Total
  • Today
  • Yesterday
  1. 2007.11.13
    C++ 클래스 설계의 정석
  2. 2007.10.30
    TCPDUMP 요약글입니다.
  3. 2007.10.30
    TCPDUMP 사용법
  4. 2007.10.25
    Visual Studio에서 OpenSSL을 사용하기 위한 라이브러.. 1
  5. 2007.10.23
    다중 접속 서버 프로그래밍
  6. 2007.10.23
    Dialog Window 띄우면서, 작업표시줄에서 감추기
  7. 2007.10.23
    중복 실행 방지.
  8. 2007.10.23
    비트 연산으로 구현한 곱셈
  9. 2007.10.23
    [CElapsedTimer] 프로파일링에 유용하게 사용
  10. 2007.10.23
    [강좌] 기초적인 압축 알고리즘 하이텔 퍼옴..

C++ 클래스 설계의 정석

생성자, 소멸자, 대입(=) 연산자

G. Bowden Wise 지음, 김 용묵 번역

여러분은 클래스를 직접 설계할 때, 속으로 여러 질문을 할 것입니다. 복사 생성자가 필요한가? 생성자 함수에 대체 인자(default arguments)가 필요한가? 클래스가 형변환이 되도록 만들 필요가 있는가? 같음 연산자를 정의할 필요가 있는가? 후위(postfix) 연산자는 어떻게 구현할까? 함수가 개체 자신을 되돌리게 할까, 개체의 참조자(reference)를 되돌리게 할까? 이 개체를 스트림으로는 어떤 식으로 내보낼까?

이런 질문은 노련한 C++ 프로그래머들도 곱씹고 고민하며, 때로는 이들조차 실수를 하여 비효율적인 코드를 작성하기도 합니다. 그래서 C++ 클래스를 결함 없이 설계하는 요령을 다룬 많은 책과 기사가 나갔습니다. 좀더 나은 C++ 프로그래머가 되려면 C++에서 있을 수 있는 미묘한 함정들을 배우고 이에 대한 안목을 키워야 할 것입니다.

이 다음부터 여러 회에 걸쳐, 나는 여러 C++전문가들의 지식과 약간의 내 경험에서 따온 C++ 활용 정석을 다루려고 합니다. 이 지침들이 여러분이 클래스를 스스로 구현하는 데 유용하게 쓰였으면 좋겠습니다. 이제 클래스의 특수한 함수부터 시작해 보겠습니다. 특수한 함수란 생성자, 소멸자, 대입 연산자 함수를 말합니다.

특수한 구성원 함수

클래스는 데이터와 함수를 모두 구성원으로 가집니다. 그리고 함수는 대개, 사용자가 개체를 대상으로 어떤 동작을 하게 해주는 중개 역할을 합니다. 그런데 함수 중에는 또다른 특별한 의미를 지닌 게 있습니다. 개체가 살아있는 동안 특별한 임무를 맡기 때문입니다. 클래스에서 특별한 구성원 함수 구실을 하는 것은 생성자, 소멸자, 대입 연산자 함수입니다.

개체의 생명 주기

개체는 인스턴스를 선언함으로써 프로그램 안에서 생겨납니다. 그밖에 컴파일러가 내부적인 목적으로 개체를 생성하는 코드를 집어넣기도 합니다. 개체를 선언할 때는 이름을 배당합니다. 이 이름을 가진 개체는 개체가 정의된 영역(scope) 안에서만 사용 가능합니다. 영역이란 코드가 들어있는 블록을 뜻하며, 개체는 그 영역 안에서만 사용 가능하게 됩니다.

영역에는 세 가지 범위가 있습니다. 중괄호 블록 안이 영역인 지역 범위가 있고, 소스 파일 안에 있는 모든 곳(블록 바깥 역시 포함)이 영역이 되는 파일 범위가 있으며, 클래스의 구성원으로 선언되어 모든 구성원 함수가 영역이 되는 클래스 범위가 있습니다. 파일 범위에 드는 변수는 전역 변수라고 일컬어집니다.

개체의 생명 주기는 저장 등급(storage class)에 의해 결정됩니다.

  • 일반(Automatic).중괄호 안에서 선언된 개체는 일반 개체가 됩니다. 일반 개체는 프로그램 실행점이 변수가 선언된 곳을 지날 때마다 생겨나며, 실행점이 중괄호 구간을 벗어나면 소멸합니다.
  • 고정(Static).개체 앞에 접두어static가 붙으면 이 개체는 고정 개체가 됩니다. 고정 개체는 한번 선언되면 프로그램이 실행돼 있는 동안 계속 그 값이 유지됩니다. 전역 개체도 역시 고정되어 있습니다. 고정 개체는 실행점이 이 개체를 선언하는 곳을 지날 때 딱 한번 생성되며, 처음 생성될 때 기본적으로 0으로 초기화됩니다.

C++은 개체가 쓰이기 전에 반드시 제대로 초기화가 될 수 있음을 언어 자체가 보증합니다. 개체는 생겨나는 때가 되면 그 개체에 딸린 메모리가 할당된 후 곧 초기화됩니다. C++언어는 개체를 초기화하는 일을 하는 특별한 함수를 제공하는데, 이것이 생성자 함수입니다.

생성자는 개체가 생겨날 때마다 호출됩니다. 개체는 지역 변수로 생길 수도 있고, 전역 변수로 생길 수도 있으며, new 연산자에 의해 생길 수도 있습니다. 생성자 함수를 명시적으로 호출해줄 수도 있고, 임시 개체가 생길 때도 개체가 생겨날 수 있습니다. 어떤 개체가 생성되면 그 개체의 일부인 개체들의 생성자도 제각기 호출됩니다.

이와 마찬가지로 개체가 생성 범위를 벗어나면 그 개체에 할당돼 있던 메모리는 사라지게 됩니다. C++언어는 개체가 소멸될 때 소멸자라고 하는 특수한 함수를 호출하여 소멸되는 개체가 고유한 마무리 작업--개체가 할당한 다른 메모리나 시스템 자원을 반환하는 일 같은--을 할 수 있게 해 줍니다.

C++은 특수한 역할을 맡는 또다른 특수한 함수를 제공합니다. 어떤 개체와 똑같은 복사본이 생겨야 할 때는 복사 생성자 함수가 호출됩니다. 그리고 개체이 어떤 값이 대입되면, 그 개체의 대입 연산자 함수가 호출되지요.

생성자

생성자 함수는 개체 자신에 할당된 메모리를 가공하여, 이것을 살아있는 개체가 저장된 메모리로 바꾸는 역할을 합니다. 생성자 함수에는 기본 생성자, 복사 생성자가 있으며, 그밖에도 여러 가지 인자를 받는 생성자가 있을 수 있습니다.

기본 생성자(Default constructor)

기본 생성자는 아무 인자도 받지 않는 생성자를 말합니다. 클래스 X에 대한 기본 생성자는 X::X()와 같은 형태를 취합니다. X::X(const int x=0)처럼 모든 인자에 대해 대체값을 가진 생성자 또한 기본 생성자가 됩니다. 아무 인자를 주지 않고도 호출할 수 있기 때문이지요.

기본 생성자는 개체가 생성자 함수에 아무런 인자를 주지 않아도 그 개체를 초기화해줍니다. 예를 들어 다음과 같은 선언은 문자열 s가 아무런 값이 없는 상태, 즉 빈 문자열로 초기화됨을 뜻하게 됩니다.

String s;

기본 생성자는 대개, 그 클래스의 형태를 띠면서 텅 빈 상태인 개체를 생성합니다. 예를 들어 복소수를 나타내는 클래스의 기본 생성자는 값이 0인 복소수를 만드는 역할을 하고, 연결리스트 클래스의 기본 생성자는 아무 내용이 없는 리스트가 되도록 자신을 초기화하지요.

정석 1.클래스에 기본 생성자를 만들어 두십시오. 그리고 대체 인자를 활용하여, 기본 생성자를 또 만드는 수고를 피하십시오.

클래스에서 생성자에 인자를 받도록 하는 경우가 자주 있습니다. 인자를 하나도 받지 않는 기본 생성자를 따로 만들기보다는, 대체 인자가 있는 생성자 함수를 만들어서 대체 인자가 기본 생성자 역할까지 하고, 대체 인자가 생성자 함수에 정보를 제공할 수 있게 하는 게 좋습니다. 이 기법은 코드를 더욱 간결하게 하고 코드의 재활용성을 높여줍니다.

예를 들어 클래스 String에 생성자가 String(), String(const char *str)과 같이 두 개가 있다면 이것을 String(const char *str=0)과 같이 하나로 줄일 수 있습니다.

기본 생성자는 하나밖에 없습니다. 그러므로 생성자 함수마다 모든 인자에 대체값을 집어넣지는 마십시오. 컴파일러가 대체 인자가 있는 생성자를 기본 생성자와 구분하지 못해 에러를 내게 됩니다.

기본 생성자를 가진 클래스를 만들기가 어려워 보일 때가 있습니다. 클래스가 만들어질 때 특정 구성원이 반드시 초기화되어야 그 클래스가 동작할 수 있는 경우 말입니다. 그렇지만 그 특정 자료를 나중에라도(이 개체가 생성되고 나서) 제공할 수 있는 방법을 열어놓는다면 그 클래스는 기본 생성자를 만들 수 있습니다. 예를 들어 여러분이 C++ 스트림 라이브러리를 써 봤다면, 출력 파일을 나타내는 개체를 파일 이름 없이 만들 수 있다는 사실을 알 것입니다.

ofstream out;
출력하는 파일 이름은 나중에 open이라는 함수로 제공할 수 있습니다.
out.open("outfile");

템플릿을 만들 땐 조심하기 바랍니다. 코딩할 때 주의를 기울이지 않으면, 여러분의 템플릿은 기본 생성자가 없는 클래스는 받아들일 수 없게 될지도 모릅니다.

T element;
위와 같은 선언문이 템플릿 함수에 있다면 기본 생성자가 있는 클래스만 이 템플릿 함수를 쓸 수 있지요.

정석 2.적당한 곳이라면 어느 구성원 함수에든지 대체 인자를 넣으십시오.

대체 인자는 생성자 함수에만 쓸 수 있는 것은 아닙니다. 어느 구성원 함수에나 쓸 수 있으므로, 적당한 곳이라면 어디에나 대체 인자를 두십시오.

복사 생성자

복사 생성자는 개체를 복사할 때 호출되는 특별한 생성자입니다. 클래스 X의 복사 생성자는 X::X(const X&)라는 꼴을 가집니다. 아래와 같은 코드가 있을 때, s2는 s1이라는 개체를 자신으로 복사하는 복사 생성자를 호출함으로써 생성됩니다.

String s1("Hello, world!");   String s2 (s1);

복사 생성자는 우리가 모르게 컴파일러가 개체의 복사본이 필요할 때마다 자주 호출됩니다. 우리가 임시 개체라고 부르는 이러한 개체는 컴파일러가 필요할 때마다 생겨났다가 자기 임무를 다하면 소멸합니다. 이런 일이 가장 일반적으로 일어나는 때는 함수가 호출될 때입니다. 예를 들어 다음과 같이 String이라는 인자를 받는 함수가 있다고 칩시다.

void DisplayError (const String s);
DisplayError()가 호출될 때마다 컴파일러는 String의 복사 생성자를 호출하여 인자로 전달할 임시 개체 s를 만듭니다. 그리고 그 임시 개체가 함수로 전달됩니다.

정석 3.클래스의 복사 생성자를 항상 만들어 두고, 컴파일러가 대신 생성하도록 하지 마십시오. 특히 클래스에 포인터로 동적 메모리를 할당하는 구성원 변수가 있다면 반드시 복사 생성자를 만들어야 합니다.

만약 복사 생성자를 두지 않는다면 컴파일러는 클래스에 대해 기본 복사 생성자를 만듭니다. 기본 생성자 함수는 클래스의 각 구성원의 값을 단순히 복사해 오는 일만 합니다. 이런 동작은 포인터를 쓰지 않는 클래스에나 적합합니다. 클래스를 설계할 때마다 복사 생성자를 꼬박꼬박 만들어 두는 습관을 들이는 것이 좋습니다.

생성자 함수를 작성하는 요령

생성자 함수는 여러분이 개체를 직접 생성할 때 외에도 컴파일러가 임시 개체를 만들 때 등을 포함해도 자주 호출됩니다. 그렇기 때문에 생성자는 최대한 간결하고 효율적이어야 합니다. 다음은 생성자 함수를 만들 때 고려해 볼만한 정석입니다.

정석 4. 생성자 함수에서 구성원 변수를 초기화할 때는 대입 연산보다는 자체 초기화 방법을 쓰십시오. 이렇게 하면 코드의 효율이 높아지고 부가적인 생성자 함수 호출을 막을 수 있게 됩니다.

_center과 _color라는 구성원을 가진 Shape라는 클래스가 있습니다. 구성원의 자료형은 전자는 Point라는 클래스고 후자는 int입니다. 생성자 함수를 만들 때, 여러분은 대입 연산자를 써서 구성원을 초기화해야겠다는 생각을 언뜻 할 것입니다.

Shape::Shape (const Point center,  const int   color)
{
_center = center;
_color = color;
}
그런데 이렇게 하면 Point 클래스의 생성자가 또 호출되게 되고 코드의 수행 속도가 떨어집니다. 그 이유를 알아보기 위해 개체들이 어떻게 생성되는지 살펴봅시다. 개체가 생성되는 과정은 다음 두 가지로 나눠 볼 수 있습니다.
  1. 구성원 변수는 클래스에 나열(선언)됐던 순서대로 초기화됩니다. 이를 '구성원 초기화'라고 합니다.
  2. 본 클래스 자체의 생성자 함수가 호출됩니다.

파생(derive) 클래스의 경우 부모 클래스의 것부터 시작하여 위의 두 과정이 수행됩니다.

Shape 클래스의 생성자에서 _center는 1단계 동안에 생성되며, 이때 Point의 기본 생성자가 호출됩니다. 그리고 2단계로 들어가서 생성자 함수가 대입 연산자 함수를 호출하면 비로소 _center의 값이 바뀝니다.

_center는 자신을 담고 있는 클래스의 생성자 함수가 실행되기 전에--나중에 생성자 함수가 _center의 값을 어떻게 바꾸든지간에-- 언제나 먼저 초기화된다는 점을 알아두십시오. 하지만 Shape 생성자 함수의 초기화 목록을 쓰면, _center를 어떤 생성자 함수로 초기화할지를 조절할 수 있습니다.

Shape::Shape (const Pointer center,  const int color) 
: _center(center) , _color (color) { }
초기화 목록에 _center(center)를 써 주면 컴파일러는 구성원 변수를 초기화할 때 _center의 기본 생성자가 아닌 복사 생성자를 쓰게 됩니다. _center는 구성원 변수 초기화 과정에서 이미 초기화가 끝났기 때문에, (위에서 1단계) 2단계인 클래스 생성자 함수 자체에서는 대입 연산이 필요하지 않은 것이지요.

생성자를 만들 때, 구성원 데이터를 모두 이런 식으로 초기화하려고 하세요. 대개 이런 식으로 모두 초기화가 가능하기 때문에, 정작 생성자 함수의 몸체에는 아무 코드도 필요하지 않을 수도 있습니다. 이렇게 하면 코드는 더욱 읽기 쉽고 유지하기도 쉬워집니다.

사실 int형과 같은 내장된 자료형에는 생성자 함수가 없습니다. 그렇기 때문에 기본 자료형 변수는 대입문을 쓰든 구성원 초기화를 쓰든 차이는 없습니다. 하지만 모든 구성원 변수를 구성원 초기화라는 같은 방법으로 값을 할당하면 코드가 더욱 이해하거나 유지하기 쉬워집니다.

정석 5.구성원 변수들이 어떤 순서로 초기화되는지를 주의깊게 살피십시오. d2라는 변수를 초기화하기 위해 d1을 사용한다면 실제로 d1이 d2보다 먼저 초기화되는지 확인하십시오.

StringHandle이라는 클래스에서 구성원 변수가 다음과 같은 순서대로 선언되었다고 합시다.

String _str;   int    _handle;
C++ 언어에서는 클래스의 구성원은 클래스에서 구성원이 나열됐던 순서대로 초기화되지 생성자 함수의 개체 초기화 목록에 있는 순서대로 초기화되는 게 아닙니다.(그러므로 _str은 _handle보다 먼저 초기화됩니다.)

다음과 같은 생성자는 정상적으로 컴파일되지만 실행중 에러를 발생시킬 것입니다.

StringHandle::StringHandle (const int h)
: _handle (h),_str(QueryHandle(_handle)) { }

_str이 클래스에서 먼저 나타났기 때문에 _str은 _handle보다 먼저 선언됩니다. 불행히도 _str은 생성 과정에서 생성되지 않은 _handle을 쓰게 되는 것입니다. 클래스에서 _handle을 _str 앞에 두면 이런 문제가 생기지 않습니다.

이 문제를 해결하려면 클래스 구성원 대신 생성자에 딸려 들어온 인자를 쓰면 됩니다.

StringHandle::StringHandle (const int h)
: _handle(h),_str (QueryStringHandle(h)) { }

초기화 목록에 있는 순서대로 초기화가 되지 않는 이유는 나중에 컴파일러가 개체들을 생성되었던 반대 순서로 소멸시킬 수 있게 하기 위해서입니다. 한 소스 파일에 클래스의 생성자와 소멸자 함수가 같이 있으리라는 보장은 없습니다. 그러나 두 함수 클래스 선언부는 같은 것을 참조하기 때문에 개체가 초기화된 순서를 알아내는 데는 모호함이 발생하지 않습니다.

정석 6.참조자 구성원과 const 개체는 생성자의 초기화 문법대로 초기화되어야 합니다.

연구실에서 구해낸 센서 자료를 관리하는 SensorData라는 클래스가 있습니다. 이 클래스는 여러 다른 센서 개체들과 더불어 자료 처리를 할 수 있기 때문에 생성자에서는 기존하는 센서의 참조자를 인자로 받습니다. 모든 센서에게 개방돼 있는 전역 데이터는 상수형 자료로 전달되지요.

참조자는 반드시 기존하는 개체를 가리키고 있어야 하기 때문에, 시작 부분에서 애초부터, 자료형이 같은 다른 개체를 가리키게끔 초기화되어야 합니다. 참조자는 일생 주기를 통틀어 대입 연산을 한 번만 할 수 있고, 상수형 개체도 초기화된 뒤부터는 값을 대입할 수 없습니다. 따라서 이 둘은 초기화 목록에서 초기화되어야 합니다. 다음은 SensorData의 생성자 함수로 가능한 모습입니다.

SensorData::SensorData (Sensor& aSensor const GlobalData data)
: _rSensor(aSensor), _global (data) { }

정석 7.생성자 함수를 최대한 간결하게 만드십시오.  함수 오버헤드나 개체가 생성되는 과정에서 발생할 수 있는 에러를 줄여야 하기 때문입니다.

생성자는 개체가 생성될 때마다 호출됩니다. 여러분이 모르는 사이에 이런 일은 컴파일러가 임시 개체를 만들어낼 때를 포함해 대단히 자주 일어납니다. 개체를 초기화하는 데 긴 시간이 걸려서는 안됩니다. 개체 생성시에는 꼭 필요한 일만 하십시오. 생성 과정을 줄이면 에러의 발생 가능성도 줄어들게 됩니다.

정석 8. 상속받은 클래스라면, 생성자 안에서 부모 클래스의 생성자 함수를 반드시 호출하도록 하세요.

생성자 함수(소멸자와 대입 연산자 함수도 포함하여)는 상속이 되지 않습니다. 클래스에서 생성자 함수가 호출되기 전, 그러니까 클래스 구성원을 초기화할 때는, 구성원 하나하나에 대해서 그 클래스에 해당하는 생성자 함수가 호출되고 본 클래스의 기반 클래스의 생성자도 호출됩니다. 4번 정석에서 보았듯이, 컴파일러는 초기화 목록을 보고서 어떤 생성자 함수를 호출할 지 결정합니다. 만약 프로그래머가 파생 클래스 생성자의 초기화 목록에서 기반 클래스 생성자를 호출하지 않으면, 컴파일러는 기반 클래스의 기본 생성자를 호출하는 코드를 자동으로 삽입합니다. 하지만 만약 기반 클래스가 기본 생성자 함수를 갖추고 있지 않다면 컴파일러는 에러를 낼 것입니다.

파생 클래스의 초기화 목록에서는 구성원 변수와 기반 클래스를 초기화해 주는 습관을 들이는 것이 좋습니다. 나는 보통 파생 클래스 자신의 구성원을 초기화하기 전에 기반 클래스의 생성자를 먼저 호출하곤 합니다.

Shape라는 클래스로부터 Triangle이란 클래스를 파생한다고 합시다.

class Triangle : public Shape
{
public:
Triangle (const Point center,
const int color,
const Point v1,
const Point v2,
const Point v3);
private:
Point _v1, _v2, _v3;
};
Triangle의 생성자에서는, Triangle의 초기화 목록에서 Shape의 생성자를 아래와 같이 호출할 수 있습니다.
Triangle::Triangle (const Point center,
const int color,
const Point v1,
const Point v2,
const Point v3)
: Shape (center, color) , _v1(v1), _v2(v2), _v3(v3) { }


소멸자

프로그램의 실행 위치가 어떤 개체의 생명 영역을 벗어나면 그 개체는 소멸하고, 그 개체가 차지하던 메모리는 해제됩니다. 개체가 소멸하기 직전에 그 개체의 소멸자 함수가 호출되며, 개체는 소멸자 함수 안에서 각종 마무리 작업을 할 수 있습니다. 클래스 X의 소멸자 함수는 X::~X()라는 꼴을 갖습니다.

정석 9.소멸자 함수는 이 개체가 생성됐던 순간뿐만 아니라 모든 시점에서 할당했던 모든 자원(메모리 등)을 해제해야 합니다.

메모리나 각종 자원의 할당은 개체가 생성되는 첫 시점에서 행해지는 게 보통입니다. 그러나 때에 따라서는, 그 개체로 어떤 동작이 가해지면 개체가 거기에 반응하는 과정에서 자원이 추가로 쓰일 수도 있습니다. 소멸자 함수를 코딩할 때는, 비단 생산될 때뿐만 아니라 이 개체가 살아 생전에 사용한 모든 자원을 반환· 해제하도록 하십시오.

정석 10.클래스에 반드시 소멸자를 갖추십시오. 컴파일러로 하여금 소멸자를 알아서 만들도록 하지 마십시오.

클래스가 동작하는 모습을 완전히 파악하고 있는 게 좋습니다. 컴파일러가 어떤 코드도 임의로 만들게 하지 마세요.

정석 11.만약 어떤 클래스가 상속 받아 상속받는 클래스의 기반 클래스로 쓰일 여지가 있다면 소멸자 함수를 가상 함수로 만드십시오. 또한 클래스에 가상 함수가 적어도 하나 이상 있다면 소멸자 함수 역시 가상 함수로 만들어야 합니다.

자동차 클래스에 대한 상속 관계를 생각해 봅시다. 상속 관계에 있는 모든 클래스가 Auto라는 클래스에서 상속을 받았습니다. Ford라는 개체가 다음과 같이 생성되었다면 생성자 함수는

Ford f;
생성자 함수는 기반 클래스의 것부터 파생 클래스의 것 순서로 호출됩니다. 그리고 개체가 소멸할 때 소멸자 함수는 그의 역순으로, 즉 파생 클래스의 것에서 기반 클래스 것 순서로 호출됩니다. 그런데 개체를 그 개체의 기반 클래스 포인터로 생성하고 소멸할 때 문제가 생깁니다.
Auto* car1 = new Ford;   // ...   delete car1;
Auto의 소멸자 함수가 가상함수가 아니기 때문에 delete문은 Ford::~Ford()가 아닌 Auto::~Auto()를 호출합니다. Auto::~Auto()를 가상함수로 선언하면 이 문제가 해결됩니다. 소멸자 함수를 가상함수로 만들면, delete에 기반 함수의 포인터가 넘겨지더라도, 그 포인터가 가리키는 개체의 실제 자료형에 맞는 소멸자가 호출되게 됩니다. 올바른 생성자 함수가 호출되고 나면, 기반 클래스에 대한 소멸자 함수는 위에서 설명한 바와 같은 규칙대로 호출되지요.

생성자와 소멸자를 작성하는 요령

다음은 생성자와 소멸자 함수 작성에 대해 적용할 수 있는 몇 가지 정석입니다.

정석 12.생성자와 소멸자 함수에서 가상함수를 호출하는 일을 삼가십시오.

생성자나 소멸자 함수 안에서도 다른 함수를 호출할 수 있습니다. 그런데 여기서 가상함수를 호출하면, 자신의 기반 클래스에서 정의된 가상함수, 혹은 자신이 새로 추가한 가상 함수는 적절히 호출되지만, 자신으로부터 상속한 새로운 파생 클래스가 새로 오버라이딩한 가상 함수는 절대 호출되지 않습니다. 이는 개체의 생성 과정에서, 아직 생성되지 않은 파생 클래스 개체를 기반 클래스가 건드리지 않기 위해서입니다. (기반 클래스의 생성자가 파생 클래스의 생성자보다 먼저 호출되지요.)

그리고 생성되거나 파괴되고 있는 개체의 순수 가상 함수를 직접 혹은 간접적으로 호출했을 때의 결과는 정해져 있지 않습니다. 올바른 결과가 나올 때란, 순수 가상 함수라도 특정 클래스의 것을 명시적으로 호출했을 때 뿐입니다. 예를 들어 A라는 클래스에 순수 가상 함수 p()가 있을 때, A::p()는 명시적인 호출이라 할 수 있습니다. 이 방식이 통하는 이유는, 명시적 호출은 가상함수 호출 메카니즘을 쓰지 않기 때문이지요.

대입 연산자

개체에 대입 연산을 시도하면 그 클래스의 대입 연산자가 호출됩니다. 클래스 X에 대한 대입 연산자는 다음 두 가지 형태 중 하나가 됩니다.

X& X::operator= (const X& x);   const X& X::operator= (const X& x);

정석 13.대입 연산자는 반드시 구성원 함수여야 하며, 친구(friend)가 될 수 없습니다.

어떤 연산자는 구성원 함수로 표현되는게 가장 좋고, 또 어떤 건 친구 함수로 표현되는 게 가장 좋습니다. 하지만 대입 연산자 함수는 선택의 여지가 없이 구성원 함수여야 합니다. 형태는 위의 두 가지 꼴 중 하나가 될 수 있죠.

정석 14.대입을 받은 개체의 참조자를 되돌림값으로 주도록 하세요.

참조자란 실제로 존재하는 개체를 비춰 보이는 거울과 같습니다. 참조자에 어떤 동작이 행해지면 참조자가 가리키는 실제 개체 역시 영향을 받습니다. 참조자란 간단히 말해 개체의 또다른 이름(별명)이라는 사실을 떠올리세요. (예를 들어 printf("%p %p \n",&,&)는 0xbffff870 0xbffff870를 출력합니다.).

참조자를 되돌리는 게 좋은 이유는 다음과 같습니다.

  1. (void형 되돌림값과 비교해서) 아래와 같이 여러 대입문이 연결될 수 있게 해 주기 때문입니다.
    Complex w, x, y, z;   w = x = y = z = Complex(0,0);
  2. (개체 자신을 되돌리는 것과 비교해서) 개체의 복사본을 또 생성하고 소멸하는 것보다 단순히 참조자만을 되돌리는 게 효율적이기 때문입니다.

대입이 연달아 되게 하기 위해서는 대입 연산자 함수의 인자와 되돌림의 자료형이 모두 같아야 합니다. 그래서 자료형이 같은 참조자를 되돌리고 전달하는 것입니다. 보통 return *this; 라고 하지요. 이렇게 하면 대입 연산의 결과가 다음 대입 연산 함수의 인자로 쓰일 수 있게 됩니다.

참조자를 되돌리는 것은 개체 자신을 직접 되돌리는 것보다 효율적입니다. 참조자가 넘겨지면 기본적인 함수 호출 방식인 "값으로 호출" (call-by-value) 메카니즘은 무시됩니다. 인자의 복사본을 받는 대신 함수는 실제 인자의 l-value를 받게 됩니다. 아무 개체도 복사되지 않으며, 참조자는 개체 전체보다 스택도 적게 차지합니다. 이와 마찬가지로 참조자가 되돌려졌을 때에도 개체 자체가 아닌 l-value가 되돌려집니다.

정석 15. 반드시 함수의 참조자는 상수형(const)으로 해서 되돌리십시오. 일반 참조자를 되돌리면 엉뚱한 결과가 생길 수 있습니다.

참조자를 되돌리는 것은 l-value(값을 대입할 수 있는 메모리 주소를 갖는 개체)를 되돌리는 것과 같습니다. 이것은 개체에 대한 포인터를 얻는 것과 비슷합니다. 그러므로 개체 참조자를 통해 개체를 변경할 수 있는 것입니다.

여기에 예를 들어 보겠습니다. 대입 연산은 오른쪽에서 왼쪽으로 진행되지만 아래에서는 괄호를 써서 우선 순위를 바꿨습니다.

Complex x, y, z;   (x = y) = z;
위의 문장은 다음과 같습니다.
(x.operator=(y)).operator=(z);
무슨 뜻인가요? 먼저 y의 값이 x에 대입됩니다. 하지만 =연산자가 x의 참조자를 되돌리기 때문에 x에는 대입 연산이 한번 더 일어납니다. z의 값이 대입되지요. 하지만 =가 상수형 참조자를 되돌리면 그 참조자가 참조하는 개체가 또다시 수정되는 일을 막을 수 있습니다. (상수 개체를 대상으로 대입을 할 수 없다고 컴파일할 때 에러가 납니다.) 개체가 예상치 못한 일로 변경되는 것을 막으려면 상수형 참조자를 되돌리도록 하십시오.

다만, 상수형이 아닌 일반 참조자를 되돌려야 하는 경우가 딱 하나 있습니다. 첨자 연산자는 일반 참조자를 되돌려야 색인 번호가 매겨진 개체에 다른 값을 대입할 수 있기 때문입니다. 벡터의 좌표를 바꾸는 코드를 작성하고 싶다고 가정해 봅시다.

Vector v;   v[1] = -2.0;
이 코드는 다음과 같은 꼴이 됩니다.
(v.operator[](1)).operator= (-2.0);
이 코드가 동작하도록 하게 위해서는 첨자 연산자 함수가 일반 참조자를 되돌리게 하면 됩니다.
float& Vector::operator[] (const int)   {      return _data[i];   }
되롤림값은 원소의 l-value(대입이 가능한 개체)이므로 대입 연산의 대상이 될 수 있는 것입니다.

정석 16.자기를 자기 자신에게 대입하지는 않는지 검사하십시오.

개체가 자기 자신으로 대입될 경우 불미스런 일이 생길 수 있습니다.

X x;   x = x;
클래스 X가 포인터 데이터 구성원을 가지고 있어 개체가 만들어질 때 메모리가 동적으로 할당된다고 생각해 봅시다. 대입 작업은 언제나 존재하는 개체의 변경을 수반합니다. 그러므로 이미 있던 동적 할당 데이터는 새 값이 들어오기 전에 먼저 사라집니다. 자기 자신을 대입하는지 검사하지 않고 무작정 대입한다면 대입 연산은 자기 자신의 포인터 데이터를 지우고 이전에 지워졌던 값을 다시 대입하려는 시도가 되고 마는 것입니다.

자기 자신으로 대입하는 게 아닌지 아래 예처럼 꼭 검사하십시오.

const X& X::operator= (const X& rhs)
{
if ( &rhs != this )
{ //각 구성원에 값을 할당합니다. }
return *this;
}

정석 17.대입연산자는 상속 받지 않는 유일한 연산자입니다. 기반 클래스의 구성원도 할당 과정에서 값을 바꾸도록 하십시오.

파생 클래스의 대입 연산자 함수는 자기가 갖고 있는 구성원 뿐만 아니라 기반 클래스의 구성원까지 반드시 고쳐야 합니다. 그런데 기반 클래스의 구성원이 private로 선언돼 있어서 파생 클래스에서 그걸 건드릴 수 없는 경우가 있습니다. 그럴 때는 기반 클래스의 대입 연산자 함수를 파생 클래스의 대입 연산자 함수에서 직접 호출해야 합니다.

Base라는 클래스가 Derived의 기반 클래스라고 칩시다. Derived형 개체에 대입이 일어나면, Base 클래스도 이 때 대입 연산이 행해져야 클래스 전체의 내용이 바뀝니다. 그래서 Derived 클래스의 대입 연산자 함수는 아래와 같이, 기반 클래스의 대입 연산자를 직접 호출할 수도 있고, (d가 피연산자라고 합시다.)

Base::operator= (d);
실질적인 연산구문으로 이 작업을 할 수도 있습니다.
((Base &) *this) = d;
언뜻 보기 이상한 이 구문은, *this를 기반 클래스의 참조자로 바꾸고, 그 결과에 대해서 대입 연산을 행합니다. 그냥 Base가 아니라, 꼭 Base의 참조자로 형변환을 해야 합니다. 참조자 대신 Base로 *this를 형변환하면, Base 클래스의 복사 생성자가 호출되고, 연산 결과는 this 자신이 아니라 새로운 임시 개체가 됩니다. 따라서 대입은 *this가 아닌 임시 개체로 행해지고, *this는 값이 바뀌지 않게 되죠.

정석 18.클래스에 자체적인 대입 연산자 함수를 꼭 만들어 두십시오. 컴파일러가 기본 함수를 만들도록 하지 마세요.

프로그래머가 대입 연산자 함수를 만들지 않으면 컴파일러는 기본 함수(모든 구성원을 똑같이 대입하는 일만 함)를 알아서 만듭니다. 여러분의 클래스가 어떻게 돌아가는지 완전히 파악하고 있기 위해서는 항상 대입 연산자 함수를 만들도록 하십시오.

정석 19.클래스에 포인터 데이터(동적 메모리 할당)가 있으면 대입 연산자를만들어야 합니다. 그리고 대입 연산자를 코딩한다면 복사 생성자도 만들어야 합니다.

컴파일러가 기본적으로 만든 대입 연산자 함수는 각 구성원을 차례대로 복사하는 작업만 합니다. 그러므로 구성원 중에 포인터가 있다면 이를 그대로 둬서는 안되는 경우가 절대 다수입니다. 두 개 이상의 개체가 똑같이 한 메모리 영역을 가리키고 있는데, 한 개체가 그 메모리 영역을 해제한다면, 다른 개체가 그 메모리를 건드릴 때 런타임 에러가 나겠지요.

정석 20.대입 연산자와 복사 생성자가 공통적으로 수행하는 작업을 따로 함수로 만들어 이들이 그 함수를 호출하게 하십시오. 이렇게 하면 코드의 중복이 사라져 보기가 더 깔끔해집니다.

문자열 클래스는 복사 생성이나 대입 연산 과정에서 문자열을 복사한다는 공통된 작업을 해야 합니다. 그래서 겹치는 작업을 다음과 같이 비공개 구성원 함수에다 두면 복사 생성자 함수와 대입 연산자 함수가 그 코드를 중복 없이 공유할 수 있을 것입니다.

void CopyString(const char* ptr);

다음에 우리는 클래스에서 연산자 함수를 구현하는 데에 도움이 되는 내용을 살펴볼 것입니다. 독자 여러분은 그때까지, 자신이 이미 만들어놓은 클래스를 살펴 보시기 바랍니다. 그리고는 생성자, 소멸자, 대입 연산자 함수가 잘 동작하고 있는지 살펴보세요. 여기 실린 정석은 여러분이 더 효율적인 코드를 작성하는 데 도움이 될 것입니다.

참고 자료

1
COPLIEN, J. O.Advanced C++ Programming Styles and Idioms. Addison-Wesley, Reading, Mass., 1992.

2
ELLIS, M. A., AND STROUSTRUP, B.The Annotated C++ Reference Manual. Addison-Wesley, Reading, Mass., 1990.

3
Journal of Object-Oriented Programming. SIGS Publications, New York.

4
MURRAY, R. B.C++ Strategies and Tactics. Addison-Wesley, Reading, Mass., 1993.

5
MYERS, S.Effective C++. Addison-Wesley, Reading, Mass., 1992.

저작권 1995G. Bowden Wise 지음

크로스로드 1.4, 1995년 5월

C++에 대해 더 자세히 알고 싶다면, 본 시리즈의차례,다음 내용,이전 내용을 살펴보십시오.

고치고 더함: 2000년 2월 4일 금요일, 11:17:16
이 사이트의 원위치: www.acm.org/crossroads/xrds1-4/ovp.html

ⓒ 저작권 2000, ACM, Inc.

and

1. tcpdump
 

 

 

 

tcpdump는 네트워크 인테페이스 상에 있는 패킷중에서 명시된 expression에 일치하는 것만을 선별하여 그 패킷의 헤더 정보를 보여주는 도구 이다.
이프로그램을 컴파일하여 설치하기 위해서는 libpcab(packet capture library)이 먼저 설치되어있어야 한다.

참고로 시모무라 소토무가 케빈 미트닉을 잡을 때 사용했던 프로그램으로도 유명하다.
이프로그램은 네트워크 상에서 돌아다니는 모든 패킷을 캡처함으로서 여러분들에게 유용한 정보를 보여준다.

 

 

 

2.소스 가져오기
 

 

 

 

 

 

 

3. 설치
 

 

 

 

 libpcap-0.6.2설치

shell> gunzip libpcap-0.6.2.tar.gz
shell> tar xvf libpcap-0.6.2.tar
libpcap-0.6.2/./
libpcap-0.6.2/./CVS/
libpcap-0.6.2/./CVS/Root
libpcap-0.6.2/./CVS/Repository
libpcap-0.6.2/./CVS/Entries
libpcap-0.6.2/./CVS/Tag

<생략>

shell> ./configure
creating cache ./config.cache
checking host system type... i586-pc-linux-gnu
checking target system type... i586-pc-linux-gnu
checking build system type... i586-pc-linux-gnu

<생략>
shell> make
gcc -O2 -I.  -DHAVE_CONFIG_H -c ./pcap-linux.c
gcc -O2 -I.  -DHAVE_CONFIG_H -c ./pcap.c
gcc -O2 -I.  -DHAVE_CONFIG_H -c ./inet.c

<생략>
shell> make install
[ -d /usr/local/lib ] || \
    (mkdir -p /usr/local/lib; chmod 755 /usr/local/lib)
/usr/bin/install -c -m 644 libpcap.a /usr/local/lib/libpcap.a ranlib /usr/local/lib/libpcap.a
[ -d /usr/local/include ] || \
    (mkdir -p /usr/local/include; chmod 755 /usr/local/include)

/usr/bin/install -c -m 644 ./pcap.h /usr/local/include/pcap.h
/usr/bin/install -c -m 644 ./pcap-namedb.h \
    /usr/local/include/pcap-namedb.h
[ -d /usr/local/include/net ] || \
    (mkdir -p /usr/local/include/net; chmod 755/usr/local/include/net)
/usr/bin/install -c -m 644 ./bpf/net/bpf.h \
    /usr/local/include/net/bpf.h
[ -d /usr/local/man/man3 ] || \
        (mkdir -p /usr/local/man/man3; chmod 755/usr/local/man/man3)
/usr/bin/install -c -m 644 ./pcap.3 \
    /usr/local/man/man3/pcap.3

shell> 

 

 tcpdump-3.6.2설치

shell> gunzip tcpdump-3.6.2.tar.gz
shell> tar xvf tcpdump-3.6.2.tar
tcpdump-3.6.2/./
tcpdump-3.6.2/./CVS/
tcpdump-3.6.2/./CVS/Root
tcpdump-3.6.2/./CVS/Repository
tcpdump-3.6.2/./CVS/Entries
tcpdump-3.6.2/./CVS/Tag
<생략>
shell> cd tcpdump-3.6.2
shell> ./configure
creating cache ./config.cache
checking host system type... i586-pc-linux-gnu
checking target system type... i586-pc-linux-gnu
checking build system type... i586-pc-linux-gnu
checking for gcc... gcc
checking whether the C compiler (gcc  ) works... yes
checking whether the C compiler (gcc  ) is a cross-compiler... no
<생략>
shell> make
gcc -O2 -DHAVE_CONFIG_H -I. -I../libpcap-0.6.2  -I./missing -I/usr/local/ssl/inc
lude -I/usr/local/ssl/include/openssl -c ./tcpdump.c
gcc -O2 -DHAVE_CONFIG_H -I. -I../libpcap-0.6.2  -I./missing -I/usr/local/ssl/inc
lude -I/usr/local/ssl/include/openssl -c ./print-arp.c
gcc -O2 -DHAVE_CONFIG_H -I. -I../libpcap-0.6.2  -I./missing -I/usr/local/ssl/inc lude -I/usr/local/ssl/include/openssl -c ./print-atalk.c
<생략>
shell> make install
[ -d /usr/local/sbin ] || \
    (mkdir -p /usr/local/sbin; chmod 755 /usr/local/sbin)
/usr/bin/install -c tcpdump /usr/local/sbin/tcpdump
[ -d /usr/local/man/man1 ] || \
    (mkdir -p /usr/local/man/man1; chmod 755/usr/local/man/man1)
/usr/bin/install -c -m 644 ./tcpdump.1 /usr/local/man/man1/tcpdump.1
shell>

 

 

 

4. 사용법
 

 

 

 

tcpdump [ -adeflnNOpqStvx ] [ -c count ] [ -F file ] [ -i interface ] [ -r file ] [ -s snaplen ] [ -T type ] [ -w file ]  [ expression ]

 <Option>

-a :

Network & Broadcast 주소들을 이름들로 바꾼다.

-c :

Number : 제시된 수의 패킷을 받은 후 종료한다

-d :

comile된 packet-matching code를 사람이 읽을 수 있도록 바꾸어 표준 출력으로 출력하고, 종료한다.

-dd :

packet-matching code를 C program의 일부로 출력한다.

-ddd :

packet-matching code를 숫자로 출력한다.

-e :

출력되는 각각의 행에 대해서 link-level 헤더를 출력한다.

-f :

외부의 internet address를 가급적 심볼로 출력한다(Sun의 yp server와의 사용은 가급적 피하자).

-F file :

filter 표현의 입력으로 파일을 받아들인다. 커맨드라인에 주어진 추가의 표현들은 모두 무시된다.

-i device :

어느 인터페이스를 경유하는 패킷들을 잡을지 지정한다. 지저되지 않으면 시스템의 인터페이스 리스트를 뒤져서 가장 낮은 번호를 가진 인터페이스를 선택한다(이 때 loopback은 제외된다).

-l :

표준 출력으로 나가는 데이터들을 line buffering한다. 다른 프로그램에서 tcpdump로부터 데이터를 받고자 할 때, 유용하다.

-n :

모든 주소들을 번역하지 않는다(port,host address 등등)

-N :

호스트 이름을 출력할 때, 도메인을 찍지 않는다.

-O :

packet-matching code optimizer를 실행하지 않는다. 이 옵션은 optimizer에 있는 버그를 찾을 때나 쓰인다.

-p :

인터페이스를 promiscuous mode로 두지 않는다.

-q :

프로토콜에 대한 정보를 덜 출력한다. 따라서 출력되는 라인이 좀 더 짧아진다.

-r <file>:

패킷들을 '-w'옵션으로 만들어진 파일로 부터 읽어 들인다. 파일에 "-" 가 사용되면 표준 입력을 통해서 받아들인다.

-s <spaplen>:

패킷들로부터 추출하는 샘플을 default값인 68Byte외의 값으로 설정할 때 사용한다(SunOS의 NIT에서는 최소가 96Byte이다). 68Byte는 IP,ICMP, TCP, UDP등에 적절한 값이지만 Name Server나 NFS 패킷들의 경우에는 프로토콜의 정보들을 Truncation할 우려가 있다. 이 옵션을 수정할 때는 신중해야만 한다. 이유는 샘플 사이즈를 크게 잡으면 곧 패킷 하나하나를 처리하는데 시간이 더 걸릴 뿐만아니라 패킷 버퍼의 사이즈도 자연히 작아지게 되어 손실되는 패킷들이 발생할 수 있기 때문이다. 또, 작게 잡으면 그만큼의 정보를 잃게되는 것이다. 따라서 가급적 캡춰하고자 하는 프로토콜의 헤더 사이즈에 가깝게 잡아주어야 한다.

-T <type> : 조건식에 의해 선택된 패킷들을 명시된 형식으로 표시한다. type에는 다음과 같은 것들이 올 수 있다. rpc(Remote Procedure Call), rtp(Real-Time Applications protocol), rtcp(Real-Time Application control protocal), vat(Visual Audio Tool), wb(distributed White Board)

-S :

TCP sequence번호를 상대적인 번호가 아닌 절대적인 번호로 출력한다.

-t :

출력되는 각각의 라인에 시간을 출력하지 않는다.

-tt :

출력되는 각각의 라인에 형식이 없는 시간들을 출력한다.

-v :

좀 더 많은 정보들을 출력한다.

-vv :

'-v'보다 좀 더 많은 정보들을 출력한다.

-w :

캡춰한 패킷들을 분석해서 출력하는 대신에 그대로 파일에 저장한다.

-x :

각각의 패킷을 헥사코드로 출력한다.

<expression>

expression은 패킷을 선택적으로 캡처하기 위한 조건을 적는 부분이다. expression이 없다면 네트워크상의 모든 패킷을 캡처하여 보여 주며expression이 정의 되어있다면 이 expression을 참으로 하는 패킷만을 선별하여 보여준다. expression은 하나이상이 primitives로 구성되며, primitives는 일반적으로qualifer을 앞에 내세운 id(이름이나 숫자)로 구성된다.

[Qualifier]

type :

id(이름이나 숫자)가 어떠한 의미를 가지는지를 알려준다. 가능한 type으로는 host, net, port가 있다.

 

ex) " host foo", "net 123.4", "port 23"      특별한 qualifier가 정해져 있지 않으면 "host"로 가정한다.

dir   :

전송방향을 결정한다. 가능한 방향은 "src", "dst", "src or dst", "src and dst"이다

 

ex) "src foo", "dst net 123.4", "src or dst port ftp"   특별한 qualifier가 정해져 있지 않으면 "src or dst"로 가정한다.

proto :

특정한 프로토콜의 패킷만을 받아들이게 한다. 가능한 proto로는 "either", "fddi", "ip" ,"arp", "rarp", "decnet", "lat", "sca", "moprd", "mopdl", "tcp","udp"가 있다.

 

ex) "either src foo", "arp net 123.4", "tcp port 23" 특별한 qualifier가 정해져 있지 않으면 "src or dst"로 가정한다.

[primitives]

dst host <host> :

패킷의목적지 필드에 있는 IP주소와 <host> (hostname, name 둘다 가능)가 일치하면 참.

src host <host> :

패킷의 발신지 필드에 있는 IP주소와 <host> (hostname, name 둘다 가능)가 일치하면 참.

host <host> :

패킷의 목적지 IP나 발싲니 IP중 어느하나라도 <host>와 일치하면 참.

ether dst <ehost> :

패킷의 목적지 ethernet 주소(48bit의 주소로 일반적으로 X:X:X:X:X;X로 표시된다.여기서 X는 16진수)가<ehost>와 일치하면 참

ether dst <ehost> :

패킷의 목적지 ethernet 주소가  <ehost>와 일치하면 참.

gateway <ehost> :

 <host>가 gateway로 쓰일 때 참. 즉, ethernet발신지 혹은 목적지 주소는 <host>이나 IP 발신지 혹은 목적지 주소로 <host>는 될 수 없다는 것을 의미한다. <host>는 반드시 /etc/hosts와 /etc/ethers에서 찾을 수 있어야 한다.이 expression은 "ether host <ehost> and not host <host>"와 동일한 의미를 가진다.

dst net <net> :

패킷의 IP목적지 주소가 <net>과 동일한 테트워크 번호(network number)를 가지면 참.

 

src net <net> : 패킷의 IP발신지 주소가 <net>과 동일한 테트워크 번호(network number)를 가지면 참.

net <net> :

패킷이 IP목적지 혹은 발신지 주소 둘 중 어느 하나라도 <net>과 동일한 network number를 가지면 참 .

net <net> mask <mask> :

IP주소가 지정된 netmask(<mask>)를 통하여 network number <net>와 일치하면 참.

net <net>/<len> :

IP주소가 network number <net>과 netmaks의 <len>비트만큼 일치하면 참.

dst port <port> :

패킷이 IP혹은 UDP 이면서 목적지 port값으로 <port>를 가지고 있다면 참. <port>는 숫자나 /etc/services에 정의 되어있는 port이름을 사용할 수 있다.

src port <port> :

패킷이 IP혹은 UDP 이면서 발신지 port값으로 <port>를 가지고 있다면 참.

port <port> :

패킷의 목적지 혹은 발신지 port값에서 둘주 어느하나라도 <port>와 같으면 참이된다. 위의 port expression들은 "tcp"나 "udp"와 함께쓰일 수 있다.
  ex) tcpdump tcp src port 23  -tcp패킷중에서 발신지 port 23인 것만 검출하여 보내준다.

less <length> :

패킷의 길이가 <length>Byte보다 작거나 같으면 참이된다.

greater <length> :

패킷의 길이가 <length>Byte보다 크거나 같으면 참이된다.

ip proto <protocol> :

패킷이 <protocol> protocol형태의 ip패킷이면 참. <protocol>에는 icmp, igrp, udp , tcp이름이 들어갈 수 있다. tcp, udp,icmp는 키워드로도 사용되기 때문에 <protocol>의 값이 되기 위해서는 앞에 backslash를 붙여준다.

ether broadcast :

패킷이 ethernet broadcast패킷이면 참. "ehter" keyword는 생략 가능.
ip broadcast : 패킷이 ip broadcast패킷이면 참.

ether multicast :

패킷이 ethernet multicast 패킷이면 참이된다. "ether"keyword 는 생략가능.

ip multicast :

패킷이 ip multicast패팃이면 참.

ether proto <protocol> :

패킷이 <protocol>의 ether형태라면 참. <protocol>의값으로는 숫자나 ip, arp, rarp와 같은 이름이 될수있다.

decnet src <host> :

DECNET발신지 주소가 <host>이면 참이 된다. <host>의 값으로는 "10.123"나 DECNET호스트 이름이 될수있다.

decnet dst <host> :

DECNET목적지 주소가 <host>이면 참.

decnet host <host> :

DECNET목적지혹은 발신지 주소중에서 어느 하나라도 <host>와 일치하면 참.

ip, arp, rarp, decnet :

"ether proto <p>"의 축약형이다. 여기서 <p>값은 위의 protocol중의 하나가된다.

tcp, udp, icmp :

"ip proto <p>"의 축약형이다. 여기서 <p>값은 위의 protocol중 하나가된다.

간단한 expression들을 다음의 관계연산자를 통하여 묶음으로서 더욱 복잡한 filter expression을 구성할 수 있다.

    Nagation : "!", "not"
    Concatenation : "&&", "and"
    Alternation : "||", "or"

 

 

 

 

5. tcp_dump의 사용
 

 

 

 

 

shell> tcpdump -t
tcpdump: listening on eth0
0:a0:c9:4:2e:48 sap c4 > 0:60:b0:f2:5f:b sap b4 sabme/Plen=43
arp who-has 143.248.1.200 (6:6b:e8:c0:4:11) tell kaist-gw.kaist.ac.kr
211.205.0.142.1432 > gaya.kaist.ac.kr.telnet: . ack 4254948997 win 15826 (DF)
gaya.kaist.ac.kr.telnet > 211.205.0.142.1432: P 1:93(92) ack 0 win 32522 (DF)
arp who-has ns.kaist.ac.kr tell gaya.kaist.ac.kr
arp reply ns.kaist.ac.kr is-at 8:0:20:cc:66:1f
gaya.kaist.ac.kr.1043 > ns.kaist.ac.kr.domain:  17263+[|domain]
ns.kaist.ac.kr.domain > gaya.kaist.ac.kr.1043:  17263 NXDomain*[|domain] (DF)
gaya.kaist.ac.kr.1043 > ns.kaist.ac.kr.domain:  17264+[|domain]
ns.kaist.ac.kr.domain > gaya.kaist.ac.kr.1043:  17264*[|domain] (DF)
gaya.kaist.ac.kr.1043 > ns.kaist.ac.kr.domain:  17265+[|domain]
ns.kaist.ac.kr.domain > gaya.kaist.ac.kr.1043:  17265 NXDomain[|domain] (DF)
gaya.kaist.ac.kr.1043 > ns.kaist.ac.kr.domain:  17266+[|domain]
ns.kaist.ac.kr.domain > gaya.kaist.ac.kr.1043:  17266*[|domain] (DF)

Example 1)
(1. icmp패킷만 출력한다. 2. 패킷의 수신지 혹은 발신지가apple 인 것을 출력한다. 3. 패킷의 수신지 혹은 발신지가 sun이나 moon이면 출력하지 않는다.)

    shell> tcpdump ip proto '\icmp && host apple && !(sun || moon)'
    or
    shell> tcpdump ip proto \\icmp and host apple and not \(sun || moon \))

Example 2)
패킷의 발신지가apple이고 수신지는 banana가 아닌 패킷을 출력한다.

    shell> tcpdump src host apple and not dst host banana

 Example 3)
echo requests/replies가 아닌 모든 ICMP패킷을 출력한다.

    shell> tcpdump 'icmp[0] != 8 and icmp[0] != 0"

Example 4)
(tcp 프로토콜 통신에서 123.45.67.89가 수신지나 발신지가 아닌 모든 SYN , FIN 패킷을 출력한다)

    shell> tcpdump 'tcp[13] & 3 != 0 and not net 123.45.67.89'

     

     

    **********************************8

    위의 내용은 초고속연구망실무자 협의회에서 발췌한 글이며..

    저작권 문제가 있을경우 바로 삭제 하겠습니다.

'2010년 이전 글 > computer' 카테고리의 다른 글

한글 putty 0.58  (1) 2008.11.04
TCPDUMP 사용법  (0) 2007.10.30
블루스크린 ErrorCode Define 값입니다.  (0) 2007.09.21
HP-UX 쉘 명령어 총정리  (0) 2007.08.22
gcc와 make 강좌  (0) 2007.05.01
and

http://security.kaist.ac.kr/docs/tcpdump.html

 

 

 

 

 

TCPDUMP User Guide
 
 

Date : January 1999

written byKwon, YongChul

-. 이 문서의 저작권은 저자(권용철:godslord@sparcs.kaist.ac.kr)에게 있습니다. 본문의 무단 인용, 복사는 저자와의 협의 없이 절대 불가능합니다.


Table of Contents


  • 이 문서를 보기 전에 알아야 할 사전 지식

    이 문서는 네트워크의 패킷들을 잡아내는 Tcpdump라는 프로그램에 대해서 설명하고 있다. 프로그램의 특성상, 네트워크에 관련된 많은 용어들과 특히 TCP/IP에 대한 내용 이 많이 나온다. 따라서 보다 잘 이해하기 위해서는 네트워크 설비 자료나, 네트워크 프로그래밍 가이드, 혹은 네트워크 프로토콜(TCP/IP)에 관련된 책들을 참조하는 것이 좋을 것이다.


  • Tcpdump란?

    Tcpdump는 주어진 조건식을 만족하는 네트워크 인터페이스를 거치는 패킷들의 헤더들 을 출력해 주는 프로그램이다. 프로그램의 특성상, 네트워크 인터페이스를 아주 심도 있게 사용하기 때문에, 실행하는 사람은 반드시 네트워크 인터페이스에 대한 읽기 권 한이 있어야만 한다.
    OS dependent)
    위에서 말하는 읽기 권한을 가지고 있어야 하는 파일, 혹은 Tcpdump의 퍼미션 이다.

    • SunOS : /dev/nit, /dev/bpf*
    • Solaris(SunOS 5.x) : /dev/le 등
    • HP-UX & IRIX & Linux : root에서 실행하거나, root로 setuid를 설정해야 함
    • Ultrix & Digital UNIX : root가 pfconfig를 이용하여, promiscuous-mode를 가능하게 설정하면 어떤 유저라도 사용할 수 있다.
    • BSD : /dev/bpf*


  • Tcpdump의 패키지 구하기

    Tcpdump는ftp://ftp.ee.lbl.gov/tcpdump.tar.Z에서 최신 버전을 구할 수 있다. 유명 한 프로그램이기 때문에, 시스템소프트웨어를 패키지형태로 제공해 주는 OS들의 경우 Vendor에서 패키징된(컴파일된) 버전으로도 구할 수 있을 것이다.


  • Tcpdump의 설치

    Tcpdump는 libpcap(Protocol Capture Library)라는 것을 사용한다. 이 라이브러리는 Platform에 상관없이 동일한 방법으로 사용자 레벨에서 패킷들을 캡춰할 수 있게 해 준다. 따라서 이 라이브러리가 없다면,ftp://ftp.ee.lbl.gov/libpcap.tar.Z에서 구하 여 설치하도록 한다.

    ANSI C 컴파일러는 아마 대부분의 시스템에서 구비하고 있을 것이다. 만약 없다면ftp://prep.ai.mit.edu/pub/gnu/gcc.tar.gz를 받아서 설치하기 바란다.

    libpcap라이브러리가 완벽하게 설치되었다는 가정하에서 다음의 절차에 따라 설치를 시작한다.

    • Makefile.in의 BINDEST와 MANDEST 항목에 각각, tcpdump 실행파일과 메뉴얼 페이 지가 설치될 디렉토리들을 입력해 준다.
    • Tcpdump 패키지와 함께 제공되는 ./configure 스크립트를 실행시킨다. 이 스크립트는 현재 시스템의 환경들을 검사하고 이에 맞추어서 Makefile을 생성해 준다.
    • make를 실행한다.
    • 컴파일이 다 됐으면, make install을 수행하여 실행파일을 설치하고, make install-man을 실행하여 메뉴얼 페이지도 설치한다.
    • tcpdump의 퍼미션이 제대로 되었는지를 검사한다. setgid가 설정되어 있기 때문에, 원하지 않는 사람이 실행하게 된다면 위험하다.
    위에서 설명한 절차를 그대로 옮겨 본다.
    # vi Makefile.in# ./configure# make# make install# make install-man
    OS dependent)
    • DEC/OSF and BSD/386, etc : tcpdump가 OS와 함께 제공되는 경우가 있다. 이럴 경우 tcpdump를 업그레이드 하기 전에 반드시 기존의 tcpdump를 백업해 두도록 하자.
    • Linux : libpcap 라이브러리는 2.0.25 커널에서 테스트 됐다. 2.0.x 대 커널 에서 25이하의 버전에서는 실행이 아마 가능할 것이다. 그러나 1.x대의 커널에서는 동작 여부가 입증되지 않았으므로 주의해야 한다.
    • OSF 4 : OSF4에서는 stack C 컴파일러에 치명적인 버그가 있다. 이 버그를 피해가려면, ./configure를 실행한 후 컴파일 하기 전에 Makefile에서 다음의 문장을 삭제한 후 컴파일해 주면 된다.
    -DETHER_HEADER_HAS_EA=1 -DETHER_ARP_HAS_EA=1


  • Tcpdump Source의 간략한 설명


  • Tcpdump의 옵션들

    • -a: Network & Broadcast 주소들을 이름들로 바꾼다.
    • -cNumber : 제시된 수의 패킷을 받은 후 종료한다.
    • -d: comile된 packet-matching code를 사람이 읽을 수 있도록 바꾸어 표준 출력으로 출력하고, 종료한다.
    • -dd: packet-matching code를 C program의 일부로 출력한다.
    • -ddd: packet-matching code를 숫자로 출력한다.
    • -e: 출력되는 각각의 행에 대해서 link-level 헤더를 출력한다.
    • -f: 외부의 internet address를 가급적 심볼로 출력한다(Sun의 yp server와의 사용은 가급적 피하자).
    • -F file: filter 표현의 입력으로 파일을 받아들인다. 커맨드라인에 주어진 추가의 표현들은 모두 무시된다.
    • -i device: 어느 인터페이스를 경유하는 패킷들을 잡을지 지정한다. 지저되지 않으면 시스템의 인터페이스 리스트를 뒤져서 가장 낮은 번호를 가진 인터페이스를 선택한다(이 때 loopback은 제외된다).
    • -l: 표준 출력으로 나가는 데이터들을 line buffering한다. 다른 프로그램에서 tcpdump로부터 데이터를 받고자 할 때, 유용하다.
    • -n: 모든 주소들을 번역하지 않는다(port,host address 등등)
    • -N: 호스트 이름을 출력할 때, 도메인을 찍지 않는다.
    • -O: packet-matching code optimizer를 실행하지 않는다. 이 옵션은 optimizer에 있는 버그를 찾을 때나 쓰인다.
    • -p: 인터페이스를 promiscuous mode로 두지 않는다.
    • -q: 프로토콜에 대한 정보를 덜 출력한다. 따라서 출력되는 라인이 좀 더 짧아진다.
    • -r file: 패킷들을 '-w'옵션으로 만들어진 파일로 부터 읽어 들인다. 파일에 "-" 가 사용되면 표준 입력을 통해서 받아들인다.
    • -s length: 패킷들로부터 추출하는 샘플을 default값인 68Byte외의 값으로 설정할 때 사용한다(SunOS의 NIT에서는 최소가 96Byte이다). 68Byte는 IP,ICMP, TCP, UDP등에 적절한 값이지만 Name Server나 NFS 패킷들의 경우에는 프로토콜의 정보들을 Truncation할 우려가 있다. 이 옵션을 수정할 때는 신중해야만 한다. 이유는 샘플 사이즈를 크게 잡으면 곧 패킷 하나하나를 처리하는데 시간이 더 걸릴 뿐만아니라 패킷 버퍼의 사이즈도 자연히 작아지게 되어 손실되는 패킷들이 발생할 수 있기 때문이다. 또, 작게 잡으면 그만큼의 정보를 잃게되는 것이다. 따라서 가급적 캡춰하고자 하는 프로토콜의 헤더 사이즈에 가깝게 잡아주어야 한다.
    • -T type: 조건식에 의해 선택된 패킷들을 명시된 형식으로 표시한다. type에는 다음과 같은 것들이 올 수 있다. rpc(Remote Procedure Call), rtp(Real-Time Applications protocol), rtcp(Real-Time Application control protocal), vat(Visual Audio Tool), wb(distributed White Board)
    • -S: TCP sequence번호를 상대적인 번호가 아닌 절대적인 번호로 출력한다.
    • -t: 출력되는 각각의 라인에 시간을 출력하지 않는다.
    • -tt: 출력되는 각각의 라인에 형식이 없는 시간들을 출력한다.
    • -v: 좀 더 많은 정보들을 출력한다.
    • -vv: '-v'보다 좀 더 많은 정보들을 출력한다.
    • -w: 캡춰한 패킷들을 분석해서 출력하는 대신에 그대로 파일에 저장한다.
    • -x: 각각의 패킷을 헥사코드로 출력한다.

    조건식(expression)

    옵션의 제일 마지막인 조건식은 어떤 패킷들을 출력할지를 선택하는데 쓰인다. 조건식이 주어지지 않는 다면 모든 패킷들이 그 대상이 될 것이다. 일단 주어지면, 아무리 패킷들이 많아도 조 건식에 부합하는 패킷만을 출력한다.

    조건식들은 하나 또는 몇 개의 primitive들로 구성되어 있다. primitive들은 보통 하나 혹은 몇개의 qualifier들 다음에 오는 하나의 값으로 이루어진다. Qualifier들은 모두 3 종류이며 다음과 같다.

    • type : 주어진 값의 종류가 무엇인지를 나타낸다. 가능한 type들은 'host', 'net', 'port'가 있다. type이 없는 값들은 type을 host라 가정한다.
    • dir : id로 부터의 어떤 특정한 전송 방향을 나타낸다. 가능한 방향은 'src', 'dst', 'src or dst', 'src and dst'이다. 만약 방향이 정해지지 않았다면, src or dst라 가정한다. "For `null' link layers (i.e. point to point protocols such as slip) the inb ound and out bound qualifiers can be used to specify a desired direction."
    • proto : 매칭을 특정 프로토콜에 한해서 수행한다. 가능한 프로토콜들은 ether, fddi, ip, arp, rarp, decnet, lat, sca, moprc, mopdl, tcp, udp이다. 만약 프로토콜이 명시되지 않았다면, 해당하는 값의 type에 관련된 모든 프로토콜들이 그 대상이 된다.

    이 밖에도 위의 패턴을 따르지 않는 Primitive들이 존재한다(gateway, broadcst, less, greater, 산술식).

    좀 더 정교한 조건식들을 사용하려면, 'and(&&)', 'or(||)', 'not(!)'들을 사용하여 여러 primitive들을 연결하면 된다. 같은 표현들은 생략될 수 있다.

    사용 가능한 Primitive들

    • dst host HOST
      packet의 IP destination 항목이 HOST일때 참이 된다.
    • src host HOST
      packet의 IP source 항목이 HOST일때 참이 된다.
    • host HOST
      IP source, IP destination 항목 중 어느 하나라도 HOST이면 참이다.
    • ether dst ehost
      ethernet destination 주소가 ehost일 때 참이다.
    • ether src ehost
      ethernet source 주소가 ehost일 때 참이다.
    • ether host ehost
      ethernet source, destination 항목들 중 어느 하나라도 ehost이면 참이다.
    • gateway host
      패킷이 host를 게이트웨이로 사용하면 참이다. 이 말의 의미는 ethernet sour ce나 destination 항목은 host이지만, IP source와 destination은 host가 아닐 때를 말한다.
    • dst net NET
      패킷의 IP destination 주소가 NET의 network number를 가지고 있을 때 참이 다.
    • src net NET
      패킷의 IP source 주소가 NET의 network number를 가지고 있을 때 참이다.
    • net NET
      패킷의 IP source 주소 혹은 destination 주소가 NET의 network number를 가 지고 있을 때 참이다.
    • net netmask mask
      IP 어드레스가 지정된 netmask를 통해서 net과 매칭되면 참이다.
    • net net/len
      IP 어드레스가 netmask와 len 비트만큼 매치되면 참이다.
    • dst port PORT
      패킷이 ip/tcp, ip/udp 프로토콜의 패킷이고 destination port의 값이 PORT일 때 참이다. port는 /etc/services에 명시된 이름일 수도 있고 그냥 숫자일 수도 있다. 만약 이름이 사용됐다면 port 번호와 프로토콜이 같이 체크될 것이다. 만약 숫자나 불 확실한 이름이 사용됐을 경우에는 port 번호만이 체크될 것이다.
    • src port PORT
      패킷의 source port의 값으로 PORT를 가지면 참이다.
    • port PORT
      패킷의 source, destination port 중에 하나라도 PORT이면 참이다.
    • less length
      패킷이 length보다 짧거나 같으면 참이다.(len <= length)
    • greater length
      패킷이 length보다 짧거나 같으면 참이다.(len >= length)
    • ip proto protocol
      패킷이 지정된 종류의 프로토콜의 ip패킷이면 참이다. Protocol은 icmp, igrp, udp, nd, tcp 중의 하나 혹은 몇 개가 될 수 있다. 주의할 점은 tcp, udp, icmp들은 '\'로 escape되어야 한다.
    • ehter broadcast
      패킷이 ethernet broadcast 패킷이라면 참이다. ehter는 생략 가능하다.
    • ip broadcast
      패킷이 IP broadcast 패킷이라면 참이다.
    • ether multicast
      패킷이 IP multicast 패킷이라면 참이다.
    • ether proto protocol
      패킷이 ether type의 protocol이라면 참이다. protocol은 ip, arp, rarp 중에 하나 혹은 몇개가 될 수 있다. ip proto protocol에서와 마찬가지로 ip, arp, rarp는 escape 되어야 한다.
    • decnet src host
      만약 DECNET의 source address가 host이면 참이다. 이 어드레스는 '10.123'이 나 DECNET의 host name일 수 있다. DECNET host name은 DECNET에서 돌아가도록 설정된 Ultrix 시스템에서만 사용 가능하다.
    • decnet dst host
      DECNET destination address가 host이면 참이다.
    • decnet host HOST
      DECNET source, destination address중의 하나라도 HOST이면 참이다.
    • ip, arp, rarp, decnet
      ether proto [ip|arp|rarp|decnet]의 약어
    • lat, moprc, mopdl
      ether proto [lat|moprc|mopdl]의 약어
    • tcp, udp, icmp
      ip proto [tcp|udp|icmp]의 약어
    • expr relop expr
      • EXPR
        proto [expr:size]의 형식을 띤다. proto, expr, size에 올 수 있는 것들은 다음과 같다.
        • proto : ether, fddi, ip, arp, rarp, tcp, udp, icmp
        • expr : indicate Byte offset of packet of proto
        • size : optional. indicate the size of bytes in field of interest
        • default is one, and can be two or four
      • RELOP
        !=, =, <=, >=, etc.

      이 조건식을 사용하기 위해서는 먼저 해당하는 Protocol(proto)의 헤더에 관련된 것들을 자세히 알아야만 한다. proto에는 대상이 될 프로토콜을 지정한다. expr에는 프로토콜 헤더의 처음부터의 Byte Offset을 지정하는 식이 들어가게 된다. Size는 Option이며 지정이 안 되어 있을 경우에는 자동으로 1byte를 지칭한다. 따라서 이 조건식을 사용하게 되면 헤더에 포함된 정보를 Bitmask를 사용하여 직 접 원하는 패킷인지를 가려낼 수 있기 때문에, 보다 정밀한 사용이 가능하게 된다.


  • Tcpdump의 사용 예제들

    • security라는 호스트로부터 날아오고, 날아가는 패킷들을 출력
      # tcpdump host security
    • security와 mazinga, getarobo 사이에 날아다니고 있는 패킷들을 출력
      # tcpdump host security and \( mazinga or getarobo \)
    • security에서 elgaim을 제외한 모든 호스트로 날아다니는 IP 패킷들을 출력
      # tcpdump ip host security and not elgaim
    • gateway amurorei를 거치는 ftp에 관련된 패킷들을 출력
      # tcpdump 'gateway amurorei and ( port ftp or ftp-data )'
    • local호스트가 아닌 호스트와 로컬호스트가 맺는 TCP 커넥션의 시작과 마지막 패 킷들을 출력한다(SYN, FIN 패킷).
      # tcpdump 'tcp[13] & 3 != 0 and not src and dst net non-local'
    • gateway amurorei를 지나는 576Byte보다 큰 패킷들을 출력한다
      # tcpdump 'gateway amurorei and ip[2:2] > 576'
    • Ethernet boradcast 혹은 multicast를 통해서 보내진 것이 아닌, IP broadcast 혹 은 multicast 패킷들을 출력한다.
      # tcpdump 'ehter[0] & 1 = 0 and ip[16] >= 224'
    • Echo request/reply가 아닌 ICMP 패킷들을 모두 출력한다.
      # tcpdump 'icmp[0] != 8 and icmp[0] != 0'


  • Tcpdump의 평가

    TCPDUMP는 여러모로 좋은 툴이다. libpcap을 거의 100% 활용한 프로그램의 예이며, 실제로 많은 툴들이 TCPDUMP와 병행하여 돌아가거나, TCPDUMP를 기반으로 제작되었다. TCPDUMP의 막강한 packet filter는 현재 로컬 네트워크 상에서 날아다니고 있는 특정한 패킷들을 실시간으로 기록해 줄 수 있으며, 이를 이용하여 네트워크에서 벌어지는 일들을 네트워크 관리자가 원하는 대로 뽑아 볼 수 있게 해 준다. 또한, 시스템 관리자들에게는 로컬 유저의 외부로의 커넥션들을 감시하고, 또 특정 침입자가 침투 경로로 자주 이용하는 호스트, 혹은 원하지 않는 호스트로부터의 커넥션을 실시간으로 감시할 수 있게 해 준다. libpcap을 이용하여 비슷한 툴을 제작하고자 하는 사람들에게도 TCPDUMP는 가장 훌륭한 예제가 될 것이다.


  • References

    • TCP dump Manual page : written by Van Jacobson, Craig Leres and Steven McCanne, all of the Lawrence Berkeley National Laboratory, University of California, Berkeley, CA.
    • TCP dump Document included in TCP dump package

'2010년 이전 글 > computer' 카테고리의 다른 글

한글 putty 0.58  (1) 2008.11.04
TCPDUMP 요약글입니다.  (0) 2007.10.30
블루스크린 ErrorCode Define 값입니다.  (0) 2007.09.21
HP-UX 쉘 명령어 총정리  (0) 2007.08.22
gcc와 make 강좌  (0) 2007.05.01
and

OpenSSL은 유닉스 / 윈도우즈 환경하에서 보안과 관련된 여러가지 기능을 제공하는 라이브러리로서

 

OpenSSL의 자세한 설명은http://www.openssl.org웹사이트에 등록되어 있습니다.

 

 

그리고, 첨부된 파일은OpenSSL을 Visual Studio에 어떻게 설치하고 사용하는지에 대한 설명을 포함하고 있는 문서입니다.

 

 

OpenSSL 라이브러리 설정이 약간 까다로운 편이라서 나름대로 최대한 그림을 많이 넣고 쉽게 설명하려고 노력했습니다 ^_^/

 

'2010년 이전 글 > VC++ 자료' 카테고리의 다른 글

MFC 유용한 팁  (0) 2007.04.18
VC++ 6.0 Command Line Compile  (0) 2007.01.30
VC++단축키정리  (0) 2006.07.28
[펌] 기본 WINAPI 소스  (0) 2004.11.01
[펌] 윈도생성모드 / 최대화, 최소화, 일반, 숨김  (0) 2004.11.01
and
 
and

BOOLCDialog::OnInitDialog()
{
      CDialog::OnInitDialog();
      ModifyStyleEx( WS_EX_APPWINDOW,WS_EX_TOOLWINDOW,0 );
      returnTRUE
}

and

BOOL CWinApp::InitInstance()
{
    // Mutex 생성
    HANDLE hMutexOneInstance =  ::CreateMutex(NULL, TRUE, _T("Unique Name of Mutex"));

    BOOL bFound = FALSE;
    // 만약 이미 만들어져 있다면 Instance가 이미 존재함
    if (::GetLastError() == ERROR_ALREADY_EXISTS)
        bFound = TRUE;


    if ( hMutexOneInstance )
        ::ReleaseMutex(hMutexOneInstance);
 
    // 이미 하나의 Instance가 존재하면 프로그램 종료
    if(bFound) {
        AfxMessageBox("이미 실행중입니다");
        return FALSE;
    }
 
         ........

}

 

and
long multiply(multiplicand, multiplier)
long multiplicand;
long multiplier;
{
    long i=0, sign=0, sum=0;

    if( multiplicand & (1<<31) )
    {
        multiplicand = ~multiplicand + 1;
        sign++;
    }
    if( multiplier & (1<<31) )
    {
        multiplier = ~multiplier + 1;
        sign++;
    }
    for(i=0, sum=0; i<32; i++)
    {
        if( (multiplier>>i) & 0x01 )
            sum += (multiplicand<<i);
    }
    if( sign & 0x01 )
    {
        sum = ~sum + 1;
    }
    return sum;
}

main()
{
    int i, j;

    for( i=-10; i < 10; i++ )
        for( j=-10; j < 10; j++ )
            printf( "%d * %d = %d\n", i, j, multiply(i,j));
}
and
#ifndef_ELAPSED_TIMER_H_#define_ELAPSED_TIMER_H_#ifdefWIN32#include<sys/timeb.h>#include<time.h>#else#include<sys/time.h>#endif#include<sys/types.h>#include<sstream>#include<map>using namespace std;#ifdefWIN32typedefstruct_timeb   TM;#elsetypedefstructtimeval  TM;#endifclassCElapseTimer{public:    CElapseTimer();virtual~CElapseTimer();public:voidInit();voidBegin(conststring&strSectionName);doubleFinish(conststring&strSectionName);doubleGetElapsedTime(conststring&strSectionName);private:typedefstruct_TimeSection{doublem_dElapse;        TM m_tBegin;        TM m_tFinish;}TimeSection;    map<string, TimeSection>m_mTime;};#endif////////////////////////////////////////////////////////////////////////////////#include"ElapseTimer.h"#include<stdlib.h>CElapseTimer::CElapseTimer(){Init();}CElapseTimer::~CElapseTimer(){}voidCElapseTimer::Init(){m_mTime.clear();}voidCElapseTimer::Begin(conststring&strSectionName){TM tBegin;#ifdefWIN32    _ftime(&tBegin );#elsegettimeofday (&tBegin, NULL );#endifTimeSection TS=m_mTime[strSectionName];    TS.m_tBegin=tBegin;    m_mTime[strSectionName]=TS;}doubleCElapseTimer::Finish(conststring&strSectionName){TM tFinish;    TimeSection TS=m_mTime[strSectionName];#ifdefWIN32    _ftime(&tFinish );    TS.m_tFinish=tFinish;    TS.m_dElapse=( ( TS.m_tFinish.time-TS.m_tBegin.time )*1000)+( TS.m_tFinish.millitm-TS.m_tBegin.millitm );#elseTS.m_tFinish=tFinish;    gettimeofday (&tFinish, NULL );    TS.m_dElapse=(((TS.m_tFinish.tv_sec-TS.m_tBegin.tv_sec)*1000000)+(TS.m_tFinish.tv_usec-TS.m_tBegin.tv_usec));#endifm_mTime[strSectionName]=TS;returnTS.m_dElapse;}doubleCElapseTimer::GetElapsedTime(conststring&strSectionName){TimeSection TS=m_mTime[strSectionName];returnTS.m_dElapse;}////////////////////////////////////////////////////////////////////////////////#include<iostream>#include"ElapseTimer.h"using namespace std;intmain (intargc,char** argv ){CElapseTimer ET;    ET.Begin("main");// Do something....ET.Finish("main");    printf("%f\n", ET.GetElapsedTime("main"));return0;}

'2010년 이전 글 > C/C++ 자료' 카테고리의 다른 글

C++ 클래스 설계의 정석  (0) 2007.11.13
비트 연산으로 구현한 곱셈  (0) 2007.10.23
C CPP 코드 검색 엔진  (0) 2007.09.21
MFC 더블버퍼링  (0) 2007.06.25
문자열(스트링) 전격 분석 2부 2강(끝)  (0) 2007.01.05
and

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

[강좌] 기초적인 압축 알고리즘 하이텔 퍼옴..

_____________________________________________________________________

 RLE / RLE+ / FAX / FAX+ / Lempel-Zip 방식압축기법

---------------------------------------------------------------------

 

 !!***********************************************************!!

 

< RLE (Run Length Encoding) 방식 >

 

 가장 쉽지만 모든것이 그렇듯 가장 압출률이 저조한 방법입니다.

 

 화일을 16 진수로 나타낸 코드를 읽어서 다음과 같은 예가 있다면

 

 

   <데이터>

   00 00 1C 1C 1C F3 F3 F3 F3 F3 D8 11 11 11 11 11

 

   

 위의 자료는 16byte의 자료입니다.

 

 RLE 방식으로 압축을 하면 다음과 같이 나타낼수 있습니다.

 

 

   <RLE 압축>

   00 02 1C 03 F3 05 D8 01 11 05

 

   

 대상코드와 그 코드의 갯수를 나란히 적은 것임을 알수 있습니다.

 

 이방법은 일반적으로 독립적으로 쓰이기보다는 여러가지 기법과 혼용되어

 

 쓰이는 방법입니다. 또한 연속된 자료가 자주 나타나는 그림화일등에서

 

 압축률을 높일수 있습니다. 다른 일반 자료는 오히려 압축한것이

 

 더큰 역효과를 가져옵니다. 이유는 자료코드 하나와 갯수코드 하나씩해서

 

 최악의 경우는(연속된자료가 전혀없는경우) 꼭 원본보다 두배의 자료가

 

 되기 때문이죠.

 

-----------------------------------------------------------------------

< RLE 보완형 >

 

 RLE의 단점은 연속되지 않은 자료의 압축시에 오히려 그 압축화일의 용량

 

 이 더욱 커진다는 것이었습니다. 그러나 일부 그림자료등을 제외하면

 

 대부분의 자료들은 같은 코드의 반복은 그리 눈에 띄지 않습니다...

 

 여기서 보일 방법은 RLE의 갯수를 세는 부분을 달리 하는 것입니다.

 

 기존에 01 02 03 ...처럼 숫자를 썼던 것 대신 C0 C1 C2 ...등으로 바꾸어

 

 쓰는 것입니다. 이 방법은 글쎄요. RLE 방법과 그다지 별차이가 없어

 

 보이지만 코딩시에 새로운 규칙을 첨가함으로써 변화를 도모합니다.

 

 그 규칙이라 함은 갯수를 설정하면 코드의 갯수가 바뀌지 않는이상 다시

 

 설정하지 않는다는 것입니다. (이해가 가실까.....그렇다면 예제를..)

 

 

   <데이터>

   F9 03 5D E3 21 00 EE 45 33 76 DE 3D 2F F4 5C B2

   

 

 위와 같은 16byte 데이터의 RLE 보완형으로 압축하면 다음과 같습니다.

 

 

   <RLE 보완압축>

   C0 F9 03 5D E3 21 00 EE 45 33 76 DE 3D 2F F4 5C B2

 

   

 17byte 로 원본보다 1byte나 손해를 보았습니다. 그러나 RLE 보다는

 

 훨씬 좋은 방법임을 알수 있습니다.

 

 위의 예는 전혀 연속되는 자료가 없었다는 겁니다. 그러나 다음의 예와

 

 같이 연속되는 자료가 존재하면 이야기는 달라집니다.

 

 

   <데이터>

   03 03 03 03 5F E3 21 00 EE 45 33 76 DE 3D 2F F4 5C

 

   <RLE 보완압축>

   C3 03 C0 5F E3 21 00 EE 45 33 76 DE 3D 2F F4 5C

 

 

 자 1byte의 압축 효과를 가져왔습니다.

 

 하지만 혹자는 여기서 이런 의문을 갖을수도 있겠습니다. 만일 데이터상에

 

 C0...CF 의 자료가 존재하면 어떻게 하는가....만일 다음과 같은 자료가

 

 있다면 예 치고는 너무 속보이는 예제입니다만....헐~

 

 

   <데이터>

   C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF

 

 

   <RLE 보완압축?>

   C0 C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF

 

 

 위의 압축된것이 맞을까요?  흐흐......절대 아니죠....위의 것을 다시 풀어

 

 쓴다고 생각하면 전혀 다른 해석이 생기게 됩니다.

 

 지금까지 쓴 RLE 보완형의 규칙을 보면 갯수는 C0...CF로적되 첨음에는 갯수

 

 다음에 코드순이었습니다....따라서 압축된것을 거기에 준하여 해석하면

 

 C0 C0 는 C0가 1개로 해석되지만 다음부터는 문제가 생기죠...C1 C2 에서는

 

 엉뚱하게도 C2 라는 코드가 2개인것으로 해석된다는 맹점입니다.

 

 따라서 이런경우를 위한 한가지의 규칙이 더 필요하게 됩니다.

 

 * 만일 데이커상에 C0...CF 의 자료가 있으경우 그부분에 대해서 특별히

   기존의 RLE 방법으로 압축한다. 갯수를 적을때는 역

 C0...CF 코드를 이용

   한다.

 

 이해를 돕기위한 예제는 다음과 같습니다.

 

   

   <데이터>

   34 DE C3 C3 C3 F4 F4 F4 DE C0 36 8D B1 A0 00 01

   

 

 

 위와 같은 16byte의 자료를 압축합니다. 압축시 주의할사항으로는 C# 코드발견

 

 시 RLE로 압축하고( 갯수 + 코드 쌍형태) 다시 RLE 보완형을 시작하는 것입니다

 

 즉, 앞에 압축한것과 별도의 압축을 한다고 생각하고 다시 C# 형태의 갯수를 쓰

 

 는것부터 하는거죠....압축된것을 보면 다음과 같습니다.(~~ 친부분 주의할곳)

                                       

 

   <RLE 보완압축>

   C0 34 DE C2 C3 C2 F4 C0 DE C0 C0 C0 36 8D B1 A0 00 01

                  ~~                ~~

   

 처음부터 차근차근 살펴보겠습니다.

   

 먼저 C0의 의미는 34 DE 라는 데이터가 한개씩 있다는 뜻입니다.

   

 C2 C3 는 C3 데이터가 3개 있다는 이야기입니다.

   

 C2 F4 는 F4 데이터가 3개있다는 의미인데...문제는 이전에 쓰인 C2 의 의미가

 

 뒤에나오는 C3 C2 F4 까지 모두 3개씩이 아닌가 하는 해석의 오류를 나을수 있

 

 다는 것입니다. 그러나 압축에서 규칙을 정한것처럼 C0...CF 자료가 둘이상 존재

   

 할때는 이것이 특수한 규칙에 의해서 압축된 RLE 방식이라는 것을 잊어서는 안

 

 됩니다...따라서 압축을 풀때에도 C0...CF 코드가 나오면 두개씩 짝을지어 RLE

 

 방식으로 해석하고 풀어야 한다는 이야기입니다. 둘씩 짝을 지어 해석한후 다시

 

 나오는 C2 는 다음에 나오는 자료가 C# 형태가 아니기 때문에 보완형으로 압축된

 

 것임을 알수 있습니다...그래서 F4가 3개 연속이라는 뜻이죠...다음에 연속되는

 

 C0의 해석에서는 C#형태가 연속되므로 이것은 RLE 방식의 압축입니다. 따라서 첫

 

 C0는 갯수 다음은 코드...해서 C0 가 하나있다는 의미이구요...(역시 2개씩 짝을

 

 짓죠...RLE 방식은 두개씩의 짝을 만들어 냅니다...) 다음에 나오는 C0는 38 8D

 

 B1 A0 00 01 이라는 코드가 모두 하나씩 존재한다는 의미입니다.

 

 역시 다음코드인 DE 가 1개라는 의미죠...조금은 복잡한듯 싶지만 그 규칙을 완

 

 전히 이해한다면 별루 어려울것은 없습니다.

 

 실전을 위해 압축된 자료의 원본을 만들어보는 연습을 하겠습니다.

 

 

   <RLE 보완압축 자료>

   C0 34 DE C2 C3 C2 F4 C0 DE C0 C0 C0 36 8D B1 A0 00 01

 

 

 C0 34 DE 가지는 34 DE 가 하나씩 있다는 의미입니다. 다음에 나온 C2 C3 C2...

 

 C#의 형태가 연속해있으므로 두개씩 묶어서 생각합니다. C2 C3 는 RLE 압축이

 

 니까...C3 라는 데이터가 3개라는 의미겠죠...다음...C2는 다시 RLE 보완형의

 

 첫부분이니까...갯수를 나타내고 다음에 나오는 F4라는 데이터의 갯수죠...

 

 따라서 F4 3개.....C0 DE 는 DE 가 1개 다음에 역시 연속되는 C0 중에...두개를

 

 취해서 C0가 1개있다는 의미이고....세번째 C0는 RLE 보완형 첫번째인 갯수 따라

 

 서 36 8D B1 A0 00 01 이 하나씩... 이상을 종합하면...

 

 

   <복원데이터>

   34 DE C3 C3 C3 F4 F4 F4 DE C0 36 8D B1 A0 00 01

   

 

 이 됩니다.....원래의 모습을 찾았죠?    이해가 가십니까?

 

 규칙에 충실하면 어려울것이 없습니다. RLE 방식은 가장 기초적인 방식으로 다른

 

 압축 기법을 공부하는데에 기본이 된다구나 할까요....

 

---------------------------------------------------------------------------

  

<FAX 압축>

 

 이 압축 방법은 현재 많이 쓰이는 사무기기인 팩시밀리에서 사용하는 압축 방식입

 

 니다. 압축률또한 위에서 언급된 RLE 계열의 방식보다 좋으며 RLE방식을 제외한

 

 기타다른 압축방식보다 구현하기가 쉬워서 맣이 사용되고 있습니다.

 

 간단히 설명하자면 이전까지 사용하던 16진 코드를 2진코드로 바꾸어 압축하는

 

 방식입니다. 다른 말로 비트맵이라는 표현을 쓰기도 합니다. 마우스의 커서등을

 

 구현할때 모눈종이에 그리던것 생각하시면 빠르겠네요..

 

 자 우선 아래와 같은 32byte의 데이터를 읽어들입니다.

 

 

   <데이터>

   00 00 0F F0 1F F8 1F F8   1F F8 0F F0 00 00 00 00

   0F F0 11 88 11 88 11 88   11 88 11 88 0F F0 00 00

   

 

 위의 데이터를 RLE 보완압축 방식으로 압축하면 다음과 같겠죠 (쩝 압축률

0%?)

 

 

   <RLE 보완압축>

   C1 00 C0 0F F0 1F F8 1F   F8 1F F8 0F F0 C3 00 C0

   0F F0 11 88 11 88 11 88   11 88 11 88 0F F0 C1 00

   

 

 비교를 위해서 RLE 방법을 쓰면 다음과 같습니다. (흐...팽창률 170%?)

 

 

   <RLE 압축>

   00 02 0F 01 F0 01 1F 01   F8 01 1F 01 F8 01 1F 01

   F8 01 0F 01 F0 01 00 04   0F 01 F0 01 11 01 88 01

   11 01 88 01 11 01 88 01   11 01 88 01 11 01 88 01

   0F 01 F0 01 00 02

 

   

 비교상으로도 RLE 보완형이 월등히 좋다는 것을 알수 있습니다....그럼 FAX 압축

 

 을 위해서 우선 비트맵으로 구성해보도록 하겠습니다.

 

 위의 데이터를 16 by 16 비트맵으로 나타내면 다음과 같습니다.(정사각형모양)

 

 

               <비트맵>        l   <16진>

                               l  

         0000 0000 0000 0000   l   00  00

         0000 1111 1111 0000   l   0F  F0

         0001 1111 1111 1000   l   1F  F8

         0001 1111 1111 1000   l   1F  F8

         0001 1111 1111 1000   l   1F  F8

         0000 1111 1111 0000   l   0F  F0

         0000 0000 0000 0000   l   00  00

         0000 0000 0000 0000   l   00  00

         0000 1111 1111 0000   l   0F  F0

         0001 0001 1000 1000   l   11  88

         0001 0001 1000 1000   l   11  88

         0001 0001 1000 1000   l   11  88

         0001 0001 1000 1000   l   11  88

         0001 0001 1000 1000   l   11  88

         0000 1111 1111 0000   l   0F  F0

         0000 0000 0000 0000   l   00  00

         

 

 먼저 일단계로 위의 비트맵에서 0이 많아지는 방향으로 데이터를 규칙있게

 

 변형시키는 것입니다. 먼저 기존의 0은 그대로 두고 0에서 1로 변화되는 부분과

 

 0 에서 1로 변화되는 부분에서마 1을 쓰는 방법이 있습니다.

 

 변형되 형태는 다음과 같은 모습이 될것입니다.

 

 

           <변형된 비트맵>

 

         0000 0000 0000 0000   

         0000 1000 0000 1000   

         0001 0000 0000 0100   

         0001 0000 0000 0100   

         0001 0000 0000 0100   

         0000 1000 0000 1000   

         0000 0000 0000 0000   

         0000 0000 0000 0000   

         0000 1000 0000 1000   

         0001 1001 0100 1100   

         0001 1001 0100 1100   

         0001 1001 0100 1100   

         0001 1001 0100 1100   

         0001 1001 0100 1100   

         0000 1000 1000 1000   

         0000 0000 0000 0000   

 

         

 

 위의 데이터는 예시를 위해 조작된 것이기 때문에 확실이 0숫자가 많이 포함

 

 되어있습니다. 그러나 일반적인 데이터는 이보다는 좋지 않은 결과가 나올경우가

 

 더 많다는 것을 알아두셔야 합니다. 또한 FAX 압축을 풀기위한 규칙으로 가장

 

 첫 비트가 1이면 그자리는 1로 표시해야합니다. 그렇지 않으면 나중에 압축

 

 을 풀어나갈때 곤란한 경우가 생기게 됩니다. 우선을 그렇게 알아두시면 됩니다.

 

 여기까지 끝이 나면 다음 단계로 넘어갑니다. 이번에는 데이터를 회전시킵니다.

 

 프로그램상으로 구현할때에는 위에서 아래로 데이터를 읽어서 왼쪽에서 오른쪽

 

 으로 써나가면 됩니다.(이유는 0을 많이 만들기 위함입니다)

 

 재변형된 데이터의 형태는 아래와 같습니다.

 

 

 

             재변형 비트맵      l  16진   l 헤더비트

         ---------------------------------------------

         0000 0000 l 0000 0000  l  00  00 l   00

         0000 0000 l 0000 0000  l  00  00 l   00  

         0000 0000 l 0000 0000  l  00  00 l   00  

         0011 1000 l 0111 1100  l  38  7C l   11

         0100 0100 l 1111 1110  l  44  FE l   11  

         0000 0000 l 0000 0000  l  00  00 l   00  

         0000 0000 l 0000 0000  l  00  00 l   00  

         0000 0000 l 0111 1100  l  00  7C l   01  

         ---------------------------------------------

         0000 0000 l 0000 0000  l  00  00 l   00  

         0000 0000 l 0111 1100  l  00 7C l   01  

         0000 0000 l 0000 0000  l  00  00 l   00  

         0000 0000 l 0000 0000  l  00  00 l   00  

         0100 0100 l 1111 1110  l  44  FE l   11  

         0011 1000 l 0111 1100  l  38  7C l   11  

         0000 0000 l 0000 0000  l  00  00 l   00  

         0000 0000 l 0000 0000  l  00  00 l   00  

 

 

 

 데이터를 입력하기위해서 우선 헤더비트를 쓴후 데이터를 입력하는 방식으로

 

 압축을 해나가는 것입니다. 여기서 헤더 비트란 것은 재변형된 비트맵상에서

 

 1byte를 취하여 그값이 0이면 0 그외의 숫자이면 1을 부여하여 일열로 나열

 

 한 일종의 암호데이터라고 할까요.(위에 세로줄로 나눈것은 byte 단위로 알아

 

 볼수 있도록 한것입니다) 위에 헤더비트를 적은 것을 보면 쉽게 그 의미를

 

 알수 있을 것입니다. 따라서 위의 데이터의 헤더비트는 다음과 같습니다.

 

 

   <헤더비트>

   0000 0011 1100 0001 0001 0000 1111 0000 : 03 C1 10 F0

 

   

 압축된 데이터는 이 헤더비트를 가장 먼저 적고 다음에 자료를 입력하는데

 

 단, 자료가 00일때는 적지 않습니다. 완성된 압축 데이터는 다음과 같습니다.

 

 

   <FAX 압축>

   03 C1 10 F0 38 7C 44 FE 7C 44 FE 38 7C

   ~~~~~~~~~~~

    헤더비트

    

       

 처음에 주어진 32 byte 데이터가 13 byte로 압축되었습니다. (압축률 약60%)

 

 사실 위의 예제는 16 by 16 비트맵을 사용했지만 8 by 8 비트맵을 4번 압축한

 

 것과도 같습니다...따라서 위의 예제를 8 by 8 비트맵으로 바꾸어 네번 같은

 

 계산을 반복해도 하나의 방법으로 생각할수 있습니다.

 

 문제는 얼마나 많은 데이터를 이용하는가와 계산상의 속도문제가 상호작용하

 

 게 되기 때문에 적정선에서 나누는것이 좋습니다. 보통 8 by 8 비트맵이나

 

 16 by 16 비트맵을 사용합니다.

 

 이 FAX 압축을 푸는 방법은 지금까지 해왔던 일련의 작업을 꺼꾸로 수행하는

 

 것입니다. 먼저 헤드를 읽어서...우선은 이 압축이 8 by 8 로 압축되었는지

 

 16 by 16 으로 압축되었는지 알아야 하는것은 당연한것이겠죠...압축한 방법

 

 으로 풀어야 풀리겠죠....어쨌든.. 헤드를 읽어서 데이터중에 00의위치와 0이

 

 아닌 데이터의위치를 파악한후 자료를 순서대로 읽어 해당위치에 넣고

 

 (여기까지는 재변형 비트맵) 회전된 것을 복구하기 위해서 데이터를 왼쪽에서

 

 오른쪽으로 읽어서 위에서 아래로 써 원래의 변형된 비트맵을 만듭니다.

 

 이제는 1과 0의 규칙에 따라서 원래의 자료로 복구를 하는 것입니다.

 

 여기서 앞에서 언급했던 첫비트의 1일때 문제는 이렇습니다. 만일 첫비트가

 

 11100 등으로 시작되었는데도....00010 등으로 쓰였을경우 복구할때...규칙에

 

 의해서 00010 은....00011 로 복구가 되죠....원하는 값이 아닙니다....따라서

 

 위의 경우...즉, 11100 으로 시작하는 데이터의 올바른 변형법은 10010 입니다.

 

 그래야 규칙에 의해서 11100으로 바뀔수가 있기 때문입니다.

 

 이점만 유의 한다면 FAX 압축도 별 어려운 것이 없을것이라고 생각합니다.

 

 참고적으로 말씀드리자면 FAX 방법에 있어서 어떤때에는 오히려 헤더부분때문에

 

 특히 16 by 16 비트맵 방식에서는 무려 4 byte 의 헤더가 이용되면서...압축률

 

 에 있어서 손해를 보게 되는 경우가 많습니다...그래서 보통은 8 by 8 비트맵

 

 의 형태를 사용하는데 그대신 많은 데이터를 한번에 읽어서 속도를 향상시키

 

 기도 합니다. 하지만 16 by 16 의 장점은 8 by 8 보다는 데이터의 압축률에 있

 

 어서 앞선다는 것입니다....그래서 나온것이 16 by 16 비트맵 방식에서...

 

 헤더의 크기를 2 byte 로 줄일수 있는 방법이 있다고 합니다.

 

 음...FAX 압축이 비록 팩시밀리에서 사용되는 방법이긴하지만 자료에 따라서는

 

 예를 들면 실행화일을 압축하는데 있어서는 결코 쓸모있는 것은 못됩니다.

 

 대부분의 경우....10% 내외의 압축률이나..마이너스 압축률이 나오기 쉽상

 

 이라는 점을 알아두시기 바랍니다.

 

---------------------------------------------------------------------------

 

<FAX 보완압축>

 

 보완형이란 다름아닌 위에서 잠시 언급한 헤더를 2byte 로 줄이는 방법입니다.

 

 구현 원리는 FAX 와 같지만 마지막에 헤더를 쓰고 자료를 적는 부분에서만

 

 차이를 보이는 것입니다. FAX 압축의 예제에서 보인것은 헤더를 만들때 8 bit

 

 를 기준으로 해서  03 C1 10 F0  라는 헤더를 얻어냈었습니다... 그러나 여기서

 

 

 는 16 bit 를 기준으로 해서 새로운 헤더를 만들어낼수 있습니다.

 

 즉, 위의 헤더비트의 예에서  

 

 

   <8bit 헤더비트의 예>

   00 00 00 11   11 00 00 01   00 01 00 00   11 11 00 00 : 03 C1 10 F0

 

 

 였던 헤더비트가 16 bit를 기준으로 하면

   

   

   <16bit 헤더비트로의 변형>

    0  0  0  1    1  0  0  1    0  1  0  0    1  1  0  0 : 19 4C

      

    

 으로 변화하게 됩니다. 즉, 헤더가 8bit 일때의 절반으로 감소함으로써 데이터의

 

 압축률이 다소 낮아지더라도 충분한 보상을 갖어오게 된다는 것입니다.

 

---------------------------------------------------------------------------

 

<Lempel-Zip 방식>           

 

 이 방식은 누구나 한번쯤은 생각했보았을만한 방법이면서 근래 많이 이용되는

 

 압축프로그램의 원형이라고 말할수 있는 방식입니다. 크랙용 프로그램인 크랙잭을

 

 보면 크랙을 위한 사전을 가지고 있는것을 볼수 있죠. 이와 비슷한 방식으로

 

 같다고 볼수는 없지만 그런 접근으로 압축을 하는 것이 이방법의 개요입니다.

 

 압축방법이라기 보다는 암호책을 대조하는 식으로 생각하면 이해가 빠를까요?

 

 최고의 효율을 가지고 있는 방식으로 현재 압축효율이 좋은 압축 프로그램들은

 

 모두 이 방식을 사용하거나 그 변형을 이용합니다. 이 방식의 실현은 상당히 어렵지

 

 만 간단하게 생각하면 사전과 같이 자주 사용되는 것을 일종의 테이블로 만들어

 

 압축하는 것입니다. 코드 필드와 서픽스로 나뉘어서 구성되는 테이블의 내용은

 

 다중 패스를 거쳐야 하는 복잡한 알고리즘을 가지고 있지만 여기서 간단히 개념만을

 

 파악한다면 다음과 같이 볼수 있습니다.

 

 

                  INDEX ENCODE 사전

                     ┌─────┐

                 ..  │ .....    │

                 ... │ .....    │

                 ... │ ....     │

                     ├─────┤

                 478 │   IS     │

 RAW DATA            ├─────┤

                 ... │  ....    │

 "THIS IS HOT ->     ├─────┤-> INDEX OUTPUT

  STUFF"         759 │  HOT     │  1295 478 759 3751

                     ├─────┤  (12954787593751)

                 ... │  ...          │

                     ├─────┤

              1295 │  THIS      │

                     ├─────┤

                .... │  ....         │

                     ├─────┤

              3751 │  STUFF     

                     ├─────┤

                .... │ ....          │

                     └─────┘

 

 INDEX ENCODE 사전이라는 것은 압축 프로그램이 규칙에의해 보유하고 있는

 

 일종의 사전이라고 보시면 됩니다. 각각에 번호나 약속된 기호들로 구성되어

 

 그 인덱스 하나가 하나의 단어내지는 기호를 나타내게 되는 것입니다.

 

 압축을 하기위해서는 해당하는 INDEX를 알아야만 가능하겠죠... 일일이 이런

 

 

 사전을 개인이 구축한다는 것은 그리 쉬운 일은 아닐것입니다. 많은 노력과

 

 시간이 들겠죠.. 파싱이라는 기법을 이용해서 인덱스를 자체 생산하는 방법

 

 이 있긴 합니다. 간단한 텍스트 압축기는 충분히 만들수 있을것이라고

 

 생각이 듭니다.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

and
prev Prev : [1] : [2] : [3] : [4] : [5] : [6] : [···] : [15] : Next next