[Memory] Little Endian, Big Endian

3 분 소요

회사에서 파일 구조를 설계하며 기존에 사용하던 파일의 구조를 분석하다, 엔디언이란 개념을 접하게 되어 공부한 내용을 기록한다.


상황

회사 코드이기 때문에 자세한 내용을 공개하기는 어렵지만, 소스 코드를 통해 파악한 내용은 대략 다음과 같다.

  • 특정 상황에서의 모드를 나타내는 열거형 타입 mode_e

    typedef enum {
    	ModeNone	= 0,
    	ModeNormal      = 1,
    	...             = 2,
      	...             = 4,
      	...             = 8,
      	...             = 10,
      	...             = 20,
    } mode_e;
    
  • 열거형 타입 mode_e를 사용하는 구조체 flag_t: 열거형 타입 mode_e에 정의된 정수 값이 unsigned short 타입의 mode 필드로 사용됨

    typedef struct {
    	union {
    		int64_t value;
    		struct {
      			
          		...
              
    			/** mode values defined as(see also mode_e)
    				ModeNone        = 0x0000,
    				ModeNormal      = 0x0001,
    				...             = 0x0002,
    				...             = 0x0004,
    				...             = 0x0008,
    				...             = 0x0010,
    				...             = 0x0020
    			 */
    			uint16_t mode;
            
          		...
    		} flag;
    	};
    } flag_t;
      
    

소스를 바탕으로 파일 구조체를 파악한 뒤, hexdump 프로그램을 이용해 실제 파일의 이진 데이터 값을 분석했다.

  • ModeNormal에 해당하는 값인 1이 flag_t.flag.mode에 쓰여 있어야 하는 파일이다
  • 그러나 flag_t.flag.mode가 있어야 할 위치에, 아무리 봐도 정의된 값이 없는 것이다!

hexdump-result

원래 저 위치에 0x0001이 있어야 했다


엔디언

한참을 혼자 삽질하다 팀장님께 도움을 청해 엔디언 때문임을 알게 되었다.

개념

위키피디아에 의하면, 엔디언(Endianness)이란, 컴퓨터 메모리 상에서 워드를 배열하는 방법을 의미한다. 조금 더 쉽게는 1바이트를 초과하는 연속된 데이터가 있을 때, 컴퓨터가 해당 데이터를 이루는 바이트를 저장하는 순서라고 말할 수 있다.

Endianness is the order or sequence of bytes of word of digital data in computer memory.

endian-word

https://sites.google.com/site/vonmanncomputer/primary-memory/bytes-bits-words
  • 워드: 컴퓨터 CPU가 한 번에 처리할 수 있는 데이터 단위
    • CPU 아키텍처에 따라 다르지만, 한 워드는 4바이트 혹은 8바이트로 이루어짐
    • 32비트 CPU의 경우 4바이트, 64비트 컴퓨터의 경우 8바이트
  • 워드의 배열: 워드를 이루는 각 바이트를 메모리 주소 상에 어떻게 배치할 것인지의 방법

필요성

컴퓨터 구조 상, 엔디언 개념이 필요할 수밖에 없다.

  • 메모리의 기본 데이터 처리 단위는 1바이트
  • CPU의 기본 데이터 처리 단위는 4바이트 혹은 8바이트
  • 메모리와 CPU 사이에 데이터 교환이 일어남
  • 따라서 CPU가 메모리에 저장된 데이터를 어떻게 읽어 오고, 메모리에 데이터를 어떻게 저장할지가 중요해짐
    • 1바이트의 데이터라면 문제가 없음
    • 1바이트를 초과하는 연속된 데이터의 경우, 큰 단위의 바이트를 높은 메모리 주소 공간부터 저장할지, 낮은 주소 공간부터 저장할지에 따라 CPU가 데이터를 다루는 순서가 달라지게 됨

종류

컴퓨터 CPU 아키텍처에 따라 빅 엔디언(Big Endian)과 리틀 엔디언(Little Endian)으로 구분하며, 둘을 모두 지원하는 미들 엔디언(Middle Endian) 방식도 있다.

endianness-type

Big Endian Little Endian
낮은 메모리 주소에 최상위 바이트부터 저장하는 방식 높은 메모리 주소에 최상위 바이트부터 저장하는 방식
1. the most significant byte of a word at the smallest memory address
2. the least significant byte of a word at the largest memory address
1. the most significnt byte of a word at the smallest memory address
2. the least significant byte of a word at the largest memory address

해당 방식별 구분은 바이트 데이터를 어떻게 나누어 저장할지에 따른 차이일 뿐이다. 어떤 것이 더 나은 방식인지는 단정할 수 없다.

  • Big Endian: 사람이 읽고 쓰는 방식과 비슷함
    • 사람이 읽고 디버깅하기 편리함
    • 데이터 각 바이트를 배열의 원소처럼 취급하기 쉬움
  • Little Endian: 물리적인 데이터 조작 및 산술 연산 수행 시 효율적
    • 변수 첫 바이트 주소가 바로 변수 주소
    • 하위 바이트에 주솟값으로 바로 접근하기 쉬움


결론

결과적으로 위에서 겪었던 문제 상황은 엔디언 개념을 이해하지 못했기 때문에 발생한 상황이다.

  • 회사 컴퓨터 CPU 아키텍처는 Intel 계열로서, Little Endian을 채택하는 프로세서임
  • hexdump 프로그램을 이용해 나타난 이진 데이터는 Little Endian 방식으로 읽어야 함
  • Little Endian 방식으로 읽을 경우, 위 mode 필드 값은 0x0001ModeNormal

위와 같은 상황을 제외하더라도, 엔디언 개념이 문제가 될 수 있는 상황이 있다. 특히 하나의 컴퓨터에서는 하나의 엔디언 방식이 적용되기 때문에 문제가 없지만, 2대 이상의 컴퓨터가 데이터를 주고 받아야 하는 경우, CPU 아키텍처가 다르면 엔디언이 달라 문제가 될 수 있다. 이 때문에 네트워크 통신 상에서는 CPU 아키텍처에 관계 없이 Big Endian 방식을 사용하도록 규정(Network Byte Order)되어 있다.


예시 코드

go 코드를 이용해 사용하고 있는 컴퓨터의 아키텍처에 따른 엔디언을 확인할 수 있다.

package main

import (
	"fmt"
	"runtime"
	"unsafe"
)

func main() {
	fmt.Printf("OS: %v, Architecture: %v\n", runtime.GOOS, runtime.GOARCH)
	a := uint16(0x0001)
	p := *(*uint8)(unsafe.Pointer(&a))
	fmt.Printf("The first byte of int16type 0x%04x: 0x%02x\n", a, p)
}
  • Go Playground에서 확인 시 결과: amd64 프로세서는 리틀 엔디언 구조를 채택하고 있음
    OS: linux, Architecture: amd64
    The first byte of int16type 0x0001: 0x01
    
  • MacOS에서 확인 시 결과: arm64 프로세서는 리틀 엔디언 구조를 채택하고 있음
    OS: darwin, Architecture: arm64
    The first byte of int16type 0x0001: 0x01
    
  • 회사 Window OS에서 확인 시 결과: amd64 프로세서로, 리틀 엔디언 구조를 채택하고 있음
    OS: windows, Architecture: arm64
    The first byte of int16type 0x0001: 0x01
    


hit count image

댓글남기기