열아홉번째 날: Perl 내부 구조 #3 : 배열 변수와 해시 변수

저자

@luzluna - C,Perl 엔지니어. 근래엔 Ops에 더 가까운 DevOps. PostgreSQL 성능 최적화의 공동 역자, luzluna at gmail.com

시작하며

첫 번째 기사에서는 펄이 어떻게 스칼라 변수를 내부적으로 처리하는지를, 두 번째 기사에서는 펄의 기본타입인 스칼라변수가 어떻게 내부적으로 표현되는지 문맥에 따라 어떻게 변화되는지를 살펴보았습니다. 오늘은 펄의 또다른 기본 타입인 배열 변수와 해시 변수에 대해 살펴봅니다.

배열 변수

일단 빈 배열 변수를 하나 만들고 살펴보겠습니다.

use Devel::Peek;

@array;

Dump(\@array);

결과를 살펴보죠.

SV = IV(0x7fd4308044a8) at 0x7fd4308044b8
  REFCNT = 1
  FLAGS = (TEMP,ROK)
  RV = 0x7fd430829690
  SV = PVAV(0x7fd430806398) at 0x7fd430829690
    REFCNT = 2
    FLAGS = ()
    ARRAY = 0x0
    FILL = -1
    MAX = -1
    ARYLEN = 0x0
    FLAGS = (REAL)

@array라는 변수는 IV 타입으로 생성되었고 FLAGS에는 ROK 그리고 RV에는 실제 PVAV 타입의 배열을 저장한 곳을 가리키고 있음을 알 수 있습니다. ARRAY0x0 그리고 MAXFILL-1로 표시되어있군요. 비어있다는 의미겠죠?

값을 넣어보겠습니다.

use Devel::Peek;

@array;
print "# push 전\n";
Dump(\@array);
push(@array, 1);
print "# push 후\n";
Dump(\@array);

결과를 살펴보죠.

# push 전
SV = IV(0x7fc1620044a8) at 0x7fc1620044b8
  REFCNT = 1
  FLAGS = (TEMP,ROK)
  RV = 0x7fc162029690
  SV = PVAV(0x7fc162006398) at 0x7fc162029690
    REFCNT = 2
    FLAGS = ()
    ARRAY = 0x0
    FILL = -1
    MAX = -1
    ARYLEN = 0x0
    FLAGS = (REAL)
# push 후
SV = IV(0x7fc162004658) at 0x7fc162004668
  REFCNT = 1
  FLAGS = (TEMP,ROK)
  RV = 0x7fc162029690
  SV = PVAV(0x7fc162006398) at 0x7fc162029690
    REFCNT = 2
    FLAGS = ()
    ARRAY = 0x7fc161c0ae80
    FILL = 0
    MAX = 3
    ARYLEN = 0x0
    FLAGS = (REAL)
    Elt No. 0
    SV = IV(0x7fc1620044a8) at 0x7fc1620044b8
      REFCNT = 1
      FLAGS = (IOK,pIOK)
      IV = 1

push 연산을 수행하기 전과 후를 비교해보면 FILL0이 되었고(-1부터 시작이니까 1개라는 의미), MAX3이 되었습니다. 아마도 최대 3개까지 저장할 수 있는 공간을 할당했다는 의미겠지요. 그리고 그 뒤에는 앞에서 살펴본 정수형 변수가 그대로 들어있는것을 보실 수 있습니다. push, pop, unshift, shift 등의 배열 조작하는 함수를 실행한 뒤 결과를 한번 살펴보면 예상대로 동작함을 확인할 수 있습니다. :)

해시 변수

이번에는 해시 변수를 살펴보죠. 역시 빈 해시 변수를 하나 만들고 살펴보겠습니다.

use Devel::Peek;

%hash;
Dump( \%hash );

결과를 살펴보죠.

SV = IV(0x7f9dea0044a8) at 0x7f9dea0044b8
  REFCNT = 1
  FLAGS = (TEMP,ROK)
  RV = 0x7f9dea029690
  SV = PVHV(0x7f9dea00a2a0) at 0x7f9dea029690
    REFCNT = 2
    FLAGS = (SHAREKEYS)
    ARRAY = 0x0
    KEYS = 0
    FILL = 0
    MAX = 7

배열 변수와 동일하게 IV 타입에 저장하고, FLAGSROK군요. RV 값을 따라가면 PVHV 타입이 저장되어 있습니다. 아마도 해시 타입을 저장하는 변수겠죠? 배열 변수 때와는 달리 FLAGSSHAREKEYS가 있고 KEYSFILL-1이 아니라 0이네요. 비어있는데도 MAX가 7이라는 점도 다르군요!

use Devel::Peek;

%hash;
$hash{"key1"} = 1;
Dump( \%hash );

key1이라는 키에 정수값인 1을 넣어보았습니다. 결과를 살펴보죠.

SV = IV(0x7f8850804658) at 0x7f8850804668
  REFCNT = 1
  FLAGS = (TEMP,ROK)
  RV = 0x7f8850829690
  SV = PVHV(0x7f885080a2a0) at 0x7f8850829690
    REFCNT = 2
    FLAGS = (SHAREKEYS)
    ARRAY = 0x7f885040ecc0  (0:7, 1:1)
    hash quality = 100.0%
    KEYS = 1
    FILL = 1
    MAX = 7
    Elt "key1" HASH = 0x57529d96
    SV = IV(0x7f88508044a8) at 0x7f88508044b8
      REFCNT = 1
      FLAGS = (IOK,pIOK)
      IV = 1

이제 배열 변수일 때와는 결과가 꽤 다름을 알 수 있습니다. MAX7인것은 최대 7개까지 저장할 수 있는 버킷을 준비해두었다는 의미입니다. Elt에 딸린 값이 배열일 때는 No. 0,1,2,3처럼 배열의 첨자를 가리켰지만, 이제는 키값인 key1과 해당 키의 해시값인 0x57529d96로 표시됨을 알 수 있습니다.

펄 5.16 이전 버전에서는 해시의 값이 변하지 않았지만, 5.18 이후 버전부터는 매번 실행할 때마다 다르게 나오게 바뀌었습니다. 해시 값을 추측하지 못하도록 실행할 때 마다 해쉬의 값이 달라지도록해 보안을 향상시키는 기능이 추가되어 발생하는 현상입니다.

익명 함수

마지막으로 익명 함수를 저장하면 어떻게 되는지 알아볼까요?

use Devel::Peek;

$sub_a = sub { print "aa"; };
Dump( $sub_a );

결과를 살펴보죠.

SV = IV(0x7fee4a029680) at 0x7fee4a029690
  REFCNT = 1
  FLAGS = (ROK)
  RV = 0x7fee4a029738
  SV = PVCV(0x7fee4a0280e8) at 0x7fee4a029738
    REFCNT = 2
    FLAGS = (PADMY,ANON,WEAKOUTSIDE,CVGV_RC)
    COMP_STASH = 0x7fee4a004320 "main"
    START = 0x7fee49c0e428 ===> 1
    ROOT = 0x7fee49c0e370
    GVGV::GV = 0x7fee4a0296c0 "main" :: "__ANON__"
    FILE = "array.pl"
    DEPTH = 0
    FLAGS = 0x490
    OUTSIDE_SEQ = 95
    PADLIST = 0x7fee49c0db80
    PADNAME = 0x7fee4a0296d8(0x7fee49c0e930) PAD = 0x7fee4a0296f0(0x7fee49c0c7d0)
    OUTSIDE = 0x7fee4a004680 (MAIN)

IV 타입 아래의 FLAGS는 똑같이 ROK, 그리고 RV를 따라가면 PVCV 타입이 저장되어 있습니다. PVCVCode Value를 저장하는 형식입니다. 즉, 컴파일된 op 코드의 Tree를 저장하는 것입니다. 아마도 이 구조 덕분에 first-class function이라든지, 심볼 테이블을 동적으로 접근하는 등 lisp에서만 사용가능한 테크닉을 펄에서도 사용할 수 있는게 아닌가 싶습니다(정확히는 저도 잘 모릅니다만 :). FLAGS를 보면 ANON 익명함수, WEAKOUTSIDE 약한 바인딩 네임스페이스가 main::__ANON__ 아래에 정의되어 있다는 점도 눈여겨 봐두세요.

정리하며

지난 기사에 이어 펄이 그 외의 다른 타입을 어떻게 저장하는지를 간략히 살펴보았습니다. 앞에서 언급하지 못했던 기타 나머지들을 모아놓았더니, 별 도움이 안되는 기사가 만들어진 것 같아서 죄송한 마음이 살짝 드네요. 벌써 대림 셋째주입니다. 기사를 읽으시러 오시는 모든 분들께 평화를 기원하겠습니다. :-)

참고문헌

더 자세한 내용을 알고 싶다면 다음 문서를 참고하세요.

EOT

blog comments powered by Disqus