Search
Duplicate
📮

메모리 주소는 왜 16진수로 표현할까? (feat. printf)

간단소개
printf에서 %p 서식 지정자로 출력하는 메모리 주소가 왜 16진수인지 궁금해서 정리한 글입니다.
팔만코딩경 컨트리뷰터
ContributorNotionAccount
주제 / 분류
C
Scrap
태그
기초지식
9 more properties
목차

메모리 주소값을 16진수로 표현하는 이유?

결론부터 말하면, 2진수나 10진수보다 16진수가 주소값을 짧게 표현할 수 있기 때문이다.

컴퓨터는 기본적으로 0과 1만 처리할 수 있다.

컴퓨터는 기본적으로 2진법을 사용하고 있다.
컴퓨터는 전기 신호로 작동하는 스위치인 트랜지스터로 구성되어 있다. 트랜지스터는 전기 신호가 들어오지 않으면 꺼지고(OFF), 들어오면 켜진다(ON). 이를 컴퓨터는 0(OFF)과 1(ON)로 인식하고 계산하기 때문에 2진법을 사용하는 것이다.
즉, 컴퓨터는 2진법으로 표현되는 수를 통해 모든 정보를 주고 받는 것이다.

CPU의 최소 처리 단위, 바이트

CPU는 정보를 처리하기 위한 최소 단위를 8비트로 정하고, 이를 1바이트로 정의했다.
참고로 최소 단위가 8비트인 이유는 컴퓨터가 영미권에서 발전했기 때문이다. 2진법으로 이루어진 수를 사람이 이해하고 인식할 수 있는 문자로 저장하는 코드들의 숫자가 7~8비트로 충분했기 때문이다. 이를 숫자로 표현한 것이 ASCII 코드이다. 역사적으로 1바이트가 7비트, 9비트, 12비트로 존재했던 시기가 있기도 했지만, 표준화를 위해 대부분의 프로그래밍 언어에서 8비트는 1바이트를 의미한다.
1비트는 0 또는 1을 의미하며, 1바이트를 2진법으로 표현하면 다음과 같다.
1111 1111 0000 0010 0000 0000
Plain Text
복사
1바이트는 총 8자리 수로 표현할 수 있으며, 10진법으로 256개의 수(0 ~ 255)를 표현할 수 있다.
이를 16진법으로 나타내면 0x00 ~ 0xFF 로 표현할 수 있다. 16진법은 1부터 9까지 10진수와 동일하지만, 10부터 16까지는 알파벳 A~F로 표현한다. 0x 는 16진수임을 표현하기 위해 사용하며, 특별한 의미는 없다.
참고로 unsigned 자료형은 양의 정수값만 가지며, signed 자료형은 맨 왼쪽의 비트가 1 이면 음수로 취급한다.
1000 0001
Plain Text
복사
unsigned 자료형으로 위의 2진수는 10진법으로 129 이지만, signed 자료형으로는 -1 이다. 즉, unsigned 자료형의 최대값의 절반만큼 음수로 넘어간 것이 signed 자료형이라고 이해할 수 있다.

32비트 운영체제, 64비트 운영체제?

운영체제나 CPU의 데이터 기본 처리 단위인 워드(word)에 따라 한 번에 처리할 수 있는 데이터의 크기가 달라진다.
32비트, 64비트는 워드 사이즈를 의미하며, 32비트 머신(CPU)는 기본 데이터 처리 단위가 32비트가 되는 것이다.
32비트는 4바이트이고, 4바이트가 가질 수 있는 가장 큰 값을 2진법으로 표현하면 다음과 같다. (/ 는 1바이트 단위로 끊기 위해 사용)
1111 1111 / 1111 1111 / 1111 1111 / 1111 1111
Plain Text
복사
이를 10진법으로 바꾸면 4,294,967,295 (약 42억)이다.
64비트는 32비트보다 더욱 큰 18,446,744,073,709,551,615 (약 1844경)이다.
여기서 16진법이 큰 수를 표기하기 좋은 이유를 발견할 수 있다.

2진법 vs 10진법 vs 16진법

n진법이 수를 표현하기 위해 필요한 자리 수는 n 이 커질 수록 적어진다.
예를 들어, 10진법으로 32비트의 최대값인 4,294,967,295 표현하기 위해서 2진법과 16진법은 다음과 같이 표현한다.
2진법: 1111 1111 / 1111 1111 / 1111 1111 / 1111 1111 16진법: 0xFFFFFFFF
Plain Text
복사
2진법이 32자리를 사용해야 하는 것에 비해, 16진법은 8자리만 사용하면 된다. (0x 제외)
즉, 수가 커질 수록 16진법은 2진법이나 10진법에 비해 더욱 짧게 표현할 수 있으며, 표기의 편리함으로 인해 16진법으로 메모리 주소를 표기하는 것이다.

메모리 주소값을 10진수로 변환하기

위의 내용을 통해 16진수로 표현되는 메모리 주소값은 결국 10진수로 바꿀 수 있다는 것을 알 수 있다.
포인터 변수가 가지고 있는 주소값을 가져오기 위해서는 이중 포인터 개념을 이용해야 한다.
아래의 코드를 통해 이해해보자.
int main(void) { int n; int *ptr; n = 10; ptr = &n; return (0); }
C
복사
n 은 정수 10 을 저장하고, ptrn 의 주소값을 저장한 코드이다.
그림으로 표현하면 다음과 같으며, 주소값은 임의로 설정했다.
n 에 저장된 값을 가져오기 위해 포인터 변수 ptrn 의 주소값을 저장했다.
마찬가지로 포인터 변수 ptr 에 저장된 값을 가져오기 위해서는 다른 포인터 변수를 사용하면 된다.
이를 코드로 표현하면 다음과 같다.
#include <stdio.h> int main(void) { int n; int *ptr; int **d_ptr; n = 10; ptr = &n; d_ptr = &ptr; printf("%d\n", *d_ptr); printf("%p\n", ptr); printf("%p\n", *d_ptr); return (0); }
C
복사
위의 코드를 컴파일 옵션 없이 컴파일하면 오류가 발생하긴 하지만, 프로그램 작동에 큰 문제는 없다.
출력 결과는 다음과 같다.
-1133898280 0x7ff7b33df5d8 0x7ff7b33df5d8
C
복사
여기서 살펴봐야 할 것은 %d 로 출력한 d_ptr 의 값이 음의 정수로 출력된 것이다. 이는 위에서 언급한 운영체제의 데이터 기본 처리 단위인 워드와 관련이 있다.
위의 코드는 64비트 운영체제에서 실행했다. 기본 처리 단위가 8바이트이기 때문에 메모리 주소의 최대값은 0xFFFFFFFFFFFF 이다. 반면, int 는 4바이트 크기를 가지는데, 16진수로 표현할 수 있는 최대값은 0xFFFFFFFF 이다. 즉, int 형으로 출력한 메모리 주소값은 오버플로우가 발생해서 음수가 출력된 것이다.
따라서 메모리 주소값을 8바이트 크기에 맞는 자료형으로 캐스팅 해주어야 한다.
#include <stdio.h> int main(void) { int n; int *ptr; int **d_ptr; n = 10; ptr = &n; d_ptr = &ptr; printf("%zu\n", *d_ptr); printf("%p\n", ptr); printf("%p\n", *d_ptr); return (0); }
C
복사
참고로 %zusize_t 자료형의 포맷이다. 출력 결과는 다음과 같다.
140701993174488 0x7ff7bc5285d8 0x7ff7bc5285d8
C
복사
메모리 주소값이 양수로 표현된 것을 확인할 수 있다.
이처럼 메모리 주소값을 10진수로 변환하고, 다시 이를 16진수으로 변환하면 메모리 주소를 출력할 수 있다.

참고자료

size_t [cppreference.com]
printf [cplusplus.com]
Signed Binary Numbers [Electronics Tutorials]