상세 컨텐츠

본문 제목

비트 모아 기가 바이트

프로그래밍/Delphi

by ryujt 2012. 3. 26. 12:11

본문

통신 등에서는 조그만 데이터의 차이가 계속 쌓이면서 큰 차이를 만드는 경우가 종종 있습니다.  이런 경우 사용 할 수 있는 간단한 트릭을 소개하도록 하겠습니다.  

우선 아래와 같이 불린을 구조체로 묶어서 보내는 경우를 생각해보겠습니다.

[소스1]
type
  TPacket = packed record
    Data1 : boolean;
    Data2 : boolean;
    Data3 : boolean;
  end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Caption := IntToStr(SizeOf(TPacket));
end;
버턴을 클릭해보면 당연히 3바이트 크기임을 알 수가 있습니다.  하지만, true/false의 상태를 가지고 있기 때문에 1 바이트에 8개의 불린을 처리할 수가 있기 때문에 아래와 같이 표현 할 수 있습니다.

[소스 2]
type
  TPacket = packed record
    Data : byte;
  end;

function GetData(APacket:TPacket; AIndex:integer):boolean;
var
  BitMask : byte;
begin
  BitMask := 1;
  BitMask := BitMask shl AIndex;
  Result := (APacket.Data and BitMask) = BitMask;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Caption := IntToStr(SizeOf(TPacket));
end;
이제 구조체의 크기는 1바이트로 줄었고, 최대 8개까지 불린 변수를 사용할 수 있게되었습니다.  SetData 함수는 일단 미뤄두기로 하겠습니다.

이제 이 넘을 좀 더 세련되게 바꿔 보도록 하겠습니다.  구조체에 메소드를 추가하는 것이 델파이 어느 버전에서부터인지 기억지 잘 안나는 군요.  오래된 버전을 사용하시는 분들은 "packed record"를 "object"로 바꿔서 사용하시기 바랍니다.  제가 도스 시절에 주로 사용하던 방법입니다.  구조체에 메소드를 추가한 타입이라고 생각하시면 됩니다.  다만, [소스 3]의 예제대로 사용하기에는 몇 가지 제약 사항이 있습니다.

[소스 3]
type
  TPacket = packed record
  private
    FData : byte;
    function GetBooleans(AIndex: integer): boolean;
    procedure SetBooleans(AIndex: integer; const Value: boolean);
  public
    property Booleans[AIndex:integer] : boolean read GetBooleans write SetBooleans;
  end;

{ TPacket }

function TPacket.GetBooleans(AIndex: integer): boolean;
var
  BitMask : byte;
begin
  BitMask := 1;
  BitMask := BitMask shl AIndex;
  Result := (FData and BitMask) = BitMask;
end;

procedure TPacket.SetBooleans(AIndex: integer; const Value: boolean);
var
  BitMask : byte;
begin
  BitMask := 1;
  BitMask := BitMask shl AIndex;

  if Value then FData := FData or BitMask
  else FData := FData and (not BitMask);
end;
이제 데이터를 보관하는 변수를 캡슐화해서 안전하게 보관하였고, property를 통해서 필요한 인터페이스만 제공하도록 하였습니다.  그리고, 여전히 구조체의 크기는 1 바이트이기 때문에 해당 구조체를 소켓 등으로 주고 받는데에도 문제가 없습니다.

저는 표현 범위가 byte, word, integer 등의 제한 범위 내에서만 사용 되면서 데이터의 크기를 줄여야 할 경우에 가끔 사용하는 방식입니다.  예를 들어 32 개수 미만의 열거형과 512 미만의 정수를 사용한다면, word 안에 집어 넣고도 몇 비트가 남게 될 것 입니다.

[소스 4]는 제가 실무에서 사용했던 예 입니다.  word의 맨 앞의 비트는 불린 변수로 사용하고, 나머지는 정수 형태의 변수로 사용하고 있습니다.

[소스 4]
type
  THeader = packed record
  private
    FIndex : word;
    function GetHasKey: boolean;
    procedure SetHasKey(const Value: boolean);
    procedure SetFrameIndex(const Value: word);
    function GetFrameIndex: word;
  public
    property HasKey : boolean read GetHasKey write SetHasKey;
    property FrameIndex : word read GetFrameIndex write SetFrameIndex;
  end;

function THeader.GetFrameIndex: word;
begin
  Result := FIndex and $7FFF;
end;

function THeader.GetHasKey: boolean;
begin
  Result := (FIndex and $8000) = $8000;
end;

procedure THeader.SetFrameIndex(const Value: word);
begin
  FIndex := (FIndex and $8000) or Value;
end;

procedure THeader.SetHasKey(const Value: boolean);
begin
  if value then
    FIndex := FIndex or $8000
  else
    FIndex := FIndex and $7FFF;
end;


 


관련글 더보기