Bit Field와 Endian (Byte Ordering)

(또 다른 Back to the Basic 포스트)
맨처음 Endian에 대해서 배웠을 때였다.
아마 교수가 이렇게 설명했던 것으로 기억한다. Big Endian은 Left-Most-Bit가 Most Significant Bit (MSB)이고 Right-Most-Bit는 Least Significant Bit (LSB)라고. 그리고 Little Endian은 그 반대.

그리고선 학생들에게 물어봤다. 이 숫자를 Big Endian으로 표현하면? Little Endian으로 표현하면? 근데 모두들 어려워했다. 나도 무척 헷갈렸다.
어디 볼까? 왜 헷갈렸는지.

0x0B를 Big Endian으로 보자. Big Endian은 사람이 생각하는 것과 같은 방식이다.
이것은 (0000 1011)이 되겠다. (우측에 2라고 subscript를 써 주여야 하지만, WordPress에서 어떻게 하는지 모르니 이진수는 괄호로 표현하겠다.)
그럼 Little Endian으로는?
(1101 0000)이 되겠다.
음.. 원래 설명대로 말이다. 즉 Little Endian으로 LMB는 LSB이고 RMB는 MSB이다.
근데 이건 틀렸다. 그럼 뭐가 맞겠는가?
(0000 1011)이다. Big Endian과 다를 바 없다.

그럼 이건 어떨까?
0x0BA0
2Bytes다.

Big Endian : (0000 1011 1010 0000 )
Little Endian : (0000 0101 1101 0000)

Big Endian으론 맞는 표현인데, Little Endian으로는 틀리다.
그럼 뭐가 옳은 Littel Endian 표현일까?

Correct Little Endian : (1010 0000 0000 1011)
이것을 memory debugging을 하면 A0 0B로 보인다.

확인해 보자.

그럼 왜 그 설명이 애초에 잘못되었나를 살펴 보자.
이 Endian 문제를 Byte Ordering문제라고 한다. 즉 Byte 단위로 어떻게 배치시킬 것인가 하는 문제다. 즉 Big Endian은 Most Significant Byte를 (Bit가 아니라)가 앞에 오는 방식이다. 즉 Byte단위로 배치시키는 것이지 bit단위로 배치시키는게 아니라는 것이다.

즉 바이트 당, 그 내부는 그대로 유지하고, 바이트 순서가 바뀐다.
즉 첫번째 예에서, 0x0B는 그 자체가 1 Byte이기때문에, Little Endian이나 Big Endian이나 똑같다. 하지만 두번째 예에서는 2 Byte이기 때문에, Byte들의 순서가 바뀐다. 단 그 한 Byte내에서의 bit들 순서는 바뀌지 않는다.
Byte Ordering인 것이다.

자 그럼 여기서 도대체 “Endian”의 뜻은 무엇일까? 미국애들도 모른다. 그냥 Big Endian, Little Endian하고 외우는 것이다. 프로그래머들이 이 Endian에 대해서 헷갈려하고 자꾸 까먹는다. 심지어 전산과 출신이 아닌 프로그래머들은 (미국엔 많다) 아예 이 문제를 모르고 있는 애들도 많고, 전산과 출신이어도 Big Endian에 대해서 전혀 모르는 사람들도 많다.왜냐하면 Intel에서 만든 프로세서들만 써봤기 때문이다.

이 세상엔 많은 프로세서가 있다. 오직 Intel에서 만든 프로세서들만 Little Endian을 쓴다. 심지어 ARM core를 fab해도 Intel이 한 것은 Little Endian을 쓴다. Intel이 fab한 ARM core 기반 프로세서들이 xScale이다. 이전에 Mac에서 쓰던 PowerPC는 특이한 프로세서인데, 전원을 넣고서 세팅을 하기에 따라서 Big Endian으로 돌기도 하고 Little Endian으로 돌기도 한다. MS에서 PowerPC용으로 만든 Windows NT는 PowerPC에서 little endian으로 돌고, PowerMac에서 돌아가는 Mac OS는 Big Endian으로 돌았다.

그런데, 왜 Intel은 Little Endian을 쓰게 되었고, 나머지들은 Big Endian을 쓰게 되었을까? Windows/DOS 기반에서 프로그래밍하는 사람들이 Big Endian에 노출되는 경우는 Network 프로그래밍을 할때 뿐이다. 네트워크를 통해서 뿌려지는 데이터 패킷의 헤더와 데이터들은 Big Endian을 표준으로 한다. 물론 Windows에서 Windows로 통신을 할때는 header만 Big Endian으로 하고, 내부 데이터는 그대로 해도 별 문제가 없겠지만, multi platform으로 할때는 반드시 Big Endian으로 해야만 한다. 그래야 다른 플랫폼에서도 해석하는데 문제가 없고, 네트워크를 통해서 날아 다니는 패킷은 무조건 Big Endian이라고 가정할 수가 있기 때문이다.

자, x86 assembly language를 배울때, memory map은 다음과 같이 그린다.

How memory map is drawn for little endian system

우선 address 0은 최 우측 하단에 있고, 좌로 갈 수록 address는 증가한다. 그리고 그 다음 줄은 최우측 바로 윗줄에서 시작한다. 그리고 각 Byte 칸에 넣어지는 숫자는 사람이 읽는 방향으로 좌에서 우로 쓴다.
그러면 2 Byte 이상의 데이터를 쓴다고 하자. 그럼 최우측 어드레스 쪽, 즉 낮은 어드레스 쪽에 쓰여지는 것이 그 데이터의 마지막 바이트다.
즉 최초에 2 Bytes짜리 데이터를 쓰면 그 첫 Byte는 0x01이라는 주소에, 그리고 두번째 Byte는 0x00이라는 주소에 쓰여지는 것이다. 즉 그 숫자의, 혹은 데이터의 끝(End)가 낮은 혹은 숫자 상으로 작은(Little) 것이다. 즉 여기서 Little Endian이란 말이 만들어지는 것이다.
하지만 그 두 Byte짜리 데이터 자체의 시작 주소는 항상 낮은 어드레스부터 한다. 즉 이 예에서 쓰여진 2Bytes의 데이터는 0x00을 그 주소로 한다.

자, 그럼 비교를 위해 Big Endian의 memory map을 보자.

How memory map is drawn for big endian system

Big Endian 시스템은 메모리 맵을 좌상단에서 시작해서 우측으로 가면서 증가하고, 그 다음 줄은 두번째 줄 최 좌측에서 시작한다. 역시 그 순서로 메모리 어드레스가 증가한다.
그리고 데이터를 쓸 때는, 역시 사람이 읽는 방식대로 좌에서 우로 쓴다.
그러면 데이터 값이 끝나는 부분(End), 역시 최우측 부분이 되겠는데, 그게 높은 메모리 쪽(Big)이 된다. 그래서 Big Endian이라고 부르는 것이다.

난 미국 사람들이라면 당연히 이걸 알 줄알았다. 우리야 영어가 외국어니까 이해보다는 외우는 방식으로 간거지만.. 근데 의외로 미국애들이 이걸 몰랐다. 즉 어떤 지식을 습득할때에는 단순 지식보다도 역사를 아는게 중요한게 이런데서 나온다.

자 그럼 지금까지, 메모리 맵을 그리는 방식의 차이가 어떻게 Byte Ordering의 차이를 부르는지 알아보았다. 아직까지 왜 Intel 사람들은 좌측하단에서 시작하는 식으로 그렸는지 모르겠다.

Little Endian으로 시스템을 만들면 여러가지 귀찮은 점이 많다. 이를테면 쓰기는 Character Array로 쓰고서, 그것을 해석할때는 2 bytes, 4 bytes 이런 식으로 떼어서 해석할때, 일일히 그 ordering을 바꿔 주어야한다.
Big Endian에서는 어떤 식으로 쓰고, 어떤 식으로 해석하건 그냥 좌에서 우로 해석하면 된다. MPEG codec을 만들다보면, 이런 연산을 많이 하게 된다. 일전에 H.264의 CABAC 코딩을 위해서 bit stream manipulator를 만든 적이 있는데, 이 문제때문에 무척 귀찮았었다.

어쨌거나 Byte Ordering은 Byte 단위로 발생한다는 것을 알아보았다.

그런데, 원래 이 글을 올린 이유는 이제부터가 시작이다.
자, 다음의 소스를 보자.

Source Code 1: BitFieldsUnion.h

typedef union {
	struct {
		unsigned char videoType:4;
		unsigned char is16x9:1;
		unsigned char _reserved:3;
	} m_field;

	unsigned char m_rawByte;
} BitFieldsUnion;

Source Code 2: main.cpp

#include <iostream>
#include "BitFieldsUnion.h"

using namespace std;

int main (int argc, char * const argv[]) {

	BitFieldsUnion myInfo;
	
	myInfo.m_field.videoType = 0x0E;
	myInfo.m_field.is16x9 = 0x01;
	myInfo.m_field._reserved = 0x03;
	
	cout << "------------------\n";
	cout << "video type : " << hex << myInfo.m_field.videoType << endl;
	cout << "is 16x9 : " << hex << myInfo.m_field.is16x9 << endl;
	cout << "_reserved : " << hex << myInfo.m_field._reserved << endl;

	unsigned char rawData = 0xEB; // 0x0E, (1011)
	myInfo.m_rawByte = rawData;
	
	cout << "------------------\n";
	cout << "video type : " << hex << myInfo.m_field.videoType << endl;
	cout << "is 16x9 : " << hex << myInfo.m_field.is16x9 << endl;
	cout << "_reserved : " << hex << myInfo.m_field._reserved << endl;	
    return 0;
}

자, BitFieldsUnion은 의도적으로 각 비트 필드의 총 합이 8bit가 되도록 하였다.
즉 1 Byte내에서 데이터를 저장하게 되는 것이다.
그러면 저 각각의 값, 0x0E, 0x01, 0x03은 어떻게 메모리에 쓰여질까?
Big Endian은 뭐가 어떻게 되었건 좌에서 우측으로 메모리 증가하는 방향으로 쓰여질테니, 사람이 보고 읽는 방식으로 그냥 그 순으로 될 것이다. (왜 Big Endian이 좋은지 알겠지요들?)
하지만 Little Endian은 어떨까? Byte 내에서는 그냥 사람 읽는 순서를 유지한다고 했으니, 0x0E, 0x01, 0x03의 순서로 쓰여질까?
그건 그렇다. 아무래도 순서대로 쓰는 만큼, videoType쓰고, is16x9쓰고 _reserved를 쓴다. 하지만 그 전체를 한 Byte로 보고 좌에서 우로 쓰는게 아니라, 각각을 좌에서 우로 쓴다.
그러므로 (0111 1110)이라고 쓰게 된다. 좌에서 우로 보면 0x03, 0x01, 0x0E인거다.
즉 이렇게 되면 0x7E가 된다.
그림을 보고 확인해 보자.

즉, 소스코드를 사람이 읽을때, 나온 순으로 생각하기가 쉬운데, little endian 시스템에선 그렇게 하면 안된다는 말이다. 시스템이 어떻게 저장하는지를 알고서 사용해야 한다.
즉 그냥 소스코드의 구조 순서에 맞추어서 값을 정해주면 0x0E, 0x01, 0x03이 되어 0xEB가 된다. 0x7E가 되어야 하는데 말이지…

특히 이 예는 문제가 된다. 즉 1Byte로 값을 정해주고, field에 따라서 해석을 할 수가 있도록 만들어진 구조체인데, 조심하지 않으면 전혀 엉뚱한 값을 가지고 계산을 할 수가 있게 되는 것이다.

이런 저런 이유로 나는 Big Endian을 좋아한다. Little Endian 시스템에서 작업을 하다보면 짜증나는 경우가 많이 생긴다.
원래 ARM core는 Big Endian으로 만들어졌는데, 이것을 Intel에서 제작하는 xScale은 Little Endian으로 움직인다. 제발 Intel 사람들아.. 너희들도 Big Endian으로 좀 넘어갈래? 어차피 세상은 다 Big Endian으로 움직인다. 네트워킹을 하건 뭘하건, 데이터를 보낼때는 다 Big Endian 아니니. 왜 너희들만 특별나게 구냐, 응?

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: