[첫화면으로]Perl/Pack

마지막으로 [b]

pack, unpack

예전부터 이 두가지는 (vec까지 하면 세 가지) 문서를 읽어도 도대체 뭐하자는 건지 알쏭달쏭하던 터였는데, 자꾸 곳곳에서 이 함수를 쓰는 경우를 보게 되는 터라 더는 미루지 못하고 이런 저런 문서를 읽으면서 정리해 둔다.

1. Pack / Unpack Tutorial - 문서 번역
1.1. 왜 pack과 unpack이 필요한가
1.2. 정수 포맷
1.3. 캐릭터 포맷
1.4. Perl의 pack 함수
1.5. Perl의 unpack 함수
1.6. 정수 포맷
1.7. 스트링 포맷
1.7.1. 'a', 'A', 그리고 'Z' 포맷
1.7.2. 'b'와 'B' 포맷
1.7.3. 'h'와 'H' 포맷
1.8. 추가 정보
1.9. 참고
1.10. 예제 코드
2. perlpacktut - Tutorial on pack and unpack - 문서 정리
2.1. 설명 Description
2.2. 기본 원리 The Basic Prinicple
2.3. 텍스트 팩킹 Packing Text
2.4. 수 팩킹 Packing Numbers
2.4.1. 정수 Integers
2.4.2. 스택 프레임 unpack하기 Unpacking a Stack Frame
2.4.3. 네트워크 위에서는 달걀을 어느 쪽으로 깨어 먹는가13
2.4.4. 바이트 순서 변경자 Byte-order modifiers
2.4.5. 부동소수 Floating point Numbers
2.5. Exotic Templates
2.5.1. 비트 스트링 Bit Strings
2.5.2. Uuencoding
2.5.3. 합 구하기 Doing Sums
2.5.4. Unicode
2.5.5. Another Portable Binary Encoding
2.6. Template Grouping
2.7. 길이와 폭 Lenghts and Widths
2.7.1. 스트링의 길이 String Lenghts
2.7.2. 동적 템플릿 Dynamic Templates
2.7.3. 반복 횟수를 세기 Counting Repetitions
2.8. C 구조체 Packing and Unpacking C Structures
2.8.1. 열맞추기 The Alignment Pit
2.8.2. 엔디안 다루기 Dealing with Endian-ness
2.8.3. 열맞추기, 장면2 Alignment, Take 2
2.8.4. 열맞추기, 장면3 Alignment, Take 3
2.8.5. 포인터를 어떻게 사용하는가 Pointers for How to Use Them
2.9. Pack 비책들 Pack Recipes
2.10. 재미있죠? Funnies Section
2.11. 저자
3. perldoc -f pack - 문서 번역
4. 관련 문서
5. 기타, comments

1. Pack / Unpack Tutorial - 문서 번역

최근에 채팅을 통해 대화를 나누다가 이 문서를 써야겠다는 생각을 하게 되었다. 어떤 초보 프로그래머가 정보를 pack과 unpack을 사용하여 인코드하려고 하였으나, pack과 unpack이 동작하는 원리를 정확히 이해하지 못해 애를 먹고 있었다. 내 경우에는 이 함수들을 사용하는데 아무런 문제가 없었으나, 이는 내가 하드웨어에 대한 배경지식이 있고 어셈블리와 C프로그래밍에 익숙한 상태에서 펄 프로그래밍을 시작했기 때문이다. 최근에 프로그래밍을 시작한 사람들은 아마 그런 저레벨1 수준의 내용을 다뤄보지 않았을 것이고 컴퓨터가 데이타를 어떻게 저장하는지 이해하지 못할 듯 하다. 저레벨 수준의 일들을 조금만 이해하면 pack과 unpack을 보다 쉽게 이해할 수 있다.

1.1. 왜 pack과 unpack이 필요한가

Perl은 스트링, 정수, 부동소수 값들을 다룰 수 있다. 때때로 펄 프로그래머가 다른 언어로 작성된 프로그램과 데이타를 교환해야 할 경우가 있다. 이런 다른 언어들은 데이타의 종류가 더 다양하다. 정수값의 범위도 다르다. 고정된 길이의 스트링만 다룰 수 있을 수도 있다 (감히 COBOL을 언급해야 하나?) 때로는, 네트워크를 통해서 다른 머신과 바이너리 데이타를 교환해야 할 수도 있다. 이런 머신들은 word의 사이즈도 다를 수 있고, 값을 저장하는 방식조차 딴판일 수 있다. 여하튼 우리의 데이타를 다른 프로그램 또는 다른 머신이 이해할 수 있는 포맷으로 변경해야 한다. 또한 우리가 받은 응답을 해석할 수도 있어야 한다.

Perl의 pack과 unpack함수는 데이타가 들어있는 버퍼를 템플릿 문자열이 지시하는 대로 읽거나 쓸 수 있게 해 준다. 템플릿 문자열을 사용하여 바이트 오더와 워드 사이즈를 지정하거나 로컬 시스템의 디폴트 값을 쓰도록 지시할 수 있다. 이를 통해 외부 프로그램과 데이타를 매우 유연하게 주고받을 수 있다.

이 모든 것이 어떻게 동작하는지를 알기 위해서는, 컴퓨터가 여러 다른 타입의 정보들을 어떻게 저장하는지를 이해하는 게 좋다.

1.2. 정수 포맷

컴퓨터의 메모리는 바이트의 거대한 배열로 생각할 수 있다. 하나의 바이트는 8개의 비트를 저장하고 0부터 255까지의 무부호 값 또는 -128부터 127까지의 부호형 값을 표현한다. 이런 작은 범위의 값만 가지고는 수많은 연산을 할 수 없기 때문에, 현대의 컴퓨터에 있는 레지스터들의 크기는 바이트보다 크다. 현대의 프로세서들은 대부분 32비트 레지스터를 사용하고, 64비트 레지스터를 사용하는 것들도 있다. 32비트 레지스터는 0부터 4,294,967,295까지의 무부호 값 또는 -2,147,483,648부터 2,147,483,647까지의 값을 저장할 수 있다.

8비트보다 더 큰 값을 메모리에 저장할 때, 이 값은 8비트 단위의 세그먼트로 나뉘어서 저장장치의 연속적인 공간에 저장된다. 어떤 프로세서들은 최상위 비트(most significant bit)를 첫번째 공간에 저장한다. 이것은 "big endian(1)" 포맷이라 부른다. 다른 프로세서들은 최하위 세그먼트를 첫번째 바이트에 저장하고 나머지 세그먼트들을 더 높은2 메모리 공간에 저장한다. 이것을 "little endian(2)" 포맷이라 한다.

그림을 보면 더 쉬울 것이다. 어떤 레지스터에 0x12345678 값이 담겨 있고 이 값을 메모리 주소 1000에 저장한다고 하면, 다음과 같이 나타낼 수 있다.
Address big endian(3) Machine little endian(4) Machine
1000 0x12 0x78
1001 0x34 0x56
1002 0x56 0x34
1003 0x78 0x12

perldoc -f pack 매뉴얼을 봤거나 Programming Perl 책에서 pack 함수를 찾아봤었다면, Template(5) 문자와 그에 대응되는 데이타 타입이 나열된 표를 봤을 것이다. 그 표에는 여러 가지 크기와 바이트 순서의 정수 포맷이 나열되어 있다. 부호형과 무부호형의 경우도 나와 있다.
포맷 설명
c(6),C(7) 부호형/무부호형 char (8비트 정수) 값
s(8),S(9) 부호형/무부호형 short, 언제나 16비트
l(10),L(11) 부호형/무부호형 long, 언제나 32비트
q(12),Q(13) 부호형/무부호형 quat (64비트 정수) 값
i(14),I(15) 부호형/무부호형 정수, 해당 기계의 고유 포맷
n(16),N(17) "network"(big endian(18)) 순서의 16/32 비트 값
v(19),V(20) "VAX"(little endian(21)) 순서의 16/32 비트 값

s,l,q 포맷은 16,32,64비트의 값을 호스트 머신의 고유한 순서로 pack한다. i 포맷은 값을 호스트 머신의 word 길이에 맞춰서 pack한다. n과 v 포맷은 사용자가 크기와 순서를 지정할 수 있으며, 다른 시스템과 데이타를 교환할 때 유용하다.

1.3. 캐릭터 포맷

string(22)은 캐릭터의 배열로 저장된다. 전통적으로, 각각의 캐릭터는 ASCII나 EDCDIC같은 특정 코딩 시스템을 사용하여 한 바이트에 인코딩된다. Unicode 같은 새로운 인코딩 시스템에서는 캐릭터를 표현하기 위하여 2바이트 이상을 사용하거나 캐릭터마다 서로 다른 길이의 바이트를 사용하기도 한다.

Perl의 pack 함수는 문자열을 처리하기 위해 다음과 같은 템플릿 문자들을 사용한다.
형식 설명
a(23),A(24) 널/공백으로 채워지는 스트링
b(25),B(26) 오름차순/내림차순 비트 순서로 된 바이너리 스트링
h(27),H(28) 낮은/높은 니블3이 먼저 나오는 16진수 스트링
Z(29) null로 끝나는 스트링

스트링은 가장 낮은 주소에 첫번째 문자가 저장되고 주소값이 커지는 방향으로 연속적으로 나머지 문자들이 저장된다.

1.4. Perl의 pack 함수

pack 함수는 템플릿 스트링과, 리스트로 구성된 값들을 인자로 받아서, 템플릿에 명시된 포맷에 맞춰 값들을 저장한 스칼라를 반환한다. 이를 사용하여 C나 다른 언어로 작성된 프로그램이 읽을 수 있는 형식으로 데이타를 작성할 수도 있고 네트워크 소켓을 통해서 원격 시스템에 데이타를 전달할 수도 있다.

템플릿은 위 테이블에 나온 일련의 문자들로 구성된다. 각각의 문자에는 옵션으로 반복 횟수(repeat count(30). 수의 경우) 또는 길이(문자열의 경우)를 뒤에 덧붙일 수 있다. 정수 포맷에서 '*(31)'는 남아 있는 값들에 대해 이 포맷을 사용하라는 의미이다. 스트링 포맷에서는 스트링의 길이를 사용하라는 의미이다.

주인장 보충:

말이 좀 애매하긴 한데, 관련 문서에 있는 aero님의 문서를 참조하면 좀 쉽다.

숫자를 나타내는 c 등에 *가 붙으면, 인자로 주어진 값 리스트의 나머지 원소들을 다 가져와서 pack하게 된다.

$a = pack "c*", "65", "66", "67"; # $a = "ABC", 즉 "66","67"을 다 가져왔다

반면에 스트링을 나타내는 a 같은 경우에는 그 문자에 해당하는 원소의 나머지 문자들 전부를 가져온다.

$a = pack "a*", "abc", "def", "ghi"; # $a = "abc", 즉 첫번째 값 "abc"의 나머지 문자인 b와 c를 가져왔다

이제 예제를 하나 만들어보자. 웹 문서의 폼에서 어떤 정보를 수집하여, C로 작성된 백엔드 시스템에 전달하여 처리한다고 가정하자. 이 폼은 직원이 사무용품 지급을 요청할 수 있는 폼이다. 백엔드 시스템은 입력이 다음과 같은 포맷으로 주어져야 한다.

    struct SupplyRequest {
        time_t request_time;    // time request was entered
        int employee_id;        // employee making request
        char item[32];          // item requested
        short quantity;         // quantity needed
        short urgent;           // request is urgent
    };

시스템의 헤더 파일을 봤더니 time_t는 long 타입이다. 백엔드 시스템에 보낼 레코드를 구성하기 위해서, 다음과 같이 할 수 있다.4

   $rec = pack( "l i Z32 s2", time, $emp_id, $item, $quan, $urgent);

이 템플릿은 "long 하나, int 하나, null로 끝나며 최대길이 32캐릭터인 스트링, short 두 개"를 의미한다.

사원번호 217641(어이! 그건 나잖아!)인 직원이 2003년 1월 1일에 종이클립 두 통을 긴급하게 요청한다면, $rec 변수에는 다음과 같은 값이 담긴다. (첫 줄은 십진수, 두번째 줄은 16진수, 셋째 줄은 해당 값을 문자로 변환할 수 있는 경우 변환된 문자) 각 필드의 경계를 나타내기 위해 파이프 기호를 사용했다.

     오프셋   내용 (주소는 좌측에서 우측으로 커짐)
         0   160  44  19  62| 41  82   3   0| 98 111 120 101 115  32 111 102
              A0  2C  13  3E| 29  52  03  00| 62  6f  78  65  73  20  6f  66
                                            |  b   o   x   e   s       o   f

        16    32 112  97 112 101 114  99 108 105 112 115   0   0   0   0   0
              20  70  61  70  65  72  63  6c  69  70  73  00  00  00  00  00
                   p   a   p   e   r   c   l   i   p   s

        32     0   0   0   0   0   0   0   0|  2   0|  1   0
              00  00  00  00  00  00  00  00| 02  00| 01  00

저것들이 어디서 왔는지 살펴 보자. 템플릿의 첫번째 항목은 'l'이고 이건 long 타입의 값을 pack한다. long은 32비트, 4바이트이다. 저장할 값은 time 함수를 사용하여 얻었다. 실제 값은 1041444000 = 0x3e132ca0이다. 이 값이 버퍼의 시작 부분에 어떻게 들어가는지 보라. 내 시스템은 인텔 펜티엄 프로세서가 달려 있고 little endian(32)이 사용된다.

두번째 템플릿 항목은 'i'이다. 이것은 머신 고유의 크기의 정수를 가져온다. 펜티엄은 32비트 프로세서이고 따라서 이번에도 4바이트로 pack한다. 사원번호는 217641이고 16진수로는 0x00035229이다.

세번째 항목은 'Z32'이다. 이것은 null로 끝나는 32캐릭터 필드를 의미한다. 위의 버퍼를 보면 'boxes of paperclips'가 저장되어 있고 32바이트 중 나머지 부분은 0(널 캐릭터)으로 채워져 있는 것을 볼 수 있다.

마지막 항목은 's2'이다. 이것은 두 개의 short값을 필요로 하고 short값은 16비트 정수이다. 이 항목은 pack에 전달된 값 리스트에서 두 개의 값을 가져온다. 16비트는 두 바이트에 저장된다. 첫번째 값은 2이고 두번째 값은 긴급을 1로서 긴급함을 나타낸다. 이 두 값은 버퍼의 마지막 네 바이트를 차지하고 있다.

1.5. Perl의 unpack 함수

우리가 이 웹 어플리케이션을 만드는 동안에, 우리도 모르는 사이에 누군가 백엔드 프로그램을 C에서 Perl로 포팅하고 있었다.(제대로 말을 했어야지!5) 그러나 이미 우리가 웹 어플리케이션을 만들어놨기 때문에, 그들은 동일한 데이타 포맷을 사용하면 되겠다고 생각했다. 따라서 그들은 우리가 보낸 데이타를 다시 unpack해야 한다.

unpacK은 pack의 정반대이다. pack은 템플릿 스트링과 값의 리스트를 받아서 스칼라를 반환한다. unpack은 템플릿 스트링과 스칼라를 받아서 값의 리스트를 반환한다.

이론적으로는, 우리가 pack을 사용하여 얻은 스칼라와, pack할 때 사용한 것과 동일한 템플릿을 인자로 준다면, 결과적으로 pack에 인자로 주었던 값의 리스트를 그대로 얻을 수 있어야 한다. 왜 "이론적으로"라고 말했냐 하면, 만일 바이트 순서가 다르거나(big 아니면 little Endian(33)), word 사이즈가 다른(16,32,64 비트) 머신에서 unpack을 한다면, unpack을 pack이 만들어낸 데이타를 다르게 해석할 것이기 때문이다. 우리가 위에서 사용한 포맷들은 전부 우리 머신 고유의 바이트 순서를 사용했고 'i'는 다른 머신에서는 사이즈가 다를 수 있기 때문에, 문제가 발생할 수 있다. 그러나 예를 간단하게 들기 위해서, 백엔드 시스템은 웹 인터페이스 부분과 동일한 머신에서 실행된다고 가정한다.

우리가 작성한 데이타를 unpack하기 위해서, 백엔드 프로그램은 다음과 같은 구문을 사용할 것이다.

    ($order_time, $monk, $itemname, $quantity, $ignore) =
        unpack( "l i Z32 s2", $rec );

여기에 사용된 템플릿이 우리가 pack할 때 사용한 템플릿과 동일하고, 같은 정보가 같은 순서로 반환되는 것에 주목하라.(우리가 pack한 $urgent 변수를 받는데 그들이 $ignore 변수를 사용한 것만 빼면 말이지. 도대체 저 친구들은 무슨 말을 하고 싶은 거지?)

1.6. 정수 포맷

(Integer(34) 포맷, 내지는, 왜 이리 많은 템플릿 타입이 필요하지?)

같은 데이타 타입의 값을 작성하는데 왜 이렇게 많은 방법이 존재하는지 의아할 수 있다. 'i', 'l', 'N', 'V'는 모두 32비트 정수를 버퍼에 기록하는데 사용된다. 그 중 하나를 골라야 하는 이유는? 음, 그건 당신에 정보를 교환할 대상이 무엇이냐에 달려있다.

동일한 머신 위에서 수행되는 프로그램들 사이에 정보를 교환한다면, 'i', 'l', 's', 'q' 또는 무부호형을 의미하는 'I', 'L', 'S', 'Q'를 사용하면 된다. 읽는 프로그램과 쓰는 프로그램이 동일한 시스템 아키텍처 위에서 실행되기 때문에, 이런 고유 포맷을 사용하는 게 낫다.

아키텍처에 따라 파일의 내용의 배치가 달라지는 경우에 그 파일을 읽는 프로그램을 작성하고 있다면, 'n', 'N', 'v', 'V'를 사용하라. 이렇게 하면 프로그램이 어떤 아키텍처 위에서 돌아가더라도 정보를 정확하게 해석할 수 있다. 예를 들어서 'wav' 파일 포맷은 little endian(35)을 쓰는 인텔 프로세서 위에서 돌아가는 윈도우즈에서 정의된 포맷이다. 'wav' 파일의 헤더를 읽으려 하는 경우, 'v'나 'V'를 사용하여 16비트 또는 32비트 값을 읽어야 한다.

'n'과 'N' 포맷은 "네트워크 순서"라 불리는데 이것이 TCP/IP 통신에 사용하기 위해 명세된 순서이기 때문이다. 네트워크 프로그래밍을 한다면 이 포맷을 사용해야 한다.

1.7. 스트링 포맷

여러가지 string(36) 포맷들 중에 무엇을 사용해야 할 지 정하는 것은 정수의 경우와 좀 달라진다. 아마도 다른 프로그램이 어떤 언어로 작성되었느냐에 따라서 'a(37)', 'A(38)', 'Z(39)' 중에 하나를 선택하게 될 것이다. 다른 프로그램이 C나 C++로 작성되었다면 'a'나 'Z'를 쓰게 될 것이다. COBOL이나 FORTRAN이라면 'A'가 좋은 선택이 될 것이다.

1.7.1. 'a', 'A', 그리고 'Z' 포맷

pack을 할 때, 'a(40)'와 'Z(41)'는 여분의 공간을 null로 채운다. 'A(42)'는 여분의 공간을 스페이스로 채운다. unpack 할 때는, 'A'는 제일 뒤에 붙은 공백과 널 캐릭터들을 제거하고, 'Z'는 첫번째 널 캐릭터 이하를 전부 제거하고, 'a'는 필드 전체를 고스란히 반환한다.

예:

    pack('a8',"hello") produces "hello\0\0\0"
    pack('Z8',"hello") produces "hello\0\0\0"
    pack('A8',"hello") produces "hello   "
    unpack('a8',"hello\0\0\0") produces "hello\0\0\0"
    unpack('Z8',"hello\0\0\0") produces "hello"
    unpack('A8',"hello   ") produces "hello"
    unpack('A8',"hello\0\0\0") produces "hello"

1.7.2. 'b'와 'B' 포맷

'b(43)'와 'B(44)' 포맷은 '0'과 '1' 캐릭터들로 구성된 스트링을 바이트 시퀀스로 pack하거나, 바이트 시퀀스를 '0'과 '1' 캐릭터들의 문자열로 unpack한다. pack할 때 Perl은 짝수값의 문자들은 0으로 취급하고 홀수값의 문자들은 1로 취급한다.6 두 포맷의 차이점은 각각의 바이트 안에서 비트들의 순서가 다르다는 것이다. 'b'를 사용할 경우, 비트는 오름차순으로 명시된다. 'B'를 사용할 경우는 내림차순으로 명시된다. 카운트 값은 pack할 비트의 수를 나타낸다.

예:

    ord(pack('b8','00100110')) produces 100 (4 + 32 + 64)
    ord(pack('B8','00100110')) produces 38 (32 + 4 + 2)

1.7.3. 'h'와 'H' 포맷

'h(45)'와 'H(46)' 포맷은 16진수 숫자들의 스트링을 pack한다. 'h'는 낮은 니블(nybble)을 먼저, 'H'는 높은 니블을 먼저 사용한다. 카운트 값은 pack할 니블의 갯수를 나타낸다. 니블이 무엇인지 궁금하다면, 니블은 바이트의 절반이다.

예:

    pack('h4','1234') produces 0x21,0x43
    pack('H4','1234') produces 0x12,0x34

1.8. 추가 정보

Perl 5.8(47)에는 pack과 unpack에 대한 [튜토리얼]이 포함되어 있다. 이 튜토리얼은 이 문서보다 더 깊이 있는 내용을 다루고 있으나, 내용 중 일부는 perl 5.8에 특화된 것일 수 있다. 아직 Perl 5.6(48)을 사용하고 있다면 저 튜토리얼에 있는 설명대로 동작하지 않는 경우는 5.6의 문서를 직접 체크해야 한다.

여기에서 다루지 않은 템플릿 문자들이 더 있다. 또한 ASCII 필드를 읽고 쓰는 방법들과 pack과 unpack을 가지고 할 수 있는 트릭들도 더 있다. 당신의 시스템에 있는 perldoc -f pack 매뉴얼이나 "[Programming Perl]" 서적을 읽어 보라. 무엇보다도, 직접 실험해보는 것을 두려워하지 말라 (잘 돌고 있는 프로그램에 하는 경우는 말고). pack이 데이타를 어떻게 다루는지 이해가 될 때까지, 아래 예제 코드에 있는 DumpString 함수를 사용하여 pack이 반환한 버퍼의 내용을 검사해 보라.

1.9. 참고

[Programming Perl], Third Edition, Larry Wall, Tom Christiansen, and Jon Orwant, ⓒ 2000, 1996, 1991 O'Reilly & Associates, Inc. ISBN 0-596-00027-8

Thanks to [bart] for [the reference] to the pack/unpack tutorial from perl 5.8(49).

Thanks to [Zaxo] and [jeffa] for reviewing this document and sharing their own efforts at creating a tutorial.

Thanks to [sulfericacid] and [PodMaster] for inspiring this on the CB.

1.10. 예제 코드

아래의 프로그램은 이 문서에 있는 예제들이 담겨 있다.7

#!/usr/bin/perl -w
use strict;

# 스트링의 각 바이트를 10진수, 16진수, 캐릭터로 덤프한다
sub DumpString {
    my @a = unpack('C*',$_[0]);
    my $o = 0;
    while (@a) {
        my @b = splice @a,0,16;
        my @d = map sprintf("%03d",$_), @b;
        my @x = map sprintf("%02x",$_), @b;
        my $c = substr($_[0],$o,16);
        $c =~ s/[[:^print:]]/ /g;
        printf "%6d %s\n",$o,join(' ',@d);
        print " "x8,join('  ',@x),"\n";
        print " "x9,join('   ',split(//,$c)),"\n";
        $o += 16;
    }
}

# 웹으로 주문을 내린다
my $t = time;
my $emp_id = 217641;
my $item = "boxes of paperclips";
my $quan = 2;
my $urgent = 1;
my $rec = pack( "l i a32 s2", $t, $emp_id, $item, $quan, $urgent);
DumpString($rec);

# 웹으로 들어온 주문을 처리한다
my ($order_time, $monk, $itemname, $quantity, $ignore) =
       unpack( "l i a32 s2", $rec );
print "Order time: ",scalar localtime($order_time),"\n";
print "Placed by monk #$monk for $quantity $itemname\n";

# 스트링 포맷들
$rec = pack('a8',"hello");               # should produce 'hello\0\0\0'
DumpString($rec);
$rec = pack('Z8',"hello");               # should produce 'hello\0\0\0'
DumpString($rec);
$rec = pack('A8',"hello");               # should produce 'hello   '
DumpString($rec);
($rec) = unpack('a8',"hello\0\0\0");     # should produce 'hello\0\0\0'
DumpString($rec);
($rec) = unpack('Z8',"hello\0\0\0");     # should produce 'hello'
DumpString($rec);
($rec) = unpack('A8',"hello   ");        # should produce 'hello'
DumpString($rec);
($rec) = unpack('A8',"hello\0\0\0");     # should produce 'hello'
DumpString($rec);

# 비트 포맷
$rec = pack('b8',"00100110");            # should produce 0x64 (100)
DumpString($rec);
$rec = pack('B8',"00100110");            # should produce 0x26 (38)
DumpString($rec);

# 16진수 포맷
$rec = pack('h4',"1234");                # should produce 0x21,0x43
DumpString($rec);
$rec = pack('H4',"1234");                # should produce 0x12,0x34
DumpString($rec);

2. perlpacktut - Tutorial on pack and unpack - 문서 정리

2.1. 설명 Description

pack과 unpack은 데이타를 "Perl에서 값들이 저장되는 방식" 또는 "Perl 프로그램이 사용되는 환경에서 요구하는 잘 정의된 표현법" 사이에서 변환시킨다. 이 변환은 사용자가 정의한 템플릿에 따라서 이뤄진다.

2.2. 기본 원리 The Basic Prinicple

C 언어처럼 변수의 주소와 크기 등을 얻어내어 메모리에 직접 접근할 수는 없지만, pack과 unpack을 사용하여 원하는 작업을 할 수 있다.

바이너리로 표현된 값들이 왜 필요한가?

간단한 예:

# $mem에 어떤 바이트 시퀀스 - 내용이 뭐가 됐든 - 가 들어있는 상태에서
my ( $hex ) = unpack( 'H*', $mem );
print "$hex\n";
# 출력예
41204d414e204120504c414e20412043414e414c2050414e414d41

반대 동작 - 16진수 숫자들의 스트링을 바이트 덩어리로 압축

my $s = pack( 'H2' x 10, map { "3$_" } ( 0..9 ) );
print "$s\n";

# 출력
0123456789

2.3. 텍스트 팩킹 Packing Text

다음과 같은 데이타 파일을 읽는 경우 필드를 어떻게 구분할까:

    Date      |Description                | Income|Expenditure
    01/24/2001 Ahmed's Camel Emporium                  1147.99
    01/28/2001 Flea spray                                24.99
    01/29/2001 Camel rides to tourists      235.00
    while (<>) {
        my $date   = substr($_,  0, 11);
        my $desc   = substr($_, 12, 27);
        my $income = substr($_, 40,  7);
        my $expend = substr($_, 52,  7);
        ...
    }
    while (<>) {
        my($date, $desc, $income, $expend) =
            m|(\d\d/\d\d/\d{4}) (.{27}) (.{7})(.*)|;
        ...
    }
    while (<>) {
        my($date, $desc, $income, $expend) = unpack("A10xA27xA7xA*", $_);
        ...
    }
    my($date) = unpack("A10", $_);

위 예에서 수입, 지출의 합을 구하고, 그 내역을 제일 마지막 행에 추가하는 예:

    while (<>) {
        my($date, $desc, $income, $expend) = unpack("A10xA27xA7xA*", $_);
        $tot_income += $income;
        $tot_expend += $expend;
    }

    $tot_income = sprintf("%.2f", $tot_income); # Get them into 
    $tot_expend = sprintf("%.2f", $tot_expend); # "financial" format

    $date = POSIX::strftime("%m/%d/%Y", localtime);

    # OK, let's go:
    print pack("A10xA27xA7xA*", $date, "Totals", $tot_income, $tot_expend);
    print pack("A11 A28 A8 A*", $date, "Totals", $tot_income, $tot_expend);
# 템플릿 스트링 안에 있는 공백은 단지 가독성을 위해 추가한 것이고, 실제 변환시 공백으로 변환되는 것이 아님
    $tot_income = sprintf("%.2f", $tot_income);
    $tot_expend = sprintf("%12.2f", $tot_expend);
    $date = POSIX::strftime("%m/%d/%Y", localtime);
    print pack("A11 A28 A8 A*", $date, "Totals", $tot_income, $tot_expend);
01/24/2001 Ahmed's Camel Emporium                  1147.99
01/28/2001 Flea spray                                24.99
01/29/2001 Camel rides to tourists     1235.00
11/07/2008 Totals                      1235.00     1172.98

여기까지의 내용 정리:

경고:

pack("A*A*", $one, $two);

2.4. 수 팩킹 Packing Numbers

2.4.1. 정수 Integers

Integer(60)를 표현하는 바이너리 표현법의 주요 속성:

20302를 16비트 정수형으로 팩킹: "s(63)"

   my $ps = pack( 's', 20302 );

$ps를 언팩하면 원래의 정수값을 얻음:

   my( $s ) = unpack( 's', $ps );

이것 - pack한 결과를 unpack하면 원래의 값을 얻을 수 있는 것 - 은 기본적으로 수와 관련된 모든 템플릿에 해당되나,

l(65), L(66) : 부호형/무부호형 32비트 정수

q(67), Q(68) : 부호형/무부호형 64비트 정수

i(69), I(70) : 부호형/무부호형 정수를 시스템 고유의 형태(원문: of "local custom" variety)로 저장 : 이런 정수는 최소 32비트를 차지하고, 최대로는 로컬 C 컴파일러에서 sizeof(int)를 수행하여 얻은 리턴값만큼의 바이트를 차지한다.

s,S,l,L,q,Q로 정수를 pack하면 어느 시스템에서 실행하든지 항상 고정된 갯수의 바이트가 나온다. 이것은 어떤 어플리케이션에서는 유용하지만, 데이타 구조를 Perl과 C 프로그램 사이에서 전달하는 경우 (XS 확장을 호출12하거나 syscall함수를 호출할 때 발생난다), 또는 바이너리 파일을 읽거나 쓰는 경우 이식성 있는 방법을 제공해주지 못한다. 시스템의 C 컴파일러가 "short", "unsigned long" 등의 코드를 컴파일했을 때 나오는 결과와 동일한 사이즈의 바이트 수를 사용하고 싶은 경우는 다음 표와 같이 느낌표를 붙여서 사용한다.

   signed unsigned  byte length in C   byte length in Perl
     s!     S!      sizeof(short)      $Config{shortsize}
     i!     I!      sizeof(int)        $Config{intsize}
     l!     L!      sizeof(long)       $Config{longsize}
     q!     Q!      sizeof(long long)  $Config{longlongsize}

2.4.2. 스택 프레임 unpack하기 Unpacking a Stack Frame

프로그램이 실행되는 시스템과 전혀 별개의 아키텍처에서 전달된 바이너리 데이타를 다뤄야 하는 경우 특정한 바이트 순서를 요청해야 한다.

Intel8086 머신에서 스택 프레임의 내용을 담고 있는 24바이트를 다루는 예:

      +---------+        +----+----+               +---------+
 TOS: |   IP    |  TOS+4:| FL | FH | FLAGS  TOS+14:|   SI    |
      +---------+        +----+----+               +---------+
      |   CS    |        | AL | AH | AX            |   DI    |
      +---------+        +----+----+               +---------+
                         | BL | BH | BX            |   BP    |
                         +----+----+               +---------+
                         | CL | CH | CX            |   DS    |
                         +----+----+               +---------+
                         | DL | DH | DX            |   ES    |
                         +----+----+               +---------+
   my( $ip, $cs, $flags, $ax, $bx, $cd, $dx, $si, $di, $bp, $ds, $es ) =
     unpack( 'v12', $frame );
   my( $fl, $fh, $al, $ah, $bl, $bh, $cl, $ch, $dl, $dh ) =
     unpack( 'C10', substr( $frame, 4, 10 ) );
   my( $ip, $cs,
       $flags,$fl,$fh,
       $ax,$al,$ah, $bx,$bl,$bh, $cx,$cl,$ch, $dx,$dl,$dh,
       $si, $di, $bp, $ds, $es ) =
   unpack( 'v2' . ('vXXCC' x 5) . 'v5', $frame );

2.4.3. 네트워크 위에서는 달걀을 어느 쪽으로 깨어 먹는가13

big endian(75) 형태의 정수 pack

이 순서는 표준 "network order"로 택해진 관례이기 때문에, 네트워크를 통하여 바이너리 데이타를 교환할 때는 반드시 이것을 사용할 것.

어떤 메시지의 길이와 그 메시지를 이어서 전송하는 경우의 예:

   my $buf = pack( 'N', length( $msg ) ) . $msg;
# 또는 아예 묶어서
   my $buf = pack( 'NA*', length( $msg ), $msg );
# 이제 $buf를 송신 루틴에 전달

2.4.4. 바이트 순서 변경자 Byte-order modifiers

n(78), N(79), v(80), V(81)는 부호형 정수나 64비트 정수를 다루지 못하는 한계가 있음.

signed big endian(82) 16-bit 정수를 플랫폼에 독립적인 방법으로 unpack하기 위해서는 다음과 같이 해야 한다:

   my @data = unpack 's*', pack 'S*', unpack 'n*', $buf;

Perl 5.9.2(83)에서는 원하는 바이트-오더를 명시할 수 있는 방법을 제공한다.

   my @data = unpack 's>*', $buf;

이 변경자들은 C 구조체를 다룰 때 더욱 유용하다.

2.4.5. 부동소수 Floating point Numbers

부동소수(Floating Point Number(88)) 관련 템플릿:

실수값에 대한 네트워크 표현법은 없다. 따라서 상대 시스템이 무엇인지 확실하지 않다면 ASCII 형태로 전송하는 것이 좋다. 더 모험적으로는, 위에 나왔던 바이트 순서 변경자를 부동 소수 코드에도 사용할 수 있다.

2.5. Exotic Templates

2.5.1. 비트 스트링 Bit Strings

'0'과 '1' 캐릭터들로 구성된 스트링과 8개 비트의 그룹인 바이트들의 시퀀스 사이의 변환.

한 바이트의 내용이 비트 스트링으로 표현되는 두 가지 방식:

     7 6 5 4 3 2 1 0
   +-----------------+
   | 1 0 0 0 1 1 0 0 |
   +-----------------+
    MSB           LSB
   $byte = pack( 'B8', '10001100' ); # start with MSB
   $byte = pack( 'b8', '00110001' ); # start with LSB

pack은 항상 다음 바이트의 경계에서 시작해서 8의 배수만큼 자르며, 필요하다면 0 비트를 추가해서 채워넣는다. 따라서 비트 필드를 pack,unpack 할 수는 없음. 비트 필드를 다루려면 vec 함수를 쓰거나, unpack해서 얻은 비트 스트링에 split, substr, concatenation 등을 사용하여 캐릭터 스트링 차원에서 처리할 것.

unpack 예제 - 상태 레지스터 분해:

   +-----------------+-----------------+
   | S Z - A - P - C | - - - - O D I T |
   +-----------------+-----------------+
    MSB           LSB MSB           LSB
   ($carry, undef, $parity, undef, $auxcarry, undef, $zero, $sign,
    $trace, $interrupt, $direction, $overflow) =
      split( //, unpack( 'b16', $status ) );

2.5.2. Uuencoding

u(95) - "uuencoded string(96)"을 pack함.

ASCII 데이타 이외의 데이타를 지원하지 않는 전송 매체를 사용할 때 쓰던 인코딩 방식이라, 현재는 쓸 필요가 거의 없다.

인코딩 방법:

   my $uubuf = pack( 'u', $bindat );

u 뒤에 붙는 카운트는 uuencoding된 라인 하나에 삽입할 바이트 수를 설정. 최대값은 디폴트로 45이고, 그 이하의 정수 중 3의 배수로 설정할 수 있다. unpack은 반복 카운트는 무시.

2.5.3. 합 구하기 Doing Sums

'%(97)<number>'

    my $buf = pack( 'iii', 100, 20, 3 );
    print unpack( '%32i3', $buf ), "\n";  # prints 123
    print unpack( '%32A*', "\x01\x10" ), "\n";  # prints 17
    my $bitcount = unpack( '%32b*', $mask );
    my $evenparity = unpack( '%1b*', $mask );

2.5.4. Unicode

PerlUnicode(100) string(101)을 처리할 때 내부적으로 UTF-8을 사용한다.

'U(102)'

   $UTF8{Euro} = pack( 'U', 0x20AC );
   # Equivalent to: $UTF8{Euro} = "\x{20ac}";
   $Unicode{Euro} = unpack( 'U', $UTF8{Euro} );
   # pack and unpack the Hebrew alphabet
   my $alefbet = pack( 'U*', 0x05d0..0x05ea );
   my @hebrew = unpack( 'U*', $utf );

일반적인 경우, Encode::decode_utf8를 사용하여 UTF-8로 인코딩된 바이트 스트링을 Perl 유니코드 스트링으로 디코드하고, Encode::encode_utf8을 써서 Perl 유니코드 스트링을 UTF-8 바이트들로 인코딩하는 것이 좋다. 잘못된 바이트 시퀀스를 처리할 수 있고 인터페이스도 더 친숙하다.

2.5.5. Another Portable Binary Encoding

'w(103)'

   my $berbuf = pack( 'w*', 1, 128, 128+1, 128*128+127 );

2.6. Template Grouping

Perl 5.8(104)부터는 '((105)'와 ')(106)'를 repeat count(107)와 합쳐서 사용할 수 있다.

앞서 봤는 스택 프레임을 unpack하는 예는 다음과 같이 고쳐 쓸 수 있다:

   unpack( 'v2 (vXXCC)5 v5', $frame )

또 다른 예:

   join( '', map( substr( $_, 0, 1 ), @str ) )
   pack( '(A)'.@str, @str )
   pack( '(A)*', @str )

(일, 월, 년) 형태로 된 날짜 데이타가 @dates 배열에 저장되어 있을 때 이것을 byte, byte, short 정수로 pack 하는 경우:

   $pd = pack( '(CCS)*', map( @$_, @dates ) );

길이가 짝수인 스트링 안에 있는 캐릭터들을 둘씩 짝지은 후 맞바꾸는 예:

   $s = pack( '(A)*', unpack( '(xAXXAx)*', $s ) );
   $s = pack( '(A)*', unpack( '(@1A @0A @2)*', $s ) );
   $s = pack( '(v)*', unpack( '(n)*', $s );

2.7. 길이와 폭 Lenghts and Widths

2.7.1. 스트링의 길이 String Lenghts

string(111)의 끝에 null을 붙이는 방식과, 스트링의 길이를 미리 명시하는 방식을 동시에 사용한 예:

   my $msg = pack( 'Z*Z*CA*', $src, $dst, length( $sm ), $sm );
   ( $src, $dst, $len, $sm ) = unpack( 'Z*Z*CA*', $msg );

이 때 문제가 있다. $sm 뒤에 다른 필드를 추가할 경우, pack은 문제가 없지만 unpack을 제대로 할 수 없다.

   # pack a message
   my $msg = pack( 'Z*Z*CA*C', $src, $dst, length( $sm ), $sm, $prio );

   # unpack fails - $prio remains undefined!
   ( $src, $dst, $len, $sm, $prio ) = unpack( 'Z*Z*CA*C', $msg );

'/(112)'로 두 개의 pack 코드를 조합하면 인자 리스트에서 하나의 값에 결합된다:

   # pack a message: ASCIIZ, ASCIIZ, length/string, byte
   my $msg = pack( 'Z* Z* C/A* C', $src, $dst, $sm, $prio );

   # unpack
   ( $src, $dst, $sm, $prio ) = unpack( 'Z* Z* C/A* C', $msg );

슬래쉬 앞에 오는 코드는 수를 표현할 수 있는 것이면 아무거나 쓸 수 있다. 수를 바이너리로 pack하는 모든 코드들과, 심지어 'A4'나 'Z*'같은 텍스트 코드도 가능하다:

   # pack/unpack a string preceded by its length in ASCII
   my $buf = pack( 'A4/A*', "Humpty-Dumpty" );
   # unpack $buf: '13  Humpty-Dumpty'
   my $txt = unpack( 'A4/A*', $buf );

'/(113)'는 Perl 5.6(114)에는 구현되지 않았으므로, 구버전 펄에서 동작하게 하려면 일단 unpack('Z* Z* C')를 하여 길이값을 얻어낸 후에, 그 값을 사용해서 스트링을 다시 unpack해야 한다:

   # pack a message: ASCIIZ, ASCIIZ, length, string, byte (5.005 compatible)
   my $msg = pack( 'Z* Z* C A* C', $src, $dst, length $sm, $sm, $prio );

   # unpack
   ( undef, undef, $len) = unpack( 'Z* Z* C', $msg );
   ($src, $dst, $sm, $prio) = unpack ( "Z* Z* x A$len C", $msg );

2.7.2. 동적 템플릿 Dynamic Templates

pack할 항목들의 길이가 고정되어 있지 않고 '((115)',')(116)','*(117)'를 사용할 수 없는 상황이라면 템플릿을 생성하는 식을 사용해야 한다.

예: 이름이 있는 스트링 값들을 C 프로그램에서 분석할 수 있는 형태로 저장한다. 이름, "=", 널로 끝나는 ASCII 스트링 순서로 각 값들을 나열하고 제일 마지막에는 널 바이트를 종결자로 추가한다.18

   my $env = pack( '(A*A*Z*)' . keys( %Env ) . 'C',
                   map( { ( $_, '=', $Env{$_} ) } keys( %Env ) ), 0 );

역으로 동작시키기 위해서, 먼저 항목들의 갯수를 알아내야 한다:

   my $n = $env =~ tr/\0// - 1;
   my %env = map( split( /=/, $_ ), unpack( "(Z*)$n", $env ) );

2.7.3. 반복 횟수를 세기 Counting Repetitions

각 데이타 항목의 끝과 전체 리스트의 끝에다가 끝을 알리는 감시 문자를 저장하는 것보다, 데이타 앞에 카운트를 넣을 수도 있다.

해쉬의 키와 값을 pack하는 예: 각각의 앞에 unsigned short 형의 카운트값을 붙이고, 제일 앞에는 키-값 쌍의 갯수를 저장한다.

   my $env = pack( 'S(S/A* S/A*)*', scalar keys( %Env ), %Env );

반복하는 횟수를 '/(118)'를 사용하여 Unpack할 수 있기 때문에, 역으로 변환하는 과정이 매우 간단해진다:

   my %env = unpack( 'S/(S/A* S/A*)', $env );

이 예는 pack과 unpack에서 동일한 템플릿을 사용할 수 없는, 드문 경우 중에 한 가지임에 유의할 것. pack에서는 ((119))(120)-그룹의 반복 횟수를 결정할 수 없다.

2.8. C 구조체 Packing and Unpacking C Structures

다뤄야 할 C 구조체가 많고, 모든 템플릿을 직접 만들고 싶지 않다면 Cpan:Convert::Binary::C 모듈을 알아보라. 이 모듈은 C 소스를 직접 분석할 수도 있고, 이 섹션에 나올 잡다한 것들을 지원할 수 있게 짜여져 있다.

2.8.1. 열맞추기 The Alignment Pit

속도와 메모리 용량간의 균형에서 빠른 속도가 더 우선시되며, 이는 C 컴파일러가 구조체를 저장할 메모리를 할당하는 방식에 영향을 끼친다: 어떤 아키텍처에서는 16비트 또는 32비트 피연산자가 메모리 주소가 짝수,4의 배수,또는 8의 배수인 곳에 정렬되면 메모리 내에서의 이동이나 레지스터와의 교환이 더 빠르게 이뤄진다. 이 경우 C 컴파일러는 구조체에 여분의 바이트를 채워넣는다.

다음 두 개의 C 구조체를 비교해보자.

typedef struct {
    char c1;
    short s;
    char c2;
    long l;
} gappy_t;

typedef struct {
    long l;
    short s;
    char c1;
    char c2;
} dense_t;
   0           +4          +8          +12
   +--+--+--+--+--+--+--+--+--+--+--+--+
   |c1|xx|  s  |c2|xx|xx|xx|     l     |    xx = fill byte
   +--+--+--+--+--+--+--+--+--+--+--+--+
   gappy_t

   0           +4          +8
   +--+--+--+--+--+--+--+--+
   |     l     |  h  |c1|c2|
   +--+--+--+--+--+--+--+--+
   dense_t

'x'를 사용하여 정렬을 맞추는 한 가지 예

  my $gappy = pack( 'cxs cxxx l!', $c1, $s, $c2, $l );

길이가 긴 구조체의 바이트 수를 세고 정렬을 살펴보는 것은 쉽지 않다.

   #include <stdio.h>
   #include <stddef.h>

   typedef struct {
     char     fc1;
     short    fs;
     char     fc2;
     long     fl;
   } gappy_t;

   #define Pt(struct,field,tchar) \
     printf( "@%d%s ", offsetof(struct,field), # tchar );

   int main() {
     Pt( gappy_t, fc1, c  );
     Pt( gappy_t, fs,  s! );
     Pt( gappy_t, fc2, c  );
     Pt( gappy_t, fl,  l! );
     printf( "\n" );
   }
  my $gappy = pack( '@0c @2s! @4c @8l!', $c1, $s, $c2, $l );

오프셋도 'x'도 쓰지 않고 갭을 건너려면

  my $gappy = pack( 'c x!2 s c x!4 l!', $c1, $s, $c2, $l );
  my $gappy = pack( 'c x![s] s c x![l!] l!', $c1, $s, $c2, $l );

2.8.2. 엔디안 다루기 Dealing with Endian-ness

바이트 순서가 다른 머신에게 데이타를 넘겨주기 위해 pack하는 경우.

  my $gappy = pack( 'c x![s] s c x![l] l', $c1, $s, $c2, $l );
  my $gappy = pack( 'c x![s] s< c x![l] l<', $c1, $s, $c2, $l );
  my $gappy = pack( '( c x![s] s c x![l] l )<', $c1, $s, $c2, $l );

2.8.3. 열맞추기, 장면2 Alignment, Take 2

구조체의 배열의 경우:

   typedef struct {
     short    count;
     char     glyph;
   } cell_t;

   typedef cell_t buffer_t[BUFLEN];

count의 앞, 또는 count와 glyph 사이에 패딩을 할 필요가 없으므로, 간단히 다음과 같이 쓸 지 모른다:

# 뭔가 잘못 되고 있음:
   pack( 's!a' x @buffer,
         map{ ( $_->{count}, $_->{glyph} ) } @buffer );

구조체나 배열의 정렬은, 각 성분의 에도 패딩을 해야 할 것인지 고려해야 한다. 올바른 템플릿은 다음과 같다:

   pack( 's!ax' x @buffer,
         map{ ( $_->{count}, $_->{glyph} ) } @buffer );

2.8.4. 열맞추기, 장면3 Alignment, Take 3

위에 나온 모든 얘기를 고려한다 해도, 또 문제가 있다:

   typedef struct {
     char     foo[2];
   } foo_t;

2.8.5. 포인터를 어떻게 사용하는가 Pointers for How to Use Them

C 구조체를 pack할때 마주치는 두번째 문제가 포인터이다.

호출하려는 함수가 'void *' 타입의 값을 요구한다고 단순히 Perl 변수의 레퍼런스를 줄 수 있는 게 아니다.

'P(129)' - "고정된 길이의 스트링을 가리키는 포인터"를 pack한다.

    # allocate some storage and pack a pointer to it
    my $memory = "\x00" x $size;
    my $memptr = pack( 'P', $memory );

pack이 반환한 것은 바이트의 시퀀스이고, C 코드가 요구하는 것은 포인터, 즉 어떤 수이다. 따라서 pack이 리턴한 바이트 문자열을 숫자로 된 주소로 바꿔야 한다:

    my $ptr = unpack( 'L!', $memptr );

포인터를 활용하는 예:

    ssize_t read(int fd, void *buf, size_t count);
    require 'syscall.ph';
    sub cat($){
        my $path = shift();
        my $size = -s $path;
        my $memory = "\x00" x $size;  # allocate some memory
        my $ptr = unpack( 'L', pack( 'P', $memory ) );
        open( F, $path ) || die( "$path: cannot open ($!)\n" );
        my $fd = fileno(F);
        my $res = syscall( &SYS_read, fileno(F), $ptr, $size );
        print $memory;
        close( F );
    }

'P(130)'를 unpack에서 사용하면 어떻게 동작하는가?

   my $mem = "abcdefghijklmn";
   print unpack( 'P5', pack( 'P', $mem ) ); # prints "abcde"

p(132) - unpack할 때 사용하면, 버퍼에서 얻은 주소에서 시작하는 "널로 끝나는 스트링"을 반환한다.

   my $buf = pack( 'p', "abc\x00efhijklmn" );
   print unpack( 'p', $buf );    # prints "abc"

주인장의 보충:

"Pack / Unpack Tutorial"의 마지막 예제 코드에 있는 DumpString를 사용하여 덤프를 출력하며 비교해보자.

my $str1 = "ABC\x{41}DEFGHI";    # 널 캐릭터 포함해서 10글자짜리
my $str2 = "abc\x{00}defghi";
my $str3 = "123\x{00}567890";

my $pack1 = pack( 'P' , $str1, $str2, $str3 );
my $pack2 = pack( 'P2', $str1, $str2, $str3 );
my $pack3 = pack( 'P*', $str1, $str2, $str3 );
my $pack4 = pack( 'PP', $str1, $str2, $str3 );
my $pack5 = pack( 'P2P3', $str1, $str2, $str3 );

DumpString( $pack1 );
DumpString( $pack2 );
DumpString( $pack3 );
DumpString( $pack4 );
DumpString( $pack5 );
     0 048 130 084 009
        30  82  54  09
         0       T
     0 048 130 084 009
        30  82  54  09
         0       T
     0 048 130 084 009
        30  82  54  09
         0       T
     0 048 130 084 009 136 081 084 009
        30  82  54  09  88  51  54  09
         0       T           Q   T
     0 048 130 084 009 136 081 084 009
        30  82  54  09  88  51  54  09
         0       T           Q   T

print "[", join( '|', unpack( 'P'  , $pack1 ) ), "]\n";   # "A"
print "[", join( '|', unpack( 'P2' , $pack2 ) ), "]\n";   # "AB"
print "[", join( '|', unpack( 'P10', $pack2 ) ), "]\n";   # "ABC\0DEFGHI"
print "[", join( '|', unpack( 'P*' , $pack3 ) ), "]\n";   # 런타임에 에러가 난다.
print "[", join( '|', unpack( 'PP' , $pack4 ) ), "]\n";   # ( "A", "a" )
print "[", join( '|', unpack( 'P2P3' , $pack4 ) ), "]\n"; # ( "AB", "abc" )
[A]
[AB]
[ABCDEFGHI]
'P' must have an explicit size in unpack at ./t07.pl line 25.
      <- 실제로는 이 시점에서 에러가 나며 프로그램이 종료되므로, 'P*' 라인을 지워야 이하의 출력이 나온다.
[A|a]
[AB|abc]

my $pack1 = pack( 'p' , $str1, $str2, $str3 );
my $pack2 = pack( 'p2', $str1, $str2, $str3 );
my $pack3 = pack( 'p*', $str1, $str2, $str3 );
my $pack4 = pack( 'pp', $str1, $str2, $str3 );
my $pack5 = pack( 'p2p3', $str1, $str2, $str3 );

DumpString( $pack1 );
DumpString( $pack2 );
DumpString( $pack3 );
DumpString( $pack4 );
DumpString( $pack5 );

     0 048 130 084 009
        30  82  54  09
         0       T
     0 048 130 084 009 136 081 084 009
        30  82  54  09  88  51  54  09
         0       T           Q   T
     0 048 130 084 009 136 081 084 009 176 223 084 009
        30  82  54  09  88  51  54  09  b0  df  54  09
         0       T           Q   T               T
     0 048 130 084 009 136 081 084 009
        30  82  54  09  88  51  54  09
         0       T           Q   T
     0 048 130 084 009 136 081 084 009 176 223 084 009 232 074 083 009
        30  82  54  09  88  51  54  09  b0  df  54  09  e8  4a  53  09
         0       T           Q   T               T           J   S
    16 232 074 083 009
        e8  4a  53  09
             J   S

print "[", join( '|', unpack( 'p'  , $pack1 ) ), "]\n";
print "[", join( '|', unpack( 'p2' , $pack2 ) ), "]\n";
print "[", join( '|', unpack( 'p10', $pack2 ) ), "]\n";
print "[", join( '|', unpack( 'p*' , $pack3 ) ), "]\n";
print "[", join( '|', unpack( 'pp' , $pack4 ) ), "]\n";
print "[", join( '|', unpack( 'p2p3' , $pack5 ) ), "]\n";
[ABC]
[ABC|abc]
[ABC|abc]
[ABC|abc|123]
[ABC|abc]
[ABC|abc|123||]

$x란 변수가 실제로 저장되어 있는데 'p'노 'P' 코드로 pack(..., $x)을 할 때는 신중해야 한다. Perl 내부에서는 변수와 변수의 주소 사이의 관계를 펄만의 비밀스런 방식으로 처리하며, 사용자가 사본을 획득한 것에 대해22 그다지 신경쓰지 않는다. 따라서

그러나, 스트링 리터럴을 P또는 p로 pack하는 것은 안전하다. 왜냐하면 Perl이 익명 변수를 할당하기 때문이다.

2.9. Pack 비책들 Pack Recipes

pack과 unpack을 사용한 유용한 비책들.

    # IP 주소를 소켓 함수에 인자로 줄 수 있는 형태로 변환
    pack( "C4", split /\./, "123.4.5.6" );

    # 어떤 메모리 영역 내에서 1인 비트의 수 합산
    unpack( '%32b*', $mask );

    # 시스템이 어떤 엔디안을 사용하고 있는지 검사
    $is_little_endian = unpack( 'c', pack( 's', 1 ) );
    $is_big_endian = unpack( 'xc', pack( 's', 1 ) );

    # 시스템 고유의 integer 타입이 몇 비트를 차지하는지 검사
    $bits = unpack( '%32I!', ~0 );

    # nanosleep 시스템 호출의 인자를 준비
    my $timespec = pack( 'L!L!', $secs, $nanosecs );

주인장 보충:

위 예 중에

    # 시스템 고유의 integer 타입이 몇 비트를 차지하는지 검사
    $bits = unpack( '%32I!', ~0 );
이것은 제대로 동작하지 않는다(적어도 주인장이 테스트한 Perl 5.8.8에서는 그랬다). 아래처럼 바꿔야 한다.
    $bits = unpack( '%32B*', pack( 'I!', ~0 ) );

2.10. 재미있죠? Funnies Section

    # 어디에서 숫자를 끌어왔을까...
    print unpack( 'C', pack( 'x' ) ),
          unpack( '%B*', pack( 'A' ) ),
          unpack( 'H', pack( 'A' ) ),
          unpack( 'A', unpack( 'C', pack( 'A' ) ) ), "\n";

    # 이별을 하며 건배를 ;-)
    my $advice = pack( 'all u can in a van' );

2.11. 저자

Simon Cozens and Wolfgang Laun.

3. perldoc -f pack - 문서 번역

pack TEMPLATE, LIST

값들의 LIST를 받아서 TEMPLATE에 의해 주어진 규칙을 사용하여 스트링으로 변환한다. 전형적으로, 변환된 값은 머신-레벨의 표현법으로 나타난다. 예를 들어, 32-비트 머신에서 정수는 네 개의 바이트의 시퀀스로 표현되며, 이 시퀀스는 네 개의 캐릭터의 시퀀스로 변환될 것이다.

TEMPLATE은 값들의 순서와 타입을 지정하는 문자들의 시퀀스이고, 다음과 같은 것들이 있다:

아래 있는 변경자를 하나 이상 옵션으로 템플릿 문자 뒤에 붙일 수 있다 (두번째 열에 있는 목록은 해당 변경자를 사용할 수 있는 문자들)

>와 < 변경자는 ()-그룹에도 사용할 수 있다. 이 경우 그 그룹과 그 안의 서브그룹들까지 포함해서 모든 성분들이 특정한 바이트 순서를 따르도록 강제한다.

다음의 규칙들이 적용된다:

숫자로 이뤄진 반복횟수 대신에 대괄호로 둘러싼 템플릿을 적어 줄 수 있다; 이 경우 그 템플릿을 pack한 결과의 길이(바이트 단위)가 카운트 값으로 사용된다. 예를 들어서, "x[L]"은 long만큼 스킵한다(즉 long타입이 차지하는 바이트의 수만큼 스킵); "$t X[$t] $t"는 $t가 unpack하는 대상을 두 번 unpack하게 된다. If the template in brackets contains alignment commands (such as "x![d]" ), its packed length is calculated as if the start of the template has the maximal possible alignment.25

'*'가 'z'와 함께 사용될 경우, 끝에 널 바이트를 추가하게 된다 (따라서 pack된 결과의 길이는 pack하는 대상 항목에 대해 length 함수를 수행한 결과보다 1이 더 클 것이다)

반복 카운트가 '@'와 함께 쓰일 경우, 가장 안쪽의 ()그룹의 시작 지점으로부터의 오프셋을 의미한다.

When used with ., the repeat count is used to determine the starting position from where the value offset is calculated. If the repeat count is 0, it's relative to the current position. If the repeat count is * , the offset is relative to the start of the packed string. And if its an integer n the offset is relative to the start of the n-th innermost () group (or the start of the string if n is bigger then the group level).

The repeat count for u is interpreted as the maximal number of bytes to encode per line of output, with 0, 1 and 2 replaced by 45. The repeat count should not be more than 65.

If the value-to-pack is too long, it is truncated. If too long and an explicit count is provided, Z packs only $count-1 bytes, followed by a null byte. Thus Z always packs a trailing null (except when the count is 0).

Starting from the beginning of the input string of pack(), each 8-tuple of characters is converted to 1 character of output. With format b the first character of the 8-tuple determines the least-significant bit of a character, and with format B it determines the most-significant bit of a character.

If the length of the input string is not exactly divisible by 8, the remainder is packed as if the input string were padded by null characters at the end. Similarly, during unpack()ing the "extra" bits are ignored.

If the input string of pack() is longer than needed, extra characters are ignored. A * for the repeat count of pack() means to use all the characters of the input field. On unpack()ing the bits are converted to a string of "0" s and "1" s.

Each character of the input field of pack() generates 4 bits of the result. For non-alphabetical characters the result is based on the 4 least-significant bits of the input character, i.e., on ord($char)%16. In particular, characters "0" and "1" generate nybbles 0 and 1, as do bytes "\0" and "\1" . For characters "a".."f" and "A".."F" the result is compatible with the usual hexadecimal digits, so that "a" and "A" both generate the nybble 0xa==10 . The result for characters "g".."z" and "G".."Z" is not well-defined.

Starting from the beginning of the input string of pack(), each pair of characters is converted to 1 character of output. With format h the first character of the pair determines the least-significant nybble of the output character, and with format H it determines the most-significant nybble.

If the length of the input string is not even, it behaves as if padded by a null character at the end. Similarly, during unpack()ing the "extra" nybbles are ignored.

If the input string of pack() is longer than needed, extra characters are ignored. A * for the repeat count of pack() means to use all the characters of the input field. On unpack()ing the nybbles are converted to a string of hexadecimal digits.

If your system has a strange pointer size (i.e. a pointer is neither as big as an int nor as big as a long), it may not be possible to pack or unpack pointers in big- or little-endian byte order. Attempting to do so will result in a fatal error.

For pack you write length-item/sequence-item and the length-item describes how the length value is packed. The ones likely to be of most use are integer-packing ones like n (for Java strings), w (for ASN.1 or SNMP) and N (for Sun XDR).

For pack, the sequence-item may have a repeat count, in which case the minimum of that and the number of available items is used as argument for the length-item. If it has no repeat count or uses a '*', the number of available items is used.

For unpack an internal stack of integer arguments unpacked so far is used. You write /sequence-item and the repeat count is obtained by popping off the last element from the stack. The sequence-item must not have a repeat count.

If the sequence-item refers to a string type ("A" , "a" or "Z" ), the length-item is a string length, not a number of strings. If there is an explicit repeat count for pack, the packed string will be adjusted to that given length.

    unpack 'W/a', "\04Gurusamy";            gives ('Guru')
    unpack 'a3/A A*', '007 Bond  J ';       gives (' Bond', 'J')
    unpack 'a3 x2 /A A*', '007: Bond, J.';  gives ('Bond, J', '.')
    pack 'n/a* w/a','hello,','world';       gives "\000\006hello,\005world"
    pack 'a/W2', ord('a') .. ord('z');      gives '2ab'

The length-item is not returned explicitly from unpack.

Adding a count to the length-item letter is unlikely to do anything useful, unless that letter is A , a or Z . Packing with a length-item of a or Z may introduce "\000" characters, which Perl does not regard as legal in numeric strings.

        print length(pack("s")), " ", length(pack("s!")), "\n";
        print length(pack("l")), " ", length(pack("l!")), "\n";

i! and I! also work but only because of completeness; they are identical to i and I .

The actual sizes (in bytes) of native shorts, ints, longs, and long longs on the platform where Perl was built are also available via Config:

       use Config;
       print $Config{shortsize},    "\n";
       print $Config{intsize},      "\n";
       print $Config{longsize},     "\n";
       print $Config{longlongsize}, "\n";

(The $Config{longlongsize} will be undefined if your system does not support long longs.)

        0x12 0x34 0x56 0x78     # big-endian
        0x78 0x56 0x34 0x12     # little-endian

Basically, the Intel and VAX CPUs are little-endian, while everybody else, for example Motorola m68k/88k, PPC, Sparc, HP PA, Power, and Cray are big-endian. Alpha and MIPS can be either: Digital/Compaq used/uses them in little-endian mode; SGI/Cray uses them in big-endian mode.

The names `big-endian' and `little-endian' are comic references to the classic "Gulliver's Travels" (via the paper "On Holy Wars and a Plea for Peace" by Danny Cohen, USC/ISI IEN 137, April 1, 1980) and the egg-eating habits of the Lilliputians.

Some systems may have even weirder byte orders such as

        0x56 0x78 0x12 0x34
        0x34 0x12 0x78 0x56

You can see your system's preference with

        print join(" ", map { sprintf "%#02x", $_ }
                            unpack("W*",pack("L",0x12345678))), "\n";

The byteorder on the platform where Perl was built is also available via Perldoc:Config:

        use Config;
        print $Config{byteorder}, "\n";

Byteorders '1234' and '12345678' are little-endian, '4321' and '87654321' are big-endian.

If you want portable packed integers you can either use the formats n , N , v , and V , or you can use the > and < modifiers. These modifiers are only available as of perl 5.9.2. See also perlport.

Exchanging signed integers between different platforms only works if all platforms store them in the same format. Most platforms store signed integers in two's complement, so usually this is not an issue.

The > or < modifiers can only be used on floating point formats on big- or little-endian machines. Otherwise, attempting to do so will result in a fatal error.

Forcing big- or little-endian byte-order on floating point values for data exchange can only work if all platforms are using the same binary representation (e.g. IEEE floating point format). Even if all platforms are using IEEE, there may be subtle differences. Being able to use > or < on floating point values can be very useful, but also very dangerous if you don't know exactly what you're doing. It is definitely not a general way to portably store floating point values.

When using > or < on an () -group, this will affect all types inside the group that accept the byte-order modifiers, including all subgroups. It will silently be ignored for all other types. You are not allowed to override the byte-order within a group that already has a byte-order modifier suffix.

If you know exactly what you're doing, you can use the > or < modifiers to force big- or little-endian byte-order on floating point values.

Note that Perl uses doubles (or long doubles, if configured) internally for all numeric calculation, and converting from double into float and thence back to double again will lose precision (i.e., unpack("f", pack("f", $foo)) will not in general equal $foo).

    pack( '@1A((@2A)@3A)', 'a', 'b', 'c' )
is the string "\0a\0\0bc".

For alignment commands count of 0 is equivalent to count of 1; both result in no-ops.

예:

    $foo = pack("WWWW",65,66,67,68);
    # foo eq "ABCD"
    $foo = pack("W4",65,66,67,68);
    # same thing
    $foo = pack("W4",0x24b6,0x24b7,0x24b8,0x24b9);
    # same thing with Unicode circled letters.
    $foo = pack("U4",0x24b6,0x24b7,0x24b8,0x24b9);
    # same thing with Unicode circled letters. You don't get the UTF-8
    # bytes because the U at the start of the format caused a switch to
    # U0-mode, so the UTF-8 bytes get joined into characters
    $foo = pack("C0U4",0x24b6,0x24b7,0x24b8,0x24b9);
    # foo eq "\xe2\x92\xb6\xe2\x92\xb7\xe2\x92\xb8\xe2\x92\xb9"
    # This is the UTF-8 encoding of the string in the previous example

    $foo = pack("ccxxcc",65,66,67,68);
    # foo eq "AB\0\0CD"

    # note: the above examples featuring "W" and "c" are true
    # only on ASCII and ASCII-derived systems such as ISO Latin 1
    # and UTF-8.  In EBCDIC the first example would be
    # $foo = pack("WWWW",193,194,195,196);

    $foo = pack("s2",1,2);
    # "\1\0\2\0" on little-endian
    # "\0\1\0\2" on big-endian

    $foo = pack("a4","abcd","x","y","z");
    # "abcd"

    $foo = pack("aaaa","abcd","x","y","z");
    # "axyz"

    $foo = pack("a14","abcdefg");
    # "abcdefg\0\0\0\0\0\0\0"

    $foo = pack("i9pl", gmtime);
    # a real struct tm (on my system anyway)

    $utmp_template = "Z8 Z8 Z16 L";
    $utmp = pack($utmp_template, @utmp1);
    # a struct utmp (BSDish)

    @utmp2 = unpack($utmp_template, $utmp);
    # "@utmp1" eq "@utmp2"

    sub bintodec {
        unpack("N", pack("B32", substr("0" x 32 . shift, -32)));
    }

    $foo = pack('sx2l', 12, 34);
    # short 12, two zero bytes padding, long 34
    $bar = pack('s@4l', 12, 34);
    # short 12, zero fill to position 4, long 34
    # $foo eq $bar
    $baz = pack('s.l', 12, 4, 34);
    # short 12, zero fill to position 4, long 34

    $foo = pack('nN', 42, 4711);
    # pack big-endian 16- and 32-bit unsigned integers
    $foo = pack('S>L>', 42, 4711);
    # exactly the same
    $foo = pack('s<l<', -42, 4711);
    # pack little-endian 16- and 32-bit signed integers
    $foo = pack('(sl)<', -42, 4711);
    # exactly the same

unpack()에서도 일반적으로 동일한 템플릿이 사용된다.

4. 관련 문서

5. 기타, comments

각주 13, "How to Eat an Egg on a Net"의 번역에 대해서인데요, "엔디언의 종류" 또는 "바이트를 정렬하는 두 가지 방법"이라고 하는 것이 무난할 것 같습니다.

big/little endian 이라는 표현은 걸리버 여행기에서 소인국 사람들이 달걀을 깨뜨리는 방법을 논쟁하는 장면에서 유래한 것이거든요. 자세한 내용은 아래 페이지에서 확인하실 수 있습니다.

http://en.wikipedia.org/wiki/Endianness
http://www.ietf.org/rfc/ien/ien137.txt

-- ai 2013-7-3 11:04 am

아 그러네요, 유래가 거기서 왔다는 것도 예전에 들었었는데 전혀 떠올리지 못하고 이걸 무슨 희한한 숙어인가라고만 생각을... 감사합니다.
-- Raymundo 2013-7-3 2:26 pm
이름:  
Homepage:
내용:
 


컴퓨터분류

각주:
1. low level, 수준이 낮다는 뜻이 아닌 건 아시겠죠? ^^
2. 주소값이 더 큰
3. nybble - 바이트의 절반
4. 예문에 있는 템플릿 문자열을 보면 항목들 사이에 공백이 있는데, 이것은 가독성을 위한 것일 뿐 실제 pack의 동작에는 영향을 미치지 않는다.
5. 원문은 "something about eating dog food, I don't think I heard it right!"인데 해석이 안 되어서;;;
6. "0100" 대신 "4566", "BCDD" 를 써도 결과가 같다.
7. 원문 코드 중반 이후에 DumpScalar라고 적혀 있는 부분들은 DumpString의 오타라서 여기 번역본에는 수정했음
8. 원본에는 템플릿 부분에 "...A7A*"로 적혀 있는데, 이후의 내용을 고려할 때 '...A7xA*"가 맞음
9. pack에서와 unpack에서 의미가 달라지는 건가?;;;
10. %12.2f로 12자리를 맞추고 있는데, 11자리여야 맞는 듯
11. 초과된 값이라기보다는 상위비트가 버려짐으로 인해 새로운 최상위 비트가 된 부분을 의미하는 게 아닐런지
12. "call XS extensions" - 잘은 모르겠지만 "XS 라는 모듈? 라이브러리? 에서 제공하는 함수를 호출"한다는 의미인 듯?
13. 걸리버 여행기에서 두 소인국이 달걀을 넓은 쪽에서 깰지 뾰족한 쪽에서 깰지에 대한 대립으로 전쟁을 하고 있었고 빅엔디안과 리틀엔디안이라는 말도 여기서 나왔음. thanks to ai님
14. perldoc -f pack에 의하면, 시스템이 long double을 지원하면서 Perl도 역시 그것을 지원하게 컴파일된 경우에만 된다고 함
15. 아래 예제에서는, 오직 "123"만 출력된다. 즉 합을 구하는데 사용된 데이타는 unpack에 의해 반환되지 않는다.
16. 128진수의 각 자리 사이에 콜론을 넣어 구분한다고 치면, 1(10) = 1(128), 128(10) = 1:0(128)인데 첫 바이트에 8번 비트를 세팅하니까 "1"대신 "0x81"이 됨. 129(10) = 1:1(128), 128*128+127(10) = 1:0:128(128)이고 처음 두 바이트는 8번 비트가 세팅되니까 각각 0x81,0x80이 되었다.
17. 이 예문에서, @str의 원소의 갯수가 3개라면 결과적으로 템플릿은 "(A)3"이 되며, 이는 "AAA"와 같다. 만일 괄호를 쓰지 않았다면 "A3"이 되고, 이것은 @str의 첫번째 원소의 처음 세 글자를 가져오게 된다.
18. 예제에는 %Env로 되어 있는데, %ENV로 수정하면 펄 프로그램이 수행될 때의 환경 변수들을 가지고 바로 확인해 볼 수 있다. 그러나 환경 변수의 값 중에 널 바이트가 포함된 값이 있을 경우 unpack이 제대로 동작하지 않는다.
19. 주제에 무관한 언급: $memory 변수에 미리 충분한 길이의 스트링을 할당하여 메모리를 확보해 두지 않으면 세그멘테이션 폴트가 나니 주의.
20. 즉 syscall의 인자로 $ptr이 아니라 $memory를 넣으면 된다는 얘기.
21. 널로 끝나는 곳까지이니까
22. 즉 포인터값을 얻어간 것에 대해
23. 외울 때 쉬우라고...
24. 위의 "1.4 Perl의 pack 함수" 참조
25. 무슨 뜻인지 모르겠음
찾아보기:
*   187Endian   33
  big endian   1, 3, 18, 61, 75, 82, 85, 156, 158, 183
  little endian   2, 4, 21, 32, 35, 62, 71, 87, 128, 160, 162, 185
Floating Point Number   88Integer   34, 60
Perl version   
  5.10   136
  5.6   48, 114
  5.8   47, 49, 104
  5.9.2   83
repeat count   30, 107, 135, 186string   22, 36, 96, 101, 111Template   5
  !   123, 181
  %   97
  >   84, 182
  <   86, 184
  (   105, 115, 119, 180
  )   106, 116, 120
  *   31, 51, 54, 59, 117, 131
  .   179
  /   112, 113, 118
  @   110, 124, 178
  [   126
  ]   127
  A   24, 38, 42, 52, 56, 57, 138
  a   23, 37, 40, 137
  B   26, 44, 93, 99, 141
  b   25, 43, 94, 98, 140
  C   7, 73, 145
  c   6, 144
  D   91, 168
  d   90, 166
  F   92, 167
  f   89, 165
  H   28, 46, 50, 143
  h   27, 45, 142
  I   15, 70, 154
  i   14, 69, 153
  J   164
  j   163
  L   11, 66, 150
  l   10, 65, 122, 149
  N   17, 77, 79, 157
  n   16, 76, 78, 155
  P   129, 130, 133, 170
  p   132, 134, 169
  Q   13, 68, 152
  q   12, 67, 151
  S   9, 148
  s   8, 63, 64, 147
  U   102, 173
  u   95, 171
  V   20, 81, 161
  v   19, 72, 80, 159
  W   146
  w   103, 175
  X   74, 109, 177
  x   53, 55, 58, 108, 121, 176
  x!N   125
  Z   29, 39, 41, 139
Unicode   100, 174uuencode   172  

마지막 편집일: 2017-1-9 11:21 pm (변경사항 [d])
7807 hits | Permalink | 변경내역 보기 [h] | 페이지 소스 보기