UNION은 흔히 공용체라고 부르기도 한다. 구조체와 생김새는 비슷하지만 한 가지 다른 점은 모든 멤머가 같은 메모리 공간을 사용한다는 것이다. 즉, UNION에 세 개의 필드가있다고 할 때, 이 세 가지 중 한가지만 사용할 수 있다는 것이다. 따라서 UNION의 크기는 세 필드중 사이즈가 가장 큰 필드의 크기와 같다. 다음과 같이 정의하면 된다.
유니온이름 UNION 필드들... 유니온이름 ENDS |
또한 구조체의 필드중 몇몇의 필드만 묶어서 UNION으로 정의할 수 있다.
구조체이름 STRUCT 구조체필드들... UNION 유니온필드이름 유니온필드들... ENDS 구조체이름 ENDS |
약간 복잡해 보이지만 사실은 '유니온필드들'만 유니온으로 감쌌을 뿐이다. 유니온필드에 접근할 때는 '구조체이름.유니온필드이름.유니온필드' 로 접근하면 된다. 지금까지 언급한 특징만 다를 뿐이지, 사용하는 방법에 있어서 유니온과 구조체는 차이점이 없다. 위와같이 하는 것이 헷갈린다면 먼저 유니온을 정의한 후, 구조체 필드에 유니온 변수를 선언하면 된다. 그리고 하는 일은 두 가지 방법이 완전히 같다.
몇몇 독자는 UNION을 과연 어디에 쓰게 될지 궁금해 하실지 모르겠다. 작성하는 프로그램이 메모리를 매우 적게 차지해야하는 경우, 구조체의 멤버 중 동시에 쓰이지 않는 멤버를 유니온으로 묶어서 사용하면 메모리를 절약할 수 있다. 이와는 달리 유니온만의 특성을 이용한 예가 있다. 바로 IP주소에 대한 것이다. 소켓 프로그래밍을 해보신 분은 알겠지만 IPv4에서 IP주소는 4바이트이다. 우리가 눈으로 볼 때는 이 4바이트의 IP주소를 1바이트씩 잘라 읽어야 편리하다는 것을 잘 알고 있을 것이다. (우리가 잘 알고 있는 203.253.21.151의 형태) 이러한 경우 UNION을 유용하게 써먹을 수 있다.
IP UNION inDword DWORD 0 inByte BYTE 0 0 0 0 IP ENDS |
매크로란, 어셈블리 구문들을 하나로 묶어놓은 것이다. 겉으로 보기엔 프로시져와 매우 유사한 형태를 갖고 있다. 하지만 C에서의 매크로 함수와 같아서, 스택에 수많은 push와 pop을 해가며 함수를 호출하는 것이 아니라, 단지 코드가 덧씌워질 뿐이다. 따라서 속도는 매우 빠른 장점을 지닌 반면, 조심해서 사용하지 않으면 코드가 덧씌워지기 때문에 프로그램의 크기가 매우 커질 것이다. 다음과 같은 매크로가 정의되어있다고 하자.
NewLine MACRO call Crlf ENDM |
이 매크로는 단지 화면에 개행문자(텍스트 에디터에서 엔터를 누른 것과 같은 효과)를 찍는 일만 수행한다. 매크로를 어떻게 정의하는지 대한 정확한 설명은 다음 단락에 이어질 것이다.
.code NewLine |
위와 같은 코드는 전처리기(어셈블러가 컴파일하기 전에 수행된다)에 의해 다음과 같이 바뀐다.
.code call Crlf |
매크로는 소스코드의 어느 부분에서도 정의될 수 있다. 하지만 매크로는 소스코드에서 정의되기 이전에 호출될 수는 없다.
매크로이름 MACRO 파라미터1, 파라미터2, ... 구문들 ENDM |
다음은 인자로 넘어온 문자를 화면에 찍어주는 일을 하는 매크로이다.
mPutchar MACRO char push eax mov al, char call WriteChar pop eax ENDM |
매크로를 호출하려면 다음과 같이 사용한다.
매크로이름 인자1, 인자2,... |
mPutchar 'A' |
push eax mov al, 'A' call WriteChar pop eax |
사실 LABEL디렉티브는 구조체나 매크로와는 그다지 관련이 있지 않다. 하지만 이 디렉티브를 언급하는 이유는 나중에 나올 링크드 리스트 예제에 이것이 나오기 때문이다. 크게 어려운 내용이 아니니 부담없이 읽어주기 바란다.
4회에서 나왔던 내용이지만, 다시한번 변수에 대한 이야기를 해 보겠다. .data에 선언된 변수의 이름은 레이블이다. 우리가 흔히 '라벨'이라고 부르는 것을 떠올리면 레이블의 역할이 뭔지 알 수 있을 것이다. 우리가 선언한 데이터들은 데이터 세그먼트에 들어가게 되고 이 데이터 세그먼트의 시작부분으로부터 선언된 데이터가 존재하는 곳 까지의 거리(Offset)를 통해서 그 데이터에 접근하게 된다. 여기서 '거리'라고 하는 것은 숫자이기 때문에 사람이 기억하기가 어렵다. 따라서 각각의 '거리'에 대한 레이블을 하나씩 만들어놓은 것이 데이터 레이블인 것이다.
LABEL디렉티브는 데이터 세그먼트에 이미 존재하는 변수에 또 하나의 이름과 타입을 지정해주는 데 이용된다. 다음의 사용예를 보아라.
.data val16 LABEL WORD val32 DWORD 12345678h .code mov ax, val16 ; AX = 5678h mov dx, val16 + 2 ; DX = 1234h |
=============================================================================================
반복 블럭은 어셈블리어 구문들을 여러번 반복하게 해주는 '디렉티브'이다. 이들의 특징은 반복횟수가 상수에 의해 결정된다는 것이다. 이들은 우리가 흔히 아는 반복문과는 약간 다르다. 지금 말하는 반복 블럭은 일종의 매크로와 같은 역할을 하는 것임을 깊이 새기기 바란다.
반복 블럭에는 WHILE, REPEAT, FOR, FORC 등이 있는데, 대표적으로 숫자에 적용되어 가장 자주 쓰이는 REPEAT에 대해서 알아보자.
다른 반복 디렉티브들은 수식, 심볼, 문자에 대해 반복하는 반면, REPEAT 디렉티브는 상수수식의 값만큼 반복하기 때문에, 그만큼 쓰임새가 크다.
REPEAT 상수수식 구문들... ENDM |
.data array LABEL DWORD iVal = 10 REPEAT 100 DWORD iVal iVal = iVal + 10 ENDM |
array DWORD 10, 20, 30, 40, ... 1000 |
이 연산자는 조금은 특이한 연산자이다. 예제를 먼저 살펴보자.
list WORD 10, 20, 30, 40 ListSize = ($ - list) / 2 ; ListSIze는 4 |
본회에서 배운 내용을 복습하는 의미에서 링크드리스트를 사용하는 간단한 프로그램을 작성해 보겠다.
링크드 리스트(Linked List)는 연결리스트라고도 하며, 각각의 노드를 포인터를 이용하여 연결함을로써 구현된다. 그림 2와 같은 구조를 떠올리면 이해하기 쉬울 것이다.(사실 여기까지 강좌를 읽으며 이해하고 계신 분이라면 충분히 알고 있을 것이다.) 그림 2는 단방향의 링크드리스트를 나타낸다.
이제 구조체를 정의해 보자. 구조체의 정의는 너무나도 간단하다.
ListNode STRUCT NodeData DWORD ? ; 노드의 데이터값을 갖는다. NodePtr DWORD ? ; 다음 노드의 주소를 갖는다. ListNode ENDS |
TotalNodeCount = 15 NULL = 0 Counter = 0 .data LinkedList LABEL DWORD ; (1) REPEAT TotalNodeCount ; (2) Counter = Counter + 1 ; (3) ListNode <Counter, ($ + (Counter * SIZEOF ListNode))> ENDM ListNode <0, NULL> ; (4) 꼬리노드 |
(1) 먼저 LABEL디렉티브가 나왔는데, LABEL디렉티브 뒤에 나온 DWORD는 형식적인 것이다. 왜냐하면 나중에 사용할 때는 어차피 ListNode인 것처럼 사용하기 때문이다. LinkedList라는 데이터 레이블은 링크드리스트 첫 번째 노드에 대한 데이터 레이블로 쓰이게 된다.
(2) REPEAT디렉티브는 첫 번째 인자의 상수값만큼(여기서는 15) 루프를 돌리게 된다.
(3) 따라서 Count가 1씩 증가되면서 15개의 ListNode가 선언된다. 위 코드에서는 선언과 동시에 초기화를 시켜주고 있는데, 두 번째 필드에서는 ($ + (Counter * SIZEOF ListNode))을 이용하여 다음 노드의 오프셋을 얻어온다. $값은 LinkedList레이블이 가리키는 오프셋을 갖고, REPEAT블럭 내에서 변하지 않는다. 왜냐하면 루프가 돌기 이전에 $연산자가 계산되기 때문이다.
(3) 결과적으로, 꼬리노드를 포함한 16개의 노드가 모두 연결된다.
이제 본격적으로 main프로시져를 정의해 보겠다. 다음의 코드는 데이터 영역에서 만든 링크드 리스트를 탐색하며 각각의 NodeData를 찍고난 후 종료한다.
.code main PROC ; (1) mov esi, OFFSET LinkedList ; NodeData 필드의 모든 정수들을 보여준다. NextNode: ; (2)꼬리노드인지 체크해 본다. mov eax, (ListNode PTR [esi]) .NextPtr cmp eax, NULL je quit ; (3)NodeData를 출력한다. mov eax, (ListNode PTR [esi]) .NodeData call WriteDec call Crlf ; (4)포인터를 다음 노드로 옮긴다. mov esi, (ListNode PTR [esi]) .NextPtr jmp NextNode quit: exit main ENDP END main |
(2) 루프를 계속 돌 것인지를 결정하는 부분이다. esi가 가리키는 주소에 존재하는 ListNode형 구조체변수의 NextPtr이라는 필드를 읽어서 eax에 넣는다. 물론 eax에 넣는 이유는 NULL인지 판단하기 위해서다. NextPtr필드가 NULL이라는 것은 더이상 다음 노드가 없다는 뜻이기 때문이다. 만약 NULL이라면 quit레이블로 점프하여 프로그램을 종료하게 된다.(NULL은 데이터 영역에서 정의한 기호상수이며 0과 같다.)
(3) esi가 가리키는 주소에 존재하는 ListNode형 구조체변수의 NodeData필드의 값을 eax에 읽어들인 뒤, 해당 데이터를 출력하고 개행문자를 출력한다. 개행문자는 우리가 텍스트 에디터에서 엔터를 누를 때와 같은 효과를 내는 문자이다.
(4) 현재 esi가 가리키는 ListNode형 구조체변수의 NextPtr필드 값을 다시 esi에 복사해 넣는다. 따라서 esi는 다음 노드의 주소를 갖게 될 것이다. 그 다음 다시 NextNode레이블로 돌아가서 지금까지 해온 과정((2)~(4))을 반복한다.
이번 회에서는 구조체와 매크로에 대해 배워보았다. 적어도 한 가지의 고급언어를 프로그래밍해보신 여러분들은 어셈블리어를 보면서 자신이 다뤘던 프로그래밍 언어들이 어떻게해서 어셈블리어로 구현될지에 대해서도 머릿속에 조금씩 떠오르리라 믿는다. 다음 회에서는 32비트 윈도우즈 프로그래밍을 간단히 배워볼 것이다. 그러나 직접 어셈블리어로 윈도우 프로그래밍을 하는 것은 단지 창 하나 띄워보는 정도로 그치고, IA-32의 메모리 관리방법에 좀더 중점을 두고 집필할 예정이다. 이쪽을 배우는 것이 좀더 여러분에게 도움이 될 것 같아서이다. 다음 회에는 좀더 흥미로운 주제로 여러분에게 다가갈 생각이다. 그럼 이만 줄이겠다.
어느덧 본 강좌도 끝을 향해 가고 있다. 이제 우리는 최소한 어셈블리어 코드를 보고 어떤 일을 하는지 정도는 파악할 수 있지만, 아직 뭔가 허전함을 느낄지 모르겠다. 본 회에 배울 구조체와 매크로에 관련된 내용은 그러한 허전함을 채워주리라 믿어 의심치 않는다.
다른 언어도 마찬가지이지만, 나중에 버전업이 되어 새로운 것이 나오더라도 예전에 사용하던 형식과 개념 등은 그대로 묻어있게 마련이다. 어셈블리어도 마찬가지인데, 특히나 다른 프로그래밍언어에서 흔히 쓰이는 것이 어셈블리어에 약간 원초적(?)인 모습으로 존재하는 것을 볼 수 있다. 여기서 원초적이라는 말은 좀더 사용하기 불편하고 원시적으로 생겼다는 이야기이다. 필자는 어셈블리어의 프로시져나 배열, 구조체, 매크로 등을 처음 접할 때도 이러한 사실을 느끼고는 매우 흥미로워 했던 기억이 난다. 여러분도 기존에 사용하던 언어에 존재하던 클래스나 구조체, 매크로 등이 어셈블리어에 좀더 원초적(?)으로 존재하는 것을 배우며 희열을 느껴보길 바란다.
다른 프로그래밍 언어를 접해보신 분이라면 구조체가 무엇인지는 잘 알고 계실 것이다. 그래도 형식상 간단히 설명하자면 구조체는 개념적으로 연관있는 변수들의 그룹이자 틀이며 데이터형이다. 물론 이렇게 설명한다고 해서 이해하시는 분은 아무도 없을 것이다. 구조체를 하나 간단히 정의해 보겠다.
COORD STRUCT X WORD ? Y WORD ? COORD ENDS |
위 구조체의 이름은 COORD이며 이것이 곧 데이터형이 된다. 뒤에 STRUCT라고 적힌 것을 보면 구조체를 말하는 것이라 추리할 수 있다. 또 마지막에 ENDS가 보이는데, 추측해 보건데, END Struct의 약자일 것이다. 보다시피 구조체의 정의는 너무나 간단하다. 구조체에 포함된 하나하나의 변수는 필드(field)라 불리우며, 멤버변수라고도 부른다. 그리고 ?표시를 한 곳은 초기화를 하지 않겠다는 뜻이다.
구조체에 관련되어 배울 항목은 정의, 선언, 구조체변수 참조 이다. 하나하나씩 항목별로 배워보자.
구조체의 정의는 다음과 같이 한다.
구조체이름 STRUCT 필드선언들... 구조체이름 ENDS |
구조체를 정의할 때는 초기값을 같이 넣어줄 수 있다. 초기값을 적는 형식은 우리가 일반 변수를 선언할 때와 같다. 다음의 예를 보자.
Employee STRUCT IdNum BYTE "000000000" LastName BYTE 30 DUP(0) Years WORD 0 SalaryHistory DWORD 0, 0, 0, 0 Employee ENDS |
첫번째 필드 IdNum을 보자. 스트링을 초기값으로 정해 놓았다. 숫자'0' 9개와 널문자 0, 총 10바이트가 들어간다. 잘 이해가 가지 않으면 3회를 다시 참고하기 바란다. 두번째 필드 LastName은 바이트원소가 30개 들어가는 배열에 초기값으로 모두 0을 넣어 두었다.(비어있는 스트링과 같기 때문에 그림1에서는 (null)이라고 적어 놓았다) 세번째 필드 Years는 WORD형으로 0을 하나 갖는다. 마지막 필드는 SalaryHistory로써 DWORD형 원소 4개에 각각 초기값을 0으로 넣어두었다. 두말할 필요도 없이 초기화를 하고 싶지 않으면 ?를 사용하면 된다.
한가지 유의할 것은 위에 적어놓은 것은 구조체의 정의라는 것이다. 따라서 일종의 데이터 타입이 하나 생긴 것이고, 이를 사용하려면 구조체 변수를 선언해서 사용해야 한다.
좀 더 엄밀히 말하면 구조체에 대한 인스턴스의 선언 또는 구조체 변수의 선언이다. 두말할 필요도 없이 다음의 예제를 보면 쉽게 이해갈 것이라 믿는다. COORD는 본 회의 "구조체란?"단락에서 정의해 놓았던 그 구조체이다.
.data point1 COORD <5, 10> point2 COORD <> point3 COORD {} |
이렇게<와 >를 사용하여 초기화를 해준다. 만약 < 와 >안에 아무것도 적어주지 않는다면 각 필드에 구조체의 정의에서 적어놓았던 초기값이 적용된다. <와 > 대신 { 와 }을 사용해도 무방하다. 이제 다음 구문을 보고 무슨 뜻일지 한번 생각해 보자.
person3 Employee <, "Jones"> |
눈치가 빠른 분은 이미 눈치챘을 것이다. 두번째 필드만 프로그래머가 지정한 초기값으로 초기화 하고 나머지는 디폴트 초기값으로 적용시키게 된다. 마지막으로 , 구조체의 배열은 어떻게 사용하는지 고민해 보자.
AllPoints COORD 3 DUP (<0,0>) |
이제 COORD형 구조체 변수가 세 개 생겼고 구조체의 배열로 사용할 수 있게 되었다.
지금까지는 구조체 변수를 선언하는 방법이었고, 다음 단락에서는 직접 구조체 변수를 사용하는 법을 배워보자.
구조체 변수를 사용하는 것은 놀랍게도 C언어와 다를 바가 없다. '.'을 이용하여 멤버에 접근하기 때문이다.
.data worker Employee <> .code mov worker.SalaryHistory, 20000 ; 첫번째 봉급 mov worker.SalaryHistory + 4, 30000 ; 두번째 봉금 |
C와 비슷하게 쓴다고 해서 마지막 줄의 코드까지 헷갈리지 않길 바란다. + 4를 했는데도 두번째 봉급이 된 이유는 DWORD타입(4바이트를 차지)이기 때문이다. 또 배열이라고 해서 다른 언어처럼 특별한 접근방식이 존재하는 것이 아니라 단지 주소에 덧셈을 하여 다음 원소로 접근한다는 것도 잊지 않기 바란다. 이제 인다이렉트 오퍼랜드로 어떻게 접근하는지 알아보자. 인다이렉트 오퍼랜드는 4회에서 언급한 바 있다. 쉽게말하면 포인터로 접근하는 방법이다.
mov esi, OFFSET worker mov ax, (Employee PTR [esi]).Years |
[esi].Years로 사용하는 실수를 범하지 않길 바란다. 구조체는 어셈블리에서 매우 잘 정의되어 있으며 사용하는데 있어 크게 어려울 것이 없다는 것을 느꼈을 것이다.
'IT_Programming > Assembly' 카테고리의 다른 글
[펌] 인라인 어셈블리를 분석하자!! (0) | 2009.10.07 |
---|---|
편견이 깨지는 어셈블리 프로그래밍 (with VC++) (0) | 2007.07.03 |
[전광성의 어셈블리어 이해하기] 스트링과 배열 (0) | 2007.07.02 |
[전광성의 어셈블리어 이해하기] 고급 프로시져 (0) | 2007.07.02 |
[전광성의 어셈블리어 이해하기] 정수 산술연산(이진연산) (0) | 2007.07.02 |