Search

포인터와 메모리 주소

Last update: @8/15/2023

메모리 주소

메모리는 하나의 byte 배열로 보면 된다.
각 byte는 주소를 가지고 있기 때문에 주소배열의 인덱스로 보면 된다.
메모리라는 배열에 저장되는 대부분의 데이터는 1바이트보다 크기 때문에 각 데이터의 첫 번째 byte 메모리 주소가 해당 데이터의 주소(또는 배열 인덱스의 주소)가 되는 것이다(big/little-endian 문제를 무시한다면).

변수(variable)

다음처럼 변수를 선언한다고 치자
int counter = 123;
C++
복사
counter라는 변수는 메인 메모리에서 세 가지 모습을 가진다.
변수 이름 - 컴파일러는 counter라는 변수 이름을 메인 메모리 주소와 매칭시킨다.
→ 컴파일러는 자기가 가진 메모리에 변수 이름과 메모리 주소를 매칭시킨 map을 만든다. 그리고 코드에서 해당 변수명을 모두 메모리 주소로 변환시킨다. 즉, 런타임에는 변수 이름이란 것은 없고, 메모리 주소만 있다고 보면 얼추 맞는다.
변수 내용 - 메인 메모리에 저장된 123
변수 주소 - 메인 메모리의 주소
변수명은 맥락에 따라서 다르게 쓰인다(context-sensitive).
변수명이 = 연산자 왼쪽에서 쓰일 때는 메모리 주소를 나타낸다(l-value).
변수명이 = 연산자 오른쪽에 쓰일 때는 해당 메모리 주소 내의 값을 나타낸다(r-value).

포인터와 메모리 주소를 헷갈리지 말자

종종 포인터와 메모리 주소를 혼동하는데, 메모리 주소는 변할 수 없고, 포인터는 변수의 한 타입일 뿐이기 때문에 그 내용물이 변할 수 있다는 점을 기억해야 한다.

포인터 연산자 - &

&는 피연산자의 주소를 얻어낸다
(a) 포인터 변수를 초기화하는 두 가지 방법. 결과는 (b)
(c) 포인터를 다른 포인터에 대입(assign)하면 오른쪽 포인터가 가지고 있던 값(본인의 주소값이 아닌 가리고 있는 주소값)이 복사되어 왼쪽의 포인터의 값으로 들어간다. 결과는 (d)
(e) C++에서, 포인터의 크기를 가리키고, 포인터가 가리키는 주소에 있는 데이터의 크기를 가리키지는 않는다.

포인터 연산자 - *

*은 두 가지 용도로 쓰인다. 포인터 쓸 때 가장 헷갈리는 부분이니 잘 구분해둬야 한다.
1.
포인터 변수를 정의할 때
→ 변수나 파라미터를 정의(define)할 때 쓰이는 경우, 포인터 변수임을 나타낸다.
int i; int* p; // p 는 포인터 i = 123; p = &i; // p는 i의 주소를 저장
C++
복사
void hello(int *i) { // i는 포인터 변수임을 나타냄 ... }
C++
복사
값을 얻어올 때 (== dereference == indirect == denote)
표현식(expression)에 쓰이면 포인터 변수를 dereference하여 포인터 변수에 저장된 주소값에 저장된 값을 얻어옴. Indirection Operator 라고 부른다.
int i = 5; int* p; p = &i; int n = *p; // n == 5
C++
복사
dereferencing 시 컴퓨터가 행하는 절차는 아래와 같다.
1.
포인터 변수 p에 매칭된 주소(0x0a000014)로 이동한다.
2.
해당 주소(0x0a000014)에 있는 주소값(0x0a000010)을 불러온다(load).
3.
불러온 주소값(0x0a000010)으로 이동한다.
4.
불러온 주소값에 저장된 값(123)을 불러온다.
아래처럼 하면 0x0a000010 주소 내의 값을 바꿀수도 있다.
*p = 456;
Go
복사

References