본문 바로가기

[네트워크] Little Endian <-> Big Endian, binary <-> dotted-decimal 변환하기

출처 Randal E.Byrant, David R. O'Hallaron, 컴퓨터 시스템 Computer Systems A Programmer's Perspective, 김형신 옮김 (퍼스트북), Pearson

 

unsigned int 타입 (32비트, 4바이트) 변수에 숫자 0x12345678을 저장한다고 했을 때 리틀 엔디안 바이트 순서로 숫자를 저장할 수도 있고 빅 엔디안 바이트 순서로 숫자를 저장할 수 있다.

 

리틀 엔디안 바이트 순서를 사용할 경우, 낮은 메모리 주소에 최하위 바이트부터 저장하기 시작하여 메모리 주소가 높아질수록 상위 바이트를 저장한다. 그래서 다음과 같이 저장된다.

 

첫 번째 바이트 쪽으로 갈수록 더 큰 값을 나타내므로 첫 번째 바이트를 최상위 바이트라고 한다.

마지막 바이트 쪽으로 갈수록 더 작은 값을 나타내므로 마지막 바이트를 최하위 바이트라고 한다.

첫 번째 바이트 (1~8번째 비트) 두 번째 바이트 (9~16번째 비트) 3번째 바이트 (17~24번째 비트) 4번째 바이트 (25~32번째 비트)
0x12 0x34 0x56 0x78

 

0x12345678을 리틀 엔디안 바이트 순서로 저장하면 다음과 같은 모습으로 저장된다.

주소 0x100 주소 0x101 주소 0x102 주소 0x103
0x78 0x56 0x34 0x12

 

Linux 시스템은 Little Endian 바이트 순서를 사용한다. 정말 그런지 확인해보겠다.

 

첫 번째 방법은 숫자가 메모리에 어떻게 저장되는지 직접 출력해보는 방법이다.

아래에 정의한 print_system_byteorder_using_memory는 uint32_t 타입 (4byte 크기)의 변수 value에 0x12345678을 저장한 다음 uint8_t * 타입 (1byte 크기) 포인터 변수 ptr을 이용하여 각 바이트에 어떤 값이 담겨 있는지 확인하는 함수이다.

제일 낮은 메모리 주소에 최하위 바이트가 저장되면 리틀 엔디안 방식이다.
main 함수에서 위에서 정의한 print_system_byteorder_using_memory 함수를 호출한다.

가장 낮은 메모리 주소에 최하위 바이트가 담겨 있는 것을 확인할 수 있다.

 

두 번째 방법은 Linux의 <endian.h>에 정의되어 있는 매크로를 이용하는 방법이다. 아래와 같이 정의한 print_system_byteorder_using_macro 함수는 전처리 단계에서 시스템이 사용 중인 바이트 순서를 확인하는 함수이다.

__BYTE_ORDER, __LITTLE_ENDIAN, __BIG_ENDIAN 매크로를 이용한다.
위에서 정의한 print_system_byteorder_using_macro 함수를 호출한다.

 

첫 번째 방법과 같은 결과가 나온다.

 

빅 엔디안 바이트 순서를 사용할 경우, 낮은 메모리 주소에 최상위 바이트부터 저장하기 시작하여 메모리 주소가 높아질수록 하위 바이트를 저장한다. 그래서 다음과 같이 저장된다.

 

0x12345678을 리틀 엔디안 바이트 순서로 저장하면 다음과 같은 모습으로 저장된다.

주소 0x101 주소 0x102 주소 0x103 주소 0x104
0x12 0x34 0x56 0x78

CSAPP 연습문제 11.1과 11.2는 리틀 엔디안 16비트 16진수를 빅 엔디안 10진수로 변환하거나 빅 엔디안 16비트 10진수를 16비트 16진수로 변환하는 프로그램을 만드는 문제였다.

이 문제를 살짝 응용해서 IPv4의 32비트 16진수 IP 주소를 리틀 엔디안에서 빅 엔디안으로 변환하여 dotted-decimal 표기법으로 출력하거나 dotted-decimal 표기법으로 표현된 10진수 32비트 IP 주소를 리틀 엔디안으로 변환하여 16진수로 출력하는 프로그램을 만들어보겠다.

그리고 CSAPP의 모범답안을 보면 csapp.h와 csapp.c 파일을 활용하고 있는데 Linux를 사용할 경우 네트워크 프로그래밍에 필요한 표준 시스템 헤더 파일은 /usr/include에 이미 내장되어있다. 그래서 교재에서 제공하는 csapp.h와 csapp.c를 사용하지 않고 CSAPP 연습문제들을 풀이해보려고 한다. 기타 프로그래밍에 필요한 매크로나 wrapper 함수들은 csapp.c를 참고하여 사용할 예정이다.

 

hex2dd.c

헤더는 다음과 같이 include했고 inet_ntop 함수에 전달할 MAXBUF는 8MB 크기로 매크로로 정의했다.

 

코드는 다음과 같다.

- 16진수 32비트 숫자를 입력받은 다음 addr에 저장한다.

- htonl 함수를 이용하여 addr을 네트워크 바이트 순서로 변환하여 inaddr.s_addr에 저장한다. 

- inet_ntop 함수를 이용하여 inaddr.s_addr에 저장되어 있는 네트워크 바이트 순서의 이진 IP 주소를 dotted-decimal 표기법 문자열로 표현한다.

프로그램 실행 결과이다.

 

htonls 함수를 통해 IP 주소가 리틀 엔디안에서 빅 엔디안 순서로 변환됐다.

 

리틀 엔디안 (호스트 바이트 순서)

상위 바이트     하위 바이트
0xC0
(192)
0xA8
(168)
0x00
(0)
0x04
(4)

 

빅 엔디안 (네트워크 바이트 순서)

상위 바이트     하위 바이트
0x04
(4)
0x00
(0)
0xA8
(168)
0xC0
(192)

 

그리고 inet_ntops 함수를 통해 네트워크 바이트 순서의 이진 IP 주소를 dotted-decimal 문자열로 표현했다.

0x0400a8c0 (네트워크 바이트 순서) => 192.168.0.4

 

dd2hex.c

이번에는 네트워크 바이트 순서의 dotted-decimal 표기법의 문자열 IPv4 주소를 전달했을 때 호스트 바이트 순서의 32비트 16진수를 출력하는 프로그램이다. 헤더 및 매크로는 hex2dd와 같으니 생략하고 main 함수와 실행 결과만 보겠다.

 

- dotted-decimal 문자열 IP 주소를 입력받는다.

- inet_pton 함수를 이용하여 문자열을 이진 IP 주소로 변환하여 inaddr 구조체에 저장한다.
(처음에는 int32_t * 타입 포인터 주소에 저장하려고 했는데, in_addr * 타입 포인터 주소에 저장하는 게 관습이라고 한다.)

- ntohl 함수를 이용하여 네트워크 바이트 순서를 호스트 바이트 순서로 변환한다.