엔디언

오른쪽 오른쪽 **엔디언**(endianness)은 컴퓨터의 메모리와 같은 1차원의 공간에 여러 개의 연속된 대상을 배열하는 방법을 뜻하며, 바이트를 배열하는 방법을 특히 **바이트 순서**(byte order)라 한다. 엔디언은 보통 큰 단위가 앞에 나오는 **빅 엔디언**(big-endian)과 작은 단위가 앞에 나오는 **리틀 엔디언**(little-endian)으로 나눌 수 있으며, 두 경우에 속하지 않거나 둘을 모두 지원하는 것을 **미들 엔디언**(middle-endian)이라 부르기도 한다.
# 유래
엔디언이라는 단어는 조너선 스위프트의 《걸리버 여행기》에 나오는 소인국 릴리퍼트 이야기에서 달걀을 깰 때 뭉툭한 끝(big-end)을 먼저 깨는 사람들(빅 엔디언)과 뾰족한 끝(little-end)을 먼저 깨는 사람들(리틀 엔디언) 사이에 격론이 벌어진 데서 따온 것이다. 빅 엔디언과 리틀 엔디언 중 어느 것을 쓰느냐 하는 문제는 상황에 따라서 임의적이고, 따라서 종종 논란의 대상이 되었다. 컴퓨터와 관련되어 엔디언이라는 말은 대니 코언(Danny Cohen)이 이런 플레임을 잠재우기 위해 1980년에 쓴 *On Holy Wars and a Plea for Peace*라는 글에서 유래했다.
# 바이트 순서
바이트 순서는 크게 빅 엔디언과 리틀 엔디언으로 나눌 수 있다. 빅 엔디언은 사람이 숫자를 쓰는 방법과 같이 큰 단위의 바이트가 앞에 오는 방법이고, 리틀 엔디언은 반대로 작은 단위의 바이트가 앞에 오는 방법이다. PDP-11과 같은 몇몇 아키텍처는 2바이트 단위와 1바이트 단위로 서로 다른 순서를 사용하기도 하는데 이들을 미들 엔디언이라 부른다. 다음은 이런 방법들을 주어진 interger type 수를 char array로 표현해서 비교한 것이다. {| class="wikitable" |- | 빅 엔디언 || `char num_array[] = {0x12, 0x34};` || `char num_array[] = {0x12, 0x34, 0x56, 0x78}` |- | 리틀 엔디언 || ` char num_array[] = {0x34, 0x12}; ` || `char num_array[] = {0x78, 0x56, 0x34, 0x12}` |- | 미들 엔디언 || - || ` char num_array[] = {0x34, 0x12, 0x78, 0x56}`
또는
`char num_array[] = {0x56, 0x78, 0x12, 0x34}` |} 두 방법 중 어느 한 쪽이 다른 쪽과 비교해 압도적으로 좋거나 나쁘지는 않다고 알려져 있으며, 두 방법은 서로 다른 여러 아키텍처에서 서로 공존하고 있다. 그러나 x86 아키텍처가 리틀 엔디언을 쓰기 때문에, 오늘날 x86 아키텍처를 사용하는 대부분의 데스크톱 컴퓨터는 리틀 엔디언을 쓰며 이를 ‘인텔 포맷’이라 한다. 거꾸로 네트워크에서는 주소를 빅 엔디언으로 쓰는데, 역사적으로 라우팅이 전화를 거는 식으로 접두 부호로 이루어졌기 때문이다. 이의 영향으로 많은 프로토콜과 몇몇 파일 포맷이 빅 엔디언을 사용하고 있다. 모토로라 프로세서들은 일반적으로 빅 엔디언을 사용하며, ARM 프로세서들은 성능 향상을 위해 빅 엔디언과 리틀 엔디언을 선택할 수 있도록 되어 있다.
## 장단점
빅 엔디언은 소프트웨어의 디버그를 편하게 해 주는 경향이 있다. 사람이 숫자를 읽고 쓰는 방법과 같기 때문에 디버깅 과정에서 메모리의 값을 보기 편한데, 예를 들어 0x59654148은 빅 엔디언으로 `59 65 41 48`로 표현된다. 반대로 리틀 엔디언은 메모리에 저장된 값의 하위 바이트들만 사용할 때 별도의 계산이 필요 없다는 장점이 있다. 예를 들어, 32비트 숫자인 0x2A는 리틀 엔디언으로 표현하면 `2A 00 00 00`이 되는데, 이 표현에서 앞의 두 바이트 또는 한 바이트만 떼어 내면 하위 16비트 또는 8비트를 바로 얻을 수 있다. 반면 32비트 빅 엔디언 환경에서는 하위 16비트나 8비트 값을 얻기 위해서는 변수 주소에 2바이트 또는 3바이트를 더해야 한다. 보통 변수의 첫 바이트를 그 변수의 주소로 삼기 때문에 이런 성질은 종종 프로그래밍을 편하게 하는 반면, 리틀 엔디언 환경의 프로그래머가 빅 엔디언 환경에서 종종 실수를 일으키는 한 이유이기도 하다. 또한 가산기가 덧셈을 하는 과정은 LSB로부터 시작하여 자리 올림을 계산해야 하므로, 첫 번째 바이트가 LSB인 리틀 엔디언에서는 가산기 설계가 조금 더 단순해진다. 빅 엔디언에서는 가산기가 덧셈을 할 때 마지막 바이트로부터 시작하여 첫 번째 바이트까지 역방향으로 진행해야 한다. 그러나 오늘날의 프로세서는 여러개의 바이트를 동시에 읽어들여 동시에 덧셈을 수행하는 구조를 갖고 있어 두 엔디언 사이에 사실상 차이가 없다.
## 바이 엔디언
몇몇 아키텍처는 빅 엔디언과 리틀 엔디언 중 하나를 선택할 수 있도록 설계되어 있고, 이를 **바이 엔디언**(Bi-endian)이라 부른다. ARM, PowerPC, DEC 알파, MIPS, PA-RISC, IA-64 등은 바이 엔디언을 사용하는 대표적인 아키텍처이다. 이들 대부분은 컴퓨터가 시작된 상태에서 소프트웨어적으로 바이트 순서를 바꿀 수 있지만, 몇몇은 하드웨어에 내장된 펌웨어에서 바이트 순서를 선택해야 하는 경우도 있다.
## 미들 엔디언
종종 한 방향으로 순서가 정해져 있는 게 아니라, 이를테면 32비트 정수가 2바이트 단위로는 빅 엔디언이고 그 안에서 1바이트 단위로는 리틀 엔디언인 경우가 종종 있는데 이를 **미들 엔디언**(Middle-endian)이라 한다. VAX와 ARM에서는 배정밀도 부동소수점 실수를 미들 엔디언으로 저장하며, PDP-11에서는 32비트 정수를 미들 엔디언으로 저장하는데 이를 **PDP 엔디언**이라 부르기도 한다.
## 비트 순서
보통 바이트나 옥텟은 원자적인 단위로 간주되지만 종종 비트 단위의 접근이 필요할 수 있다. 따라서 바이트 순서가 아니라 비트 순서를 따질 수도 있으나, 여러 가지 이유로 비트 순서는 상대적으로 덜 중요하다. 보통 메모리의 접근은 바이트 단위로 이루어지기 때문에 비트 단위의 접근에는 별도의 연산 과정이 필요한데, 이러한 연산 자체가 비트 순서에 대해 잘 정의되어 있기 때문에 비트를 접근하는 방법은 아키텍처에 중립적이다. 비트 순서와 비슷하게, 네트워크 프로토콜이나 파일 포맷 같이 저수준으로 내려 갈 경우 비트 단위의 데이터를 최상위 비트부터 채울 것인가 최하위 비트부터 채울 것인가 하는 문제가 있다. 예를 들어 C는 구조체에서 바이트보다 더 작은 단위의 변수를 선언할 수 있는 비트 필드를 지원하는데, 여러 개의 비트 필드가 배열되는 방법은 기계어를 생성하는 과정에서 중요해진다. 이때 최상위 비트(MSB)부터 채우는 것을 **최상위 비트 우선**, 최하위 비트(LSB)부터 채우는 것을 **최하위 비트 우선**이라 한다. 예를 들어 PNG와 GIF는 각각 최상위/최하위 비트 우선을 쓰는 대표적인 파일 포맷이다.