[lv2/C++] 포인터 Pointer
Pointer라는 단어를 사전에서 찾아보면 '가리키는 것'이라는 의미가 나올 것이다. 이 의미처럼 프로그래밍 언어에서 포인터는 메모리 공간에 저장된 변수의 주소를 가리키는 변수를 말한다.
비유를 통해 좀 더 쉽게 접근해 보자면 우리는 모두 '집'이라는 곳에 살고있다. 집은 각각 위치를 나타내는 유일무이한 '주소'를 가지고 있으며 이 주소는 중복되지 않고 단 하나의 집만을 가리킨다. 그 공간에 살고 있는 우리는 자기 자신이 살고있는 주소를 알고있으며 그 공간안에 어떤 '물건'들이 있는지 확인할 수 있다. 여기서 '물건'은 메모리 공간 안에 있는 '값'에 비유하고, '주소'는 변수가 저장된 메모리의 '주소'이며, '집'은 메모리의 '공간'을 뜻한다. 이 때 '우리'는 각각의 포인터 변수로 본다. 우리는 각자 살고 있는 집의 주소(저장된주소)를 알고 있으며, 집의 크기(할당된 메모리 크기)를 잴 수 있고 집 안에 있는 물건(주소에 있는 값)들을 확인하거나 변경할 수 있다.
가상의 인물 철수(포인터)라는 사람은 집의 주소(저장된주소)를 알고 있어서 쉽게 접근할 수 있고 집에 있는 물건(값)을 꺼내어 사용할 수 있으며 이사를 갈 경우 집의 주소(저장된 주소)를 바꿀 수 있다.
이해가 우선시 되는 영역이라 서론이 길었지만 위 예시를 본다고 한 번에 이해가 될거라는 생각은 하지 않는다. 위 예시를 기억속에 넣고 아래에 있는 설명을 본 후 다시 읽어본다면 충분히 이해가 될 것이라 믿는다.
포인터의 선언과 사용
포인터 변수는 일반적인 변수 선언과 비슷하지만 변수명 앞에 '*'기호(간접참조연산자)가 붙는다. ( 변수타입 *변수이름 )
선언 후에는 *변수명 으로 가리키고 있는 공간에 들어있는 값을 참조할 수 있고 '&'기호(주소연산자)를 앞에 붙여 포인터 변수가 가리키고 있는 주소값을 알 수 있다. '*'연산자와 '&'연산자를 곱하기나 비트 연산자와 헷갈릴 수 있지만 어떤 연산자들이 있는지 기억이 안난다면 연산자와 우선순위를 외우고 오자.
포인터를 한번 사용해 보고 생각해 보자
#include <iostream>
using namespace std;
int main()
{
int value = 10; // 일반 변수 (물건)
int *ptr; // 포인터 변수 선언 (철수)
ptr = &value; // == int *ptr = &value; (선언시)
cout << "value = " << value << endl; // 물건
cout << "*ptr(value) = " << *ptr << endl; // 철수가 물건 확인
cout << "&value(address) = " << &value << endl; // 물건이 있는 곳
cout << "ptr(address) = " << ptr << endl; // 철수가 알고 있는 집 주소
cout << "&ptr(address) = " << &ptr << endl; // 철수가 현재 있는 곳
cout << "------------------\n";
*ptr = 20; // 철수가 물건 변경
cout << "value = " << value << endl; // 물건
cout << "*ptr(value) = " << *ptr << endl; // 철수가 물건 확인
cout << "&value(address) = " << &value << endl; // 물건이 있는 곳
cout << "ptr(address) = " << ptr << endl; // 철수가 알고 있는 집 주소
cout << "&ptr(address) = " << &ptr << endl; // 철수가 현재 있는 곳
return 0;
}
결과에서 중요한 것은 세 가지가 있다.
1. 포인터 변수 ptr은 value의 주소를 가지고 있으므로 *ptr 값을 변경하면 value값도 변경된다. (역참조)
2. value의 값이 변해도 메모리상 주소는 변하지 않는다.
3. 일반적인 변수와 포인터 변수가 메모리상 어디에 저장될지 모른다.
번외. value의 주소값과 ptr의 주소값이 12바이트 차이가 나는 것은 정의되지 않은 동작이다.
- C++ 기초를 배우고 있는 사람은 넘어가는게 정신 건강에 좋습니다.
- int자료형의 크기는 4Byte인데 컴파일러는 주소를 왜 연속적으로 할당하지 않는 것일까?
- ptr의 주소가 0x00EFFAD4일 때 다음 int타입 변수는 0xEFFAD8에 할당하는 것이 문제 요지가 있을까?
- stackoverflow에 따르면 12바이트 차이가 나는 것은 정의되지 않은 동작이라고 한다. 정의되지 않은 동작이란 무엇일까
정수 타입의 value 변수를 만들고 value에 10을 넣어주었고 포인터 변수 ptr을 만들어 value의 주소값을 넣어주었다.
value의 값은 당연하게도 10이 들어가 있고 역참조를한 포인터 변수 *ptr도 10이 출력된다. ptr이 가지고 있는 주소에 가서 값을 확인한 것이다. ptr에 value의 주소(&value)를 넣어 주었으니 ptr의 값과 value의 주소(&value)도 같음을 확인할 수 있다. value의 주소는 컴파일러가 임의로 지정한 것이며 포인터 변수 ptr의 주소 (&ptr) 또한 임의로 지정된 것이다.
아래에서 *ptr이 value의 주소에 있는 값을 20으로 변경하니 value 값 또한 20으로 변경되었고 주소의 변화는 없다. 집에 있는 물건을 변경한다고 집이 이사를 가지 않으니 당연한 이치다.
구현한 코드와 결과를 봤을 때 포인터 변수를 더 쉽게 정의할 수 있을 것 같다. 포인터 변수란 주소를 저장할 수 있는 변수이다. 즉 일반적으로 int 변수를 선언하면 정수형만 담을 수 있는 것처럼 포인터 변수를 선언하면 주소값('&')만 담을 수 있다. 포인터 변수가 주소를 가리키고 있고 주소에 있는 값을 참조하려면 '*'간접 참조 연산자가 필요하다.
대부분의 도서에는 포인터의 크기가 4 바이트로 고정된다고 설명하고 있지만 운영체제의 종류 컴파일의 종류에 따라 크기는 달라진다. 우리는 대부분 32bit로 컴파일 되어 4바이트지만 64bit로 컴파일 할 경우에는 8바이트 16bit로 컴파일 할 경우 2바이트가 된다.
'Programming > C, C++' 카테고리의 다른 글
LV2 C++ 동적 할당(Dynamic Allocation) (0) | 2022.09.08 |
---|---|
LV2 C++ 포인터(Pointer) 사용 (0) | 2022.09.08 |
LV1 C++ 데이터 형식(자료형) 종류와 범위 (0) | 2022.09.06 |
LV1 C++ 배열 Array 정리 (0) | 2022.09.06 |
LV1 C++ 반복문 while, do - while (0) | 2022.09.02 |